Merge branch 'master' into audio
@ -4,12 +4,14 @@ go:
|
||||
- 1.4
|
||||
|
||||
before_install:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- export DISPLAY=:99.0
|
||||
- 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-updates main restricted universe multiverse'
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -qq libglew-dev libglfw3-dev
|
||||
- export NODE_PATH=$(npm config get prefix)/lib/node_modules
|
||||
- npm install --global gl
|
||||
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
@ -18,6 +20,7 @@ install:
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
||||
- gopherjs test -v github.com/hajimehoshi/ebiten github.com/hajimehoshi/ebiten/internal
|
||||
- gopherjs build -v github.com/hajimehoshi/ebiten/example/blocks
|
||||
|
||||
notifications:
|
||||
|
18
_docs/gen.go
@ -151,23 +151,29 @@ func clear() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if m, _ := regexp.MatchString("~$", path); m {
|
||||
return nil
|
||||
}
|
||||
// Remove auto-generated html files.
|
||||
m, err := regexp.MatchString(".html$", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !m {
|
||||
return nil
|
||||
if m {
|
||||
return os.Remove(path)
|
||||
}
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
if !m {
|
||||
return nil
|
||||
if m {
|
||||
if err := os.RemoveAll(path); err != nil {
|
||||
return err
|
||||
}
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return os.Remove(path)
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ pre {
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li>2D Graphics</li>
|
||||
<li>Input (Mouse, Keyboard)</li>
|
||||
<li>Input (Mouse, Keyboard, Gamepad)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Example</h2>
|
||||
@ -61,7 +61,7 @@ pre {
|
||||
:; go get github.com/gopherjs/webgl</code></pre>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<ul>
|
||||
<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
|
||||
|
||||
## blocks/font.png
|
||||
## arcadefont.png
|
||||
|
||||
```
|
||||
9031 Font ReadMe
|
||||
|
@ -41,10 +41,9 @@ pre {
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/example/keyboard/keyboard"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -52,13 +51,23 @@ const (
|
||||
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{
|
||||
ebiten.KeyBackspace: "Backspace",
|
||||
ebiten.KeyComma: "','",
|
||||
ebiten.KeyDelete: "Delete",
|
||||
ebiten.KeyBackspace: "BS",
|
||||
ebiten.KeyComma: ",",
|
||||
ebiten.KeyDelete: "Del",
|
||||
ebiten.KeyEnter: "Enter",
|
||||
ebiten.KeyEscape: "Esc",
|
||||
ebiten.KeyPeriod: "'.'",
|
||||
ebiten.KeyPeriod: ".",
|
||||
ebiten.KeySpace: "Space",
|
||||
ebiten.KeyTab: "Tab",
|
||||
|
||||
@ -74,7 +83,32 @@ var keyNames = map[ebiten.Key]string{
|
||||
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 {
|
||||
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{}
|
||||
for i := 0; i <= 9; i++ {
|
||||
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
||||
@ -96,9 +130,13 @@ func update(screen *ebiten.Image) error {
|
||||
pressed = append(pressed, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(pressed)
|
||||
str := "Pressed Keys: " + strings.Join(pressed, ", ")
|
||||
ebitenutil.DebugPrint(screen, str)
|
||||
|
||||
op = &ebiten.DrawImageOptions{
|
||||
ImageParts: pressedKeysParts(pressed),
|
||||
}
|
||||
op.GeoM.Translate(offsetX, offsetY)
|
||||
screen.DrawImage(keyboardImage, op)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ pre {
|
||||
<h2>Features</h2>
|
||||
<ul>
|
||||
<li>2D Graphics</li>
|
||||
<li>Input (Mouse, Keyboard)</li>
|
||||
<li>Input (Mouse, Keyboard, Gamepad)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Example</h2>
|
||||
@ -87,7 +87,7 @@ pre {
|
||||
:; go get github.com/gopherjs/webgl</code></pre>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<ul>
|
||||
<li>v1.1.0-rc1 released.
|
||||
|
@ -23,6 +23,9 @@ import (
|
||||
)
|
||||
|
||||
// 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) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
|
@ -16,98 +16,18 @@ package blocks
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/example/internal"
|
||||
"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 {
|
||||
w := textWidth(str) * scale
|
||||
w := internal.ArcadeFont.TextWidth(str) * scale
|
||||
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 {
|
||||
w := textWidth(str) * scale
|
||||
w := internal.ArcadeFont.TextWidth(str) * scale
|
||||
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 {
|
||||
once sync.Once
|
||||
sceneManager *SceneManager
|
||||
input *Input
|
||||
input Input
|
||||
}
|
||||
|
||||
func NewGame() *Game {
|
||||
game := &Game{
|
||||
return &Game{
|
||||
sceneManager: NewSceneManager(NewTitleScene()),
|
||||
input: NewInput(),
|
||||
}
|
||||
return game
|
||||
}
|
||||
|
||||
func (game *Game) Update(r *ebiten.Image) error {
|
||||
game.input.Update()
|
||||
game.sceneManager.Update(&GameState{
|
||||
SceneManager: game.sceneManager,
|
||||
Input: game.input,
|
||||
Input: &game.input,
|
||||
})
|
||||
game.sceneManager.Draw(r)
|
||||
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 (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/example/internal"
|
||||
"image/color"
|
||||
_ "image/jpeg"
|
||||
"math/rand"
|
||||
@ -25,7 +26,6 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
imageEmpty *ebiten.Image
|
||||
imageGameBG *ebiten.Image
|
||||
imageWindows *ebiten.Image
|
||||
imageGameover *ebiten.Image
|
||||
@ -67,12 +67,6 @@ func linesTextBoxPosition() (x, y int) {
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
imageEmpty, err = ebiten.NewImage(16, 16, ebiten.FilterNearest)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
imageEmpty.Fill(color.White)
|
||||
|
||||
// Background
|
||||
imageGameBG, _, err = ebitenutil.NewImageFromFile("images/gophers.jpg", ebiten.FilterLinear)
|
||||
if err != nil {
|
||||
@ -91,7 +85,7 @@ func init() {
|
||||
}
|
||||
// Windows: Next
|
||||
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)
|
||||
}
|
||||
x, y = nextWindowPosition()
|
||||
@ -127,18 +121,13 @@ func init() {
|
||||
}
|
||||
|
||||
func drawWindow(r *ebiten.Image, x, y, width, height int) error {
|
||||
w, h := imageEmpty.Size()
|
||||
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)
|
||||
return r.DrawFilledRect(x, y, width, height, color.NRGBA{0, 0, 0, 0xc0})
|
||||
}
|
||||
|
||||
var fontColor = color.NRGBA{0x40, 0x40, 0xff, 0xff}
|
||||
|
||||
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
|
||||
}
|
||||
y += blockWidth
|
||||
@ -249,6 +238,7 @@ func (s *GameScene) Update(state *GameState) error {
|
||||
s.field.Update()
|
||||
|
||||
if s.gameover {
|
||||
// TODO: Gamepad key?
|
||||
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
||||
state.SceneManager.GoTo(NewTitleScene())
|
||||
}
|
||||
@ -274,23 +264,23 @@ func (s *GameScene) Update(state *GameState) error {
|
||||
piece := s.currentPiece
|
||||
x := s.currentPieceX
|
||||
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)
|
||||
moved = angle != s.currentPieceAngle
|
||||
}
|
||||
if state.Input.StateForKey(ebiten.KeyZ) == 1 {
|
||||
if state.Input.IsRotateLeftTrigger() {
|
||||
s.currentPieceAngle = s.field.RotatePieceLeft(piece, x, y, angle)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
moved = y != s.currentPieceY
|
||||
if moved {
|
||||
@ -314,7 +304,7 @@ func (s *GameScene) Update(state *GameState) error {
|
||||
if moved {
|
||||
s.landingCount = 0
|
||||
} 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
|
||||
} else {
|
||||
s.landingCount++
|
||||
|
@ -16,26 +16,97 @@ package blocks
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/exp/gamepad"
|
||||
)
|
||||
|
||||
type Input struct {
|
||||
states [256]int
|
||||
var gamepadStdButtons = []gamepad.StdButton{
|
||||
gamepad.StdButtonLL,
|
||||
gamepad.StdButtonLR,
|
||||
gamepad.StdButtonLD,
|
||||
gamepad.StdButtonRD,
|
||||
gamepad.StdButtonRR,
|
||||
}
|
||||
|
||||
func NewInput() *Input {
|
||||
return &Input{}
|
||||
type Input struct {
|
||||
keyStates [256]int
|
||||
gamepadButtonStates [256]int
|
||||
gamepadStdButtonStates [16]int
|
||||
gamepadConfig gamepad.Configuration
|
||||
}
|
||||
|
||||
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() {
|
||||
for key := range i.states {
|
||||
for key := range i.keyStates {
|
||||
if !ebiten.IsKeyPressed(ebiten.Key(key)) {
|
||||
i.states[key] = 0
|
||||
i.keyStates[key] = 0
|
||||
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 (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/example/internal"
|
||||
"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 {
|
||||
s.count++
|
||||
if state.Input.StateForKey(ebiten.KeySpace) == 1 {
|
||||
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
|
||||
}
|
||||
@ -83,9 +112,12 @@ func (s *TitleScene) Draw(r *ebiten.Image) error {
|
||||
}
|
||||
|
||||
message := "PRESS SPACE TO START"
|
||||
x := (ScreenWidth - textWidth(message)) / 2
|
||||
x := (ScreenWidth - internal.ArcadeFont.TextWidth(message)) / 2
|
||||
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 {
|
||||
@ -97,8 +129,8 @@ func (s *TitleScene) drawTitleBackground(r *ebiten.Image, c int) error {
|
||||
|
||||
func drawLogo(r *ebiten.Image, str string) error {
|
||||
scale := 4
|
||||
textWidth := textWidth(str) * scale
|
||||
textWidth := internal.ArcadeFont.TextWidth(str) * scale
|
||||
x := (ScreenWidth - textWidth) / 2
|
||||
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
|
||||
|
||||
## blocks/font.png
|
||||
## arcadefont.png
|
||||
|
||||
```
|
||||
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 (
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/ebitenutil"
|
||||
"github.com/hajimehoshi/ebiten/example/keyboard/keyboard"
|
||||
"log"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -28,13 +27,23 @@ const (
|
||||
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{
|
||||
ebiten.KeyBackspace: "Backspace",
|
||||
ebiten.KeyComma: "','",
|
||||
ebiten.KeyDelete: "Delete",
|
||||
ebiten.KeyBackspace: "BS",
|
||||
ebiten.KeyComma: ",",
|
||||
ebiten.KeyDelete: "Del",
|
||||
ebiten.KeyEnter: "Enter",
|
||||
ebiten.KeyEscape: "Esc",
|
||||
ebiten.KeyPeriod: "'.'",
|
||||
ebiten.KeyPeriod: ".",
|
||||
ebiten.KeySpace: "Space",
|
||||
ebiten.KeyTab: "Tab",
|
||||
|
||||
@ -50,7 +59,32 @@ var keyNames = map[ebiten.Key]string{
|
||||
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 {
|
||||
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{}
|
||||
for i := 0; i <= 9; i++ {
|
||||
if ebiten.IsKeyPressed(ebiten.Key(i) + ebiten.Key0) {
|
||||
@ -72,9 +106,13 @@ func update(screen *ebiten.Image) error {
|
||||
pressed = append(pressed, name)
|
||||
}
|
||||
}
|
||||
sort.Strings(pressed)
|
||||
str := "Pressed Keys: " + strings.Join(pressed, ", ")
|
||||
ebitenutil.DebugPrint(screen, str)
|
||||
|
||||
op = &ebiten.DrawImageOptions{
|
||||
ImageParts: pressedKeysParts(pressed),
|
||||
}
|
||||
op.GeoM.Translate(offsetX, offsetY)
|
||||
screen.DrawImage(keyboardImage, op)
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
// 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 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) {
|
||||
var innerImage *innerImage
|
||||
var img *Image
|
||||
var err error
|
||||
ui.Use(func(c *opengl.Context) {
|
||||
var texture *graphics.Texture
|
||||
var framebuffer *graphics.Framebuffer
|
||||
texture, err = graphics.NewTexture(c, width, height, glFilter(c, filter))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
innerImage, err = newInnerImage(c, texture)
|
||||
innerImage.Clear(c)
|
||||
framebuffer, err = graphics.NewFramebufferFromTexture(c, texture)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
img = &Image{framebuffer: framebuffer, texture: texture}
|
||||
})
|
||||
if err != nil {
|
||||
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 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) {
|
||||
var innerImage *innerImage
|
||||
var eimg *Image
|
||||
var err error
|
||||
ui.Use(func(c *opengl.Context) {
|
||||
var texture *graphics.Texture
|
||||
var framebuffer *graphics.Framebuffer
|
||||
texture, err = graphics.NewTextureFromImage(c, img, glFilter(c, filter))
|
||||
if err != nil {
|
||||
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 {
|
||||
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
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"github.com/hajimehoshi/ebiten/internal"
|
||||
"log"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
)
|
||||
|
||||
@ -224,12 +223,10 @@ func (k KeyNames) Swap(i, j int) {
|
||||
}
|
||||
|
||||
func main() {
|
||||
l, err := ioutil.ReadFile("license.txt")
|
||||
license, err := internal.LicenseComment()
|
||||
if err != nil {
|
||||
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."
|
||||
|
||||
@ -263,13 +260,15 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// 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,
|
||||
"Notice": notice,
|
||||
"KeyCodeToName": keyCodeToName,
|
||||
"Codes": codes,
|
||||
"KeyNames": names,
|
||||
"KeyNamesWithoutMods": namesWithoutMods,
|
||||
})
|
||||
}); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ const (
|
||||
FilterLinear
|
||||
)
|
||||
|
||||
func glFilter(c *opengl.Context, filter Filter) opengl.FilterType {
|
||||
func glFilter(c *opengl.Context, filter Filter) opengl.Filter {
|
||||
switch filter {
|
||||
case FilterNearest:
|
||||
return c.Nearest
|
||||
|
@ -29,14 +29,14 @@ func newGraphicsContext(c *opengl.Context, screenWidth, screenHeight, screenScal
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
screen, err := newInnerImage(c, texture)
|
||||
screenF, err := graphics.NewFramebufferFromTexture(c, texture)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
screen := &Image{framebuffer: screenF, texture: texture}
|
||||
return &graphicsContext{
|
||||
glContext: c,
|
||||
defaultR: &innerImage{f, nil},
|
||||
defaultR: &Image{framebuffer: f, texture: nil},
|
||||
screen: screen,
|
||||
screenScale: screenScale,
|
||||
}, nil
|
||||
@ -44,8 +44,8 @@ func newGraphicsContext(c *opengl.Context, screenWidth, screenHeight, screenScal
|
||||
|
||||
type graphicsContext struct {
|
||||
glContext *opengl.Context
|
||||
screen *innerImage
|
||||
defaultR *innerImage
|
||||
screen *Image
|
||||
defaultR *Image
|
||||
screenScale int
|
||||
}
|
||||
|
||||
@ -59,18 +59,18 @@ func (c *graphicsContext) dispose() {
|
||||
}
|
||||
|
||||
func (c *graphicsContext) preUpdate() error {
|
||||
return c.screen.Clear(c.glContext)
|
||||
return c.screen.Clear()
|
||||
}
|
||||
|
||||
func (c *graphicsContext) postUpdate() error {
|
||||
if err := c.defaultR.Clear(c.glContext); err != nil {
|
||||
if err := c.defaultR.Clear(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scale := float64(c.screenScale)
|
||||
options := &DrawImageOptions{}
|
||||
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 nil
|
||||
|
215
image.go
@ -16,6 +16,7 @@ package ebiten
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/hajimehoshi/ebiten/internal"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics"
|
||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||
@ -24,141 +25,32 @@ import (
|
||||
"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.
|
||||
// The pixel format is alpha-premultiplied.
|
||||
// Image implements image.Image.
|
||||
type Image struct {
|
||||
inner *innerImage
|
||||
pixels []uint8
|
||||
framebuffer *graphics.Framebuffer
|
||||
texture *graphics.Texture
|
||||
pixels []uint8
|
||||
}
|
||||
|
||||
// Size returns the size of the image.
|
||||
func (i *Image) Size() (width, height int) {
|
||||
return i.inner.size()
|
||||
return i.framebuffer.Size()
|
||||
}
|
||||
|
||||
// Clear resets the pixels of the image into 0.
|
||||
func (i *Image) Clear() (err error) {
|
||||
i.pixels = nil
|
||||
ui.Use(func(c *opengl.Context) {
|
||||
err = i.inner.Clear(c)
|
||||
})
|
||||
return
|
||||
return i.Fill(color.Transparent)
|
||||
}
|
||||
|
||||
// Fill fills the image with a solid color.
|
||||
func (i *Image) Fill(clr color.Color) (err error) {
|
||||
i.pixels = nil
|
||||
r, g, b, a := internal.RGBA(clr)
|
||||
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
|
||||
}
|
||||
@ -181,20 +73,68 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) {
|
||||
if i == image {
|
||||
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) {
|
||||
i.pixels = nil
|
||||
// DrawLine draws a line.
|
||||
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) {
|
||||
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
|
||||
}
|
||||
|
||||
// Bounds returns the bounds of the image.
|
||||
func (i *Image) Bounds() image.Rectangle {
|
||||
w, h := i.inner.size()
|
||||
w, h := i.Size()
|
||||
return image.Rect(0, 0, w, h)
|
||||
}
|
||||
|
||||
@ -210,30 +150,35 @@ func (i *Image) At(x, y int) color.Color {
|
||||
if i.pixels == nil {
|
||||
ui.Use(func(c *opengl.Context) {
|
||||
var err error
|
||||
i.pixels, err = i.inner.texture.Pixels(c)
|
||||
i.pixels, err = i.framebuffer.Pixels(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
w, _ := i.inner.size()
|
||||
w, _ := i.Size()
|
||||
w = internal.NextPowerOf2Int(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]
|
||||
return color.RGBA{r, g, b, a}
|
||||
}
|
||||
|
||||
// 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)
|
||||
// ReplacePixels replaces the pixels of the image with p.
|
||||
//
|
||||
// The given p must represent RGBA pre-multiplied alpha values. len(p) must equal to 4 * (image width) * (image height).
|
||||
//
|
||||
// This function may be slow (as for implementation, this calls glTexSubImage2D).
|
||||
func (i *Image) ReplacePixels(p []uint8) error {
|
||||
w, h := i.Size()
|
||||
l := 4 * w * h
|
||||
if len(p) != l {
|
||||
return errors.New(fmt.Sprintf("p's length must be %d", l))
|
||||
}
|
||||
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.
|
||||
|
@ -18,19 +18,29 @@ import (
|
||||
. "github.com/hajimehoshi/ebiten"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
_ "image/png"
|
||||
"os"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func openImage(path string) (*Image, image.Image, error) {
|
||||
file, err := os.Open(path)
|
||||
var ebitenImageBin = ""
|
||||
|
||||
func openImage(path string) (image.Image, error) {
|
||||
file, err := readFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
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 {
|
||||
return nil, nil, err
|
||||
}
|
||||
@ -50,22 +60,22 @@ func diff(x, y uint8) uint8 {
|
||||
}
|
||||
|
||||
func TestImagePixels(t *testing.T) {
|
||||
eimg, img, err := openImage("testdata/ebiten.png")
|
||||
img0, img, err := openEbitenImage("testdata/ebiten.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
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())
|
||||
}
|
||||
|
||||
for j := 0; j < eimg.Bounds().Size().Y; j++ {
|
||||
for i := 0; i < eimg.Bounds().Size().X; i++ {
|
||||
got := eimg.At(i, j)
|
||||
for j := 0; j < img0.Bounds().Size().Y; j++ {
|
||||
for i := 0; i < img0.Bounds().Size().X; i++ {
|
||||
got := img0.At(i, j)
|
||||
want := color.RGBAModel.Convert(img.At(i, j))
|
||||
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}
|
||||
img3Color := color.NRGBA{0x85, 0xa3, 0x08, 0xd3}
|
||||
|
||||
img1, _, err := openImage("testdata/ebiten.png")
|
||||
img1, _, err := openEbitenImage("testdata/ebiten.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
@ -135,7 +145,7 @@ func TestImageComposition(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImageSelf(t *testing.T) {
|
||||
img, _, err := openImage("testdata/ebiten.png")
|
||||
img, _, err := openEbitenImage("testdata/ebiten.png")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
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)
|
||||
|
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
|
||||
}
|
||||
|
||||
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}
|
||||
return a, nil
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"github.com/hajimehoshi/ebiten/internal"
|
||||
"github.com/hajimehoshi/ebiten/internal/graphics/internal/shader"
|
||||
"github.com/hajimehoshi/ebiten/internal/opengl"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
func orthoProjectionMatrix(left, right, bottom, top int) *[4][4]float64 {
|
||||
@ -94,21 +95,55 @@ type Matrix interface {
|
||||
|
||||
type TextureQuads interface {
|
||||
Len() int
|
||||
Vertex(i int) (x0, y0, x1, y1 float32)
|
||||
Texture(i int) (u0, v0, u1, v1 float32)
|
||||
Vertex(i int) (x0, y0, x1, y1 int)
|
||||
Texture(i int) (u0, v0, u1, v1 int)
|
||||
}
|
||||
|
||||
func (f *Framebuffer) Fill(c *opengl.Context, r, g, b, a float64) error {
|
||||
if err := f.setAsViewport(c); err != nil {
|
||||
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 {
|
||||
if err := f.setAsViewport(c); err != nil {
|
||||
return err
|
||||
}
|
||||
projectionMatrix := f.projectionMatrix()
|
||||
return shader.DrawTexture(c, t.native, projectionMatrix, quads, geo, clr)
|
||||
p := f.projectionMatrix()
|
||||
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 (
|
||||
"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 {
|
||||
const size = 10000
|
||||
const uint16Size = 2
|
||||
|
||||
shaderVertexNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertex))
|
||||
shaderVertexModelviewNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertexModelview))
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
defer c.DeleteShader(shaderColorMatrixNative)
|
||||
defer c.DeleteShader(shaderVertexColorNative)
|
||||
|
||||
shaders := []opengl.Shader{
|
||||
shaderVertexNative,
|
||||
shaderColorMatrixNative,
|
||||
shaderVertexColorLineNative, err := c.NewShader(c.VertexShader, shader(c, shaderVertexColorLine))
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
const stride = 4 * 4
|
||||
c.NewBuffer(c.ArrayBuffer, stride*size, c.DynamicDraw)
|
||||
programSolidRect, err = c.NewProgram([]opengl.Shader{
|
||||
shaderVertexColorNative,
|
||||
shaderFragmentSolidNative,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
indices := make([]uint16, 6*size)
|
||||
for i := uint16(0); i < size; i++ {
|
||||
programSolidLine, err = c.NewProgram([]opengl.Shader{
|
||||
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+1] = 4*i + 1
|
||||
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+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
|
||||
}
|
||||
|
||||
var lastProgram opengl.Program
|
||||
|
||||
func useProgramColorMatrix(c *opengl.Context, projectionMatrix []float32, geo Matrix, color Matrix) opengl.Program {
|
||||
if lastProgram != programColorMatrix {
|
||||
c.UseProgram(programColorMatrix)
|
||||
lastProgram = programColorMatrix
|
||||
type programFinisher func()
|
||||
|
||||
func (p programFinisher) FinishProgram() {
|
||||
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)
|
||||
|
||||
@ -107,5 +169,65 @@ func useProgramColorMatrix(c *opengl.Context, projectionMatrix []float32, geo Ma
|
||||
}
|
||||
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
|
||||
|
||||
const (
|
||||
shaderVertex shaderId = iota
|
||||
shaderColorMatrix
|
||||
shaderVertexModelview shaderId = iota
|
||||
shaderVertexColor
|
||||
shaderVertexColorLine
|
||||
shaderFragmentTexture
|
||||
shaderFragmentSolid
|
||||
)
|
||||
|
||||
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{
|
||||
shaderVertex: `
|
||||
shaderVertexModelview: `
|
||||
uniform highp mat4 projection_matrix;
|
||||
uniform highp mat4 modelview_matrix;
|
||||
attribute highp vec2 vertex;
|
||||
@ -48,7 +51,29 @@ void main(void) {
|
||||
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 mat4 color_matrix;
|
||||
uniform lowp vec4 color_matrix_translation;
|
||||
@ -69,5 +94,12 @@ void main(void) {
|
||||
|
||||
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),
|
||||
},
|
||||
}
|
||||
if nrgba, ok := img.(*image.RGBA); ok && img.Bounds() == adjustedImageBounds {
|
||||
return nrgba
|
||||
if adjustedImage, ok := img.(*image.RGBA); ok && img.Bounds() == adjustedImageBounds {
|
||||
return adjustedImage
|
||||
}
|
||||
|
||||
adjustedImage := image.NewRGBA(adjustedImageBounds)
|
||||
@ -54,7 +54,7 @@ func (t *Texture) Size() (width, height int) {
|
||||
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)
|
||||
h := internal.NextPowerOf2Int(height)
|
||||
if w < 4 {
|
||||
@ -70,7 +70,7 @@ func NewTexture(c *opengl.Context, width, height int, filter opengl.FilterType)
|
||||
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()
|
||||
if origSize.X < 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)
|
||||
}
|
||||
|
||||
func (t *Texture) Pixels(c *opengl.Context) ([]uint8, error) {
|
||||
w, h := internal.NextPowerOf2Int(t.width), internal.NextPowerOf2Int(t.height)
|
||||
return c.TexturePixels(t.native, w, h)
|
||||
func (t *Texture) ReplacePixels(c *opengl.Context, p []uint8) error {
|
||||
c.BindTexture(t.native)
|
||||
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"
|
||||
)
|
||||
|
||||
type Texture int
|
||||
type Framebuffer int
|
||||
type Shader int
|
||||
type Program int
|
||||
type UniformLocation int
|
||||
type AttribLocation int
|
||||
type Texture gl.Texture
|
||||
type Framebuffer gl.Framebuffer
|
||||
type Shader gl.Shader
|
||||
type Program gl.Program
|
||||
type Buffer gl.Buffer
|
||||
|
||||
// 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{}
|
||||
|
||||
@ -41,6 +54,8 @@ func NewContext() *Context {
|
||||
ElementArrayBuffer: gl.ELEMENT_ARRAY_BUFFER,
|
||||
DynamicDraw: gl.DYNAMIC_DRAW,
|
||||
StaticDraw: gl.STATIC_DRAW,
|
||||
Triangles: gl.TRIANGLES,
|
||||
Lines: gl.LINES,
|
||||
}
|
||||
c.init()
|
||||
return c
|
||||
@ -53,7 +68,7 @@ func (c *Context) init() {
|
||||
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()
|
||||
if t < 0 {
|
||||
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
|
||||
}
|
||||
|
||||
func (c *Context) TexturePixels(t Texture, width, height int) ([]uint8, error) {
|
||||
func (c *Context) FramebufferPixels(f Framebuffer, width, height int) ([]uint8, error) {
|
||||
gl.Flush()
|
||||
// TODO: Use glGetTexLevelParameteri and GL_TEXTURE_WIDTH?
|
||||
|
||||
gl.Framebuffer(f).Bind()
|
||||
|
||||
pixels := make([]uint8, 4*width*height)
|
||||
gl.Texture(t).Bind(gl.TEXTURE_2D)
|
||||
gl.GetTexImage(gl.TEXTURE_2D, 0, 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 {
|
||||
return nil, errors.New(fmt.Sprintf("gl error: %d", e))
|
||||
}
|
||||
@ -89,6 +105,10 @@ func (c *Context) DeleteTexture(t Texture) {
|
||||
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) {
|
||||
f := gl.GenFramebuffer()
|
||||
f.Bind()
|
||||
@ -115,7 +135,7 @@ func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
||||
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.Clear(gl.COLOR_BUFFER_BIT)
|
||||
return nil
|
||||
@ -173,13 +193,17 @@ func (c *Context) UseProgram(p Program) {
|
||||
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) {
|
||||
l := gl.Program(p).GetUniformLocation(location)
|
||||
l := gl.UniformLocation(GetUniformLocation(c, p, location))
|
||||
l.Uniform1i(v)
|
||||
}
|
||||
|
||||
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) {
|
||||
case 4:
|
||||
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) {
|
||||
gl.Program(p).GetAttribLocation(location).AttribPointer(2, gl.FLOAT, false, stride, v)
|
||||
func (c *Context) GetAttribLocation(p Program, location string) AttribLocation {
|
||||
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) {
|
||||
gl.Program(p).GetAttribLocation(location).EnableArray()
|
||||
l := gl.AttribLocation(GetAttribLocation(c, p, location))
|
||||
l.EnableArray()
|
||||
}
|
||||
|
||||
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) {
|
||||
gl.GenBuffer().Bind(gl.GLenum(bufferType))
|
||||
func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsage BufferUsage) Buffer {
|
||||
b := gl.GenBuffer()
|
||||
b.Bind(gl.GLenum(bufferType))
|
||||
size := 0
|
||||
ptr := v
|
||||
switch v := v.(type) {
|
||||
@ -219,16 +255,21 @@ func (c *Context) NewBuffer(bufferType BufferType, v interface{}, bufferUsageTyp
|
||||
default:
|
||||
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) {
|
||||
const float32Size = 4
|
||||
gl.BufferSubData(gl.GLenum(bufferType), 0, float32Size*len(data), data)
|
||||
func (c *Context) BindElementArrayBuffer(b Buffer) {
|
||||
gl.Buffer(b).Bind(gl.ELEMENT_ARRAY_BUFFER)
|
||||
}
|
||||
|
||||
func (c *Context) DrawElements(len int) {
|
||||
gl.DrawElements(gl.TRIANGLES, len, gl.UNSIGNED_SHORT, uintptr(0))
|
||||
func (c *Context) BufferSubData(bufferType BufferType, data []int16) {
|
||||
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() {
|
||||
|
@ -23,27 +23,61 @@ import (
|
||||
"github.com/gopherjs/webgl"
|
||||
)
|
||||
|
||||
type Texture js.Object
|
||||
type Framebuffer js.Object
|
||||
type Shader js.Object
|
||||
type Program js.Object
|
||||
type UniformLocation js.Object
|
||||
type Texture struct {
|
||||
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 ProgramID int
|
||||
|
||||
func GetProgramID(p Program) ProgramID {
|
||||
return ProgramID(p.Get("__ebiten_programId").Int())
|
||||
}
|
||||
|
||||
type context struct {
|
||||
gl *webgl.Context
|
||||
}
|
||||
|
||||
var lastFramebuffer Framebuffer
|
||||
|
||||
func NewContext(gl *webgl.Context) *Context {
|
||||
c := &Context{
|
||||
Nearest: FilterType(gl.NEAREST),
|
||||
Linear: FilterType(gl.LINEAR),
|
||||
Nearest: Filter(gl.NEAREST),
|
||||
Linear: Filter(gl.LINEAR),
|
||||
VertexShader: ShaderType(gl.VERTEX_SHADER),
|
||||
FragmentShader: ShaderType(gl.FRAGMENT_SHADER),
|
||||
ArrayBuffer: BufferType(gl.ARRAY_BUFFER),
|
||||
ElementArrayBuffer: BufferType(gl.ELEMENT_ARRAY_BUFFER),
|
||||
DynamicDraw: BufferUsageType(gl.DYNAMIC_DRAW),
|
||||
StaticDraw: BufferUsageType(gl.STATIC_DRAW),
|
||||
DynamicDraw: BufferUsage(gl.DYNAMIC_DRAW),
|
||||
StaticDraw: BufferUsage(gl.STATIC_DRAW),
|
||||
Triangles: Mode(gl.TRIANGLES),
|
||||
Lines: Mode(gl.LINES),
|
||||
}
|
||||
c.gl = gl
|
||||
c.init()
|
||||
@ -57,11 +91,11 @@ func (c *Context) init() {
|
||||
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
|
||||
t := gl.CreateTexture()
|
||||
if t == nil {
|
||||
return nil, errors.New("glGenTexture failed")
|
||||
return Texture{nil}, errors.New("glGenTexture failed")
|
||||
}
|
||||
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
|
||||
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_MIN_FILTER, int(filter))
|
||||
|
||||
// TODO: Can we use glTexSubImage2D with linear filtering?
|
||||
|
||||
// void texImage2D(GLenum target, GLint level, GLenum internalformat,
|
||||
// GLsizei width, GLsizei height, GLint border, GLenum format,
|
||||
// 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)
|
||||
|
||||
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.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)
|
||||
gl.BindTexture(gl.TEXTURE_2D, t)
|
||||
gl.ReadPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels)
|
||||
if e := gl.GetError(); e != gl.NO_ERROR {
|
||||
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) {
|
||||
gl := c.gl
|
||||
gl.BindTexture(gl.TEXTURE_2D, t)
|
||||
gl.BindTexture(gl.TEXTURE_2D, t.Object)
|
||||
}
|
||||
|
||||
func (c *Context) DeleteTexture(t Texture) {
|
||||
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
|
||||
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
|
||||
f := gl.CreateFramebuffer()
|
||||
lastFramebuffer = Framebuffer{nil}
|
||||
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 {
|
||||
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 {
|
||||
gl := c.gl
|
||||
if lastFramebuffer != f {
|
||||
// TODO: Fix this after the GopherJS bug was fixed (#159)
|
||||
if lastFramebuffer.Object != f.Object {
|
||||
gl.Flush()
|
||||
lastFramebuffer = f
|
||||
}
|
||||
if f != nil {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, f)
|
||||
if f.Object != nil {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, f.Object)
|
||||
} else {
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, nil)
|
||||
}
|
||||
@ -140,7 +181,8 @@ func (c *Context) SetViewport(f Framebuffer, width, height int) error {
|
||||
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.ClearColor(float32(r), float32(g), float32(b), float32(a))
|
||||
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) {
|
||||
gl := c.gl
|
||||
gl.DeleteFramebuffer(f)
|
||||
gl.DeleteFramebuffer(f.Object)
|
||||
}
|
||||
|
||||
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))
|
||||
if s == nil {
|
||||
println(gl.GetError())
|
||||
return nil, errors.New("glCreateShader failed")
|
||||
return Shader{nil}, errors.New("glCreateShader failed")
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
gl := c.gl
|
||||
p := gl.CreateProgram()
|
||||
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 {
|
||||
gl.AttachShader(p, shader)
|
||||
gl.AttachShader(p, shader.Object)
|
||||
}
|
||||
gl.LinkProgram(p)
|
||||
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) {
|
||||
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) {
|
||||
gl := c.gl
|
||||
l, ok := uniformLocationCache[location]
|
||||
if !ok {
|
||||
l = gl.GetUniformLocation(p, location)
|
||||
uniformLocationCache[location] = l
|
||||
}
|
||||
gl.Uniform1i(l, v)
|
||||
l := GetUniformLocation(c, p, location)
|
||||
gl.Uniform1i(l.Object, v)
|
||||
}
|
||||
|
||||
func (c *Context) UniformFloats(p Program, location string, v []float32) {
|
||||
gl := c.gl
|
||||
l, ok := uniformLocationCache[location]
|
||||
if !ok {
|
||||
l = gl.GetUniformLocation(p, location)
|
||||
uniformLocationCache[location] = l
|
||||
}
|
||||
l := GetUniformLocation(c, p, location)
|
||||
switch len(v) {
|
||||
case 4:
|
||||
gl.Call("uniform4fv", l, v)
|
||||
gl.Call("uniform4fv", l.Object, v)
|
||||
case 16:
|
||||
gl.UniformMatrix4fv(l, false, v)
|
||||
gl.UniformMatrix4fv(l.Object, false, v)
|
||||
default:
|
||||
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
|
||||
l, ok := attribLocationCache[location]
|
||||
if !ok {
|
||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
||||
attribLocationCache[location] = l
|
||||
return AttribLocation(gl.GetAttribLocation(p.Object, location))
|
||||
}
|
||||
|
||||
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) {
|
||||
gl := c.gl
|
||||
l, ok := attribLocationCache[location]
|
||||
if !ok {
|
||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
||||
attribLocationCache[location] = l
|
||||
}
|
||||
l := GetAttribLocation(c, p, location)
|
||||
gl.EnableVertexAttribArray(int(l))
|
||||
}
|
||||
|
||||
func (c *Context) DisableVertexAttribArray(p Program, location string) {
|
||||
gl := c.gl
|
||||
l, ok := attribLocationCache[location]
|
||||
if !ok {
|
||||
l = AttribLocation(gl.GetAttribLocation(p, location))
|
||||
attribLocationCache[location] = l
|
||||
}
|
||||
l := GetAttribLocation(c, p, location)
|
||||
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
|
||||
b := gl.CreateBuffer()
|
||||
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
|
||||
const float32Size = 4
|
||||
gl.BufferSubData(int(bufferType), 0, data)
|
||||
}
|
||||
|
||||
func (c *Context) DrawElements(len int) {
|
||||
func (c *Context) DrawElements(mode Mode, len int) {
|
||||
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() {
|
||||
|
@ -14,7 +14,41 @@
|
||||
|
||||
package opengl
|
||||
|
||||
// Note: This cache is created only for one program.
|
||||
// If we use two or more programs, the key of the map should be changed.
|
||||
var uniformLocationCache = map[string]UniformLocation{}
|
||||
var attribLocationCache = map[string]AttribLocation{}
|
||||
// Since js.Object (Program) can't be keys of a map, use integers (ProgramID) instead.
|
||||
|
||||
var uniformLocationCache = map[ProgramID]map[string]UniformLocation{}
|
||||
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
|
||||
|
||||
type FilterType int
|
||||
type Filter int
|
||||
type ShaderType int
|
||||
type BufferType int
|
||||
type BufferUsageType int
|
||||
type BufferUsage int
|
||||
type Mode int
|
||||
|
||||
type Context struct {
|
||||
Nearest FilterType
|
||||
Linear FilterType
|
||||
Nearest Filter
|
||||
Linear Filter
|
||||
VertexShader ShaderType
|
||||
FragmentShader ShaderType
|
||||
ArrayBuffer BufferType
|
||||
ElementArrayBuffer BufferType
|
||||
DynamicDraw BufferUsageType
|
||||
StaticDraw BufferUsageType
|
||||
DynamicDraw BufferUsage
|
||||
StaticDraw BufferUsage
|
||||
Triangles Mode
|
||||
Lines Mode
|
||||
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
|
||||
|
||||
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 {
|
||||
keyPressed [256]bool
|
||||
mouseButtonPressed [256]bool
|
||||
cursorX int
|
||||
cursorY int
|
||||
gamepads [16]gamePad
|
||||
}
|
||||
|
||||
func (i *input) isKeyPressed(key Key) bool {
|
||||
return i.keyPressed[key]
|
||||
}
|
||||
|
||||
func (i *input) isMouseButtonPressed(button MouseButton) bool {
|
||||
return i.mouseButtonPressed[button]
|
||||
}
|
||||
|
||||
func (i *input) cursorPosition() (x, y int) {
|
||||
return i.cursorX, i.cursorY
|
||||
type gamePad struct {
|
||||
axisNum int
|
||||
axes [16]float64
|
||||
buttonNum int
|
||||
buttonPressed [256]bool
|
||||
}
|
||||
|
@ -21,25 +21,13 @@ import (
|
||||
"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{
|
||||
glfw.MouseButtonLeft: MouseButtonLeft,
|
||||
glfw.MouseButtonRight: MouseButtonRight,
|
||||
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 {
|
||||
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()
|
||||
i.cursorX = int(math.Floor(x)) / 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
|
||||
|
||||
var currentInput input
|
||||
|
||||
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()
|
||||
}
|
||||
import (
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
)
|
||||
|
||||
func (i *input) keyDown(key int) {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func DoEvents() {
|
||||
current.doEvents()
|
||||
func DoEvents() error {
|
||||
return current.doEvents()
|
||||
}
|
||||
|
||||
func Terminate() {
|
||||
@ -92,7 +92,6 @@ type ui struct {
|
||||
window *glfw.Window
|
||||
scale int
|
||||
glContext *opengl.Context
|
||||
input input
|
||||
funcs chan func()
|
||||
}
|
||||
|
||||
@ -143,17 +142,22 @@ func Start(width, height, scale int, title string) (actualScale int, err error)
|
||||
return actualScale, nil
|
||||
}
|
||||
|
||||
func (u *ui) pollEvents() {
|
||||
func (u *ui) pollEvents() error {
|
||||
glfw.PollEvents()
|
||||
u.input.update(u.window, u.scale)
|
||||
return currentInput.update(u.window, u.scale)
|
||||
}
|
||||
|
||||
func (u *ui) doEvents() {
|
||||
u.pollEvents()
|
||||
func (u *ui) doEvents() error {
|
||||
if err := u.pollEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
for current.window.GetAttribute(glfw.Focused) == 0 {
|
||||
time.Sleep(time.Second / 60)
|
||||
u.pollEvents()
|
||||
if err := u.pollEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *ui) terminate() {
|
||||
|
@ -47,11 +47,13 @@ func vsync() {
|
||||
<-ch
|
||||
}
|
||||
|
||||
func DoEvents() {
|
||||
func DoEvents() error {
|
||||
vsync()
|
||||
for !shown() {
|
||||
vsync()
|
||||
}
|
||||
currentInput.updateGamepads()
|
||||
return nil
|
||||
}
|
||||
|
||||
func Terminate() {
|
||||
@ -67,33 +69,23 @@ func SwapBuffers() {
|
||||
}
|
||||
|
||||
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")
|
||||
window := js.Global.Get("window")
|
||||
if doc.Get("body") == nil {
|
||||
ch := make(chan struct{})
|
||||
js.Global.Get("window").Call("addEventListener", "load", func() {
|
||||
window.Call("addEventListener", "load", func() {
|
||||
close(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.Set("width", 16)
|
||||
@ -131,33 +123,79 @@ func init() {
|
||||
canvas.Call("setAttribute", "tabindex", 1)
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
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()
|
||||
currentInput.mouseUp(button)
|
||||
return false
|
||||
setMouseCursorFromEvent(e)
|
||||
})
|
||||
canvas.Call("addEventListener", "contextmenu", func(e js.Object) bool {
|
||||
return false
|
||||
canvas.Call("addEventListener", "mousemove", func(e js.Object) {
|
||||
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()
|
||||
}
|
||||
|
||||
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 {
|
||||
// TODO: What if ratio is not an integer but a float?
|
||||
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()
|
||||
canvas.Set("width", width*actualScale)
|
||||
canvas.Set("height", height*actualScale)
|
||||
canvas.Get("dataset").Set("ebitenScale", scale)
|
||||
canvasStyle := canvas.Get("style")
|
||||
|
||||
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("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")
|
||||
|
||||
audio.Start()
|
||||
|
@ -14,7 +14,7 @@
|
||||
## Features
|
||||
|
||||
* 2D Graphics
|
||||
* Input (Mouse, Keyboard)
|
||||
* Input (Mouse, Keyboard, Gamepad)
|
||||
|
||||
## Documentation
|
||||
|
||||
|
22
run.go
@ -54,26 +54,22 @@ func Run(f func(*Image) error, width, height, scale int, title string) error {
|
||||
frames := 0
|
||||
t := time.Now().UnixNano()
|
||||
for {
|
||||
ui.DoEvents()
|
||||
if err := ui.DoEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
if ui.IsClosed() {
|
||||
return nil
|
||||
}
|
||||
ui.Use(func(*opengl.Context) {
|
||||
err = graphicsContext.preUpdate()
|
||||
})
|
||||
if err != nil {
|
||||
if err := graphicsContext.preUpdate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f(&Image{inner: graphicsContext.screen}); err != nil {
|
||||
if err := f(graphicsContext.screen); err != nil {
|
||||
return err
|
||||
}
|
||||
ui.Use(func(*opengl.Context) {
|
||||
err = graphicsContext.postUpdate()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
ui.SwapBuffers()
|
||||
})
|
||||
if err := graphicsContext.postUpdate(); err != nil {
|
||||
return err
|
||||
}
|
||||
ui.SwapBuffers()
|
||||
if err != nil {
|
||||
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
|
||||
}
|