Merge branch 'master' into audio

This commit is contained in:
Hajime Hoshi 2015-01-21 10:12:14 +09:00
commit 4c37efd673
62 changed files with 2922 additions and 668 deletions

View File

@ -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:

View File

@ -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
}

View File

@ -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.

View File

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,6 +1,6 @@
# License
## blocks/font.png
## arcadefont.png
```
9031 Font ReadMe

View File

@ -41,10 +41,9 @@ pre {
import (
&#34;github.com/hajimehoshi/ebiten&#34;
&#34;github.com/hajimehoshi/ebiten/ebitenutil&#34;
&#34;github.com/hajimehoshi/ebiten/example/keyboard/keyboard&#34;
&#34;log&#34;
&#34;sort&#34;
&#34;strconv&#34;
&#34;strings&#34;
)
const (
@ -52,13 +51,23 @@ const (
screenHeight = 240
)
var keyboardImage *ebiten.Image
func init() {
var err error
keyboardImage, _, err = ebitenutil.NewImageFromFile(&#34;images/keyboard/keyboard.png&#34;, ebiten.FilterNearest)
if err != nil {
log.Fatal(err)
}
}
var keyNames = map[ebiten.Key]string{
ebiten.KeyBackspace: &#34;Backspace&#34;,
ebiten.KeyComma: &#34;&#39;,&#39;&#34;,
ebiten.KeyDelete: &#34;Delete&#34;,
ebiten.KeyBackspace: &#34;BS&#34;,
ebiten.KeyComma: &#34;,&#34;,
ebiten.KeyDelete: &#34;Del&#34;,
ebiten.KeyEnter: &#34;Enter&#34;,
ebiten.KeyEscape: &#34;Esc&#34;,
ebiten.KeyPeriod: &#34;&#39;.&#39;&#34;,
ebiten.KeyPeriod: &#34;.&#34;,
ebiten.KeySpace: &#34;Space&#34;,
ebiten.KeyTab: &#34;Tab&#34;,
@ -74,7 +83,32 @@ var keyNames = map[ebiten.Key]string{
ebiten.KeyAlt: &#34;Alt&#34;,
}
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 := &amp;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 &lt;= 9; i&#43;&#43; {
if ebiten.IsKeyPressed(ebiten.Key(i) &#43; ebiten.Key0) {
@ -96,9 +130,13 @@ func update(screen *ebiten.Image) error {
pressed = append(pressed, name)
}
}
sort.Strings(pressed)
str := &#34;Pressed Keys: &#34; &#43; strings.Join(pressed, &#34;, &#34;)
ebitenutil.DebugPrint(screen, str)
op = &amp;ebiten.DrawImageOptions{
ImageParts: pressedKeysParts(pressed),
}
op.GeoM.Translate(offsetX, offsetY)
screen.DrawImage(keyboardImage, op)
return nil
}

View File

@ -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.

View File

@ -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 {

View File

@ -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)
}

View File

@ -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

View 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
}

View File

@ -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++

View File

@ -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)
}

View File

@ -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
View 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
View 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

View File

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 1008 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,6 +1,6 @@
# License
## blocks/font.png
## arcadefont.png
```
9031 Font ReadMe

122
example/internal/font.go Normal file
View 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
}

View 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)
}
}

View 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
}

View File

@ -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
View 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
View 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
View 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
View 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
View 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
}

View File

@ -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
View 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)
)

View File

@ -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)
}
}
}

View File

@ -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

View File

@ -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
View File

@ -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.

View File

@ -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
View 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)
}

View File

@ -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
}

View File

@ -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)
}

View 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
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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;
}
`,
}

View File

@ -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
View 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
}

View File

@ -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() {

View File

@ -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() {

View File

@ -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
}

View File

@ -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
}

View 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
)

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
}
}
}

View File

@ -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() {

View File

@ -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()

View File

@ -14,7 +14,7 @@
## Features
* 2D Graphics
* Input (Mouse, Keyboard)
* Input (Mouse, Keyboard, Gamepad)
## Documentation

22
run.go
View File

@ -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
View 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
View 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
}