diff --git a/CHANGELOG.md b/CHANGELOG.md
index c96f255..e594886 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- Multiple Window Management Framework
+- Adding Bezier Curve Support for Interpolation (See original [PR](https://github.com/faiface/pixel/pull/266))
+
+## [v0.12.0] 2023-10-02
- Add AnchorPos struct and functions #252
- Add Clipboard Support
- Fix SIGSEGV on text.NewAtlas if glyph absent
@@ -55,7 +59,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
Changelog for this and older versions can be found on the corresponding [GitHub
releases](https://github.com/faiface/pixel/releases).
-[Unreleased]: https://github.com/faiface/pixel/compare/v0.10.0...HEAD
+[Unreleased]: https://github.com/gopxl/pixel/compare/v0.12.0...HEAD
+[v0.12.0]: https://github.com/gopxl/pixel/compare/v0.12.0...dev
[v0.10.0]: https://github.com/faiface/pixel/compare/v0.10.0-beta...v0.10.0
[v0.10.0-beta]: https://github.com/faiface/pixel/compare/v0.10.0-alpha...v0.10.0-beta
[v0.10.0-alpha]: https://github.com/faiface/pixel/compare/v0.9.0...v0.10.0-alpha
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 22c308f..32093f8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,16 +1,16 @@
# Contributing to Pixel
-:tada: Hi! I'm really glad you're considering contributing to Pixel! :tada:
+:tada: Hi! I'm really glad you're considering contributing to Pixel2! :tada:
## Here are a few ways you can contribute
1. **Make a community example** and place it inside the `community` folder of the [examples repository][examples].
2. **Add tests**. There only few tests in Pixel at the moment. Take a look at them and make some similar.
-3. **Add a small feature or an improvement**. Feel like some small feature is missing? Just make a PR. Be ready that I might reject it, though, if I don't find it particularly appealing.
+3. **Add a small feature or an improvement**. Feel like some small feature is missing? Make a PR and let's discuss it!
4. **Join the big development** by joining the discussion on our [Discord Server](https://discord.gg/q2DK4MP), where we can discuss bigger changes and implement them after that.
## How to make a pull request
-Go gives you a nice surprise when attempting to make a PR on Github. The thing is, that when user _xyz_ forks Pixel on Github, it ends up in _github.com/xyz/pixel_, which fucks up your import paths. Here's how you deal with that: https://www.reddit.com/r/golang/comments/2jdcw1/how_do_you_deal_with_github_forking/.
+Go gives you a nice surprise when attempting to make a PR on Github. The thing is, that when user _xyz_ forks Pixel on Github, it ends up in _github.com/xyz/pixel_, which conflicts with your import paths. Here's how you deal with that: https://www.reddit.com/r/golang/comments/2jdcw1/how_do_you_deal_with_github_forking/.
-[examples]: https://github.com/faiface/pixel-examples/tree/master/community
+[examples]: https://github.com/gopxl/pixel/tree/master/examples/community
diff --git a/README.md b/README.md
index ce95459..2201ab4 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,44 @@
-
+# __**Important Notice **__
+Revived fork of the original [Pixel](https://github.com/faiface/pixel) library by [faiface](https://github.com/faiface). This fork is intended to be a community-driven effort to continue the development of the library. We were unable to get a hold of the original author, to take ownership of the original repository to carry on the legacy of the wonderful work. If you are interested in contributing, please join us in the [Discord Chat!](https://discord.gg/n2Y8uVneK6)
-# **Important Notice **
-Revived fork of the original long-dead [Pixel](https://github.com/faiface/pixel) library by [faiface](https://github.com/faiface). This fork is intended to be a community-driven effort to continue the development of the library. We were unable to get a hold of the original author, to take ownership of the original repository to carry on the legacy of the wonderful work. If you are interested in contributing, please join us in the [Discord Chat!](https://discord.gg/D6mQ8Pnm)
+
-# Pixel 2 [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/faiface/pixel?status.svg)](https://godoc.org/github.com/faiface/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/faiface/pixel)](https://goreportcard.com/report/github.com/faiface/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord Chat](https://img.shields.io/discord/699679031603494954)](https://discord.gg/q2DK4MP)
+# Pixel 2 [![Build Status](https://travis-ci.org/faiface/pixel.svg?branch=master)](https://travis-ci.org/faiface/pixel) [![GoDoc](https://godoc.org/github.com/gopxl/pixel?status.svg)](https://godoc.org/github.com/gopxl/pixel) [![Go Report Card](https://goreportcard.com/badge/github.com/gopxl/pixel)](https://goreportcard.com/report/github.com/gopxl/pixel) [![Join the chat at https://gitter.im/pixellib/Lobby](https://badges.gitter.im/pixellib/Lobby.svg)](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Discord Chat](https://img.shields.io/discord/699679031603494954)](https://discord.gg/q2DK4MP)
A hand-crafted 2D game library in Go. Take a look into the [features](#features) to see what it can
do.
```
-go get github.com/faiface/pixel
+go get github.com/gopxl/pixel
```
If you are using Modules (Go 1.11 or higher) and want a mutable copy of the source code:
```
-git clone https://github.com/faiface/pixel # clone outside of $GOPATH
-cd pixel
+git clone https://github.com/gopxl/pixel # clone outside of $GOPATH
+cd pixel2
go install ./...
```
See [requirements](#requirements) for the list of libraries necessary for compilation.
-All significant changes are documented in [CHANGELOG.md](CHANGELOG.md).
+Take a look at [CHANGELOG.md](CHANGELOG.md) for upcoming releases and history.
## Tutorial
-The [Wiki of this repo](https://github.com/faiface/pixel/wiki) contains an extensive tutorial
+The [Wiki of this repo](https://github.com/gopxl/pixel/wiki) contains an extensive tutorial
covering several topics of Pixel. Here's the content of the tutorial parts so far:
-- [Creating a Window](https://github.com/faiface/pixel/wiki/Creating-a-Window)
-- [Drawing a Sprite](https://github.com/faiface/pixel/wiki/Drawing-a-Sprite)
-- [Moving, scaling and rotating with Matrix](https://github.com/faiface/pixel/wiki/Moving,-scaling-and-rotating-with-Matrix)
-- [Pressing keys and clicking mouse](https://github.com/faiface/pixel/wiki/Pressing-keys-and-clicking-mouse)
-- [Drawing efficiently with Batch](https://github.com/faiface/pixel/wiki/Drawing-efficiently-with-Batch)
-- [Drawing shapes with IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw)
-- [Typing text on the screen](https://github.com/faiface/pixel/wiki/Typing-text-on-the-screen)
-- [Using a custom fragment shader](https://github.com/faiface/pixel/wiki/Using-a-custom-fragment-shader)
+- [Creating a Window](https://github.com/gopxl/pixel/wiki/Creating-a-Window)
+- [Drawing a Sprite](https://github.com/gopxl/pixel/wiki/Drawing-a-Sprite)
+- [Moving, scaling and rotating with Matrix](https://github.com/gopxl/pixel/wiki/Moving,-scaling-and-rotating-with-Matrix)
+- [Pressing keys and clicking mouse](https://github.com/gopxl/pixel/wiki/Pressing-keys-and-clicking-mouse)
+- [Drawing efficiently with Batch](https://github.com/gopxl/pixel/wiki/Drawing-efficiently-with-Batch)
+- [Drawing shapes with IMDraw](https://github.com/gopxl/pixel/wiki/Drawing-shapes-with-IMDraw)
+- [Typing text on the screen](https://github.com/gopxl/pixel/wiki/Typing-text-on-the-screen)
+- [Using a custom fragment shader](https://github.com/gopxl/pixel/wiki/Using-a-custom-fragment-shader)
## [Examples](https://github.com/faiface/pixel-examples)
@@ -66,6 +66,9 @@ Here are some screenshots from the examples!
| --- | --- |
| ![Raycaster](https://github.com/faiface/pixel-examples/blob/master/community/raycaster/screenshot.png) | ![Gizmo](https://github.com/Lallassu/gizmo/blob/master/preview.png) |
+## Release Schedule
+We aim to release a new version the 1st of every month. Checkout [CHANGELOG.md](CHANGELOG.md) for upcoming features.
+
## Features
Here's the list of the main features in Pixel. Although Pixel is still under heavy development,
@@ -74,15 +77,15 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea
- Fast 2D graphics
- Sprites
- Primitive shapes with immediate mode style
- [IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw) (circles, rectangles,
+ [IMDraw](https://github.com/gopxl/pixel/wiki/Drawing-shapes-with-IMDraw) (circles, rectangles,
lines, ...)
- - Optimized drawing with [Batch](https://github.com/faiface/pixel/wiki/Drawing-efficiently-with-Batch)
- - Text drawing with [text](https://godoc.org/github.com/faiface/pixel/text) package
+ - Optimized drawing with [Batch](https://github.com/gopxl/pixel/wiki/Drawing-efficiently-with-Batch)
+ - Text drawing with [text](https://godoc.org/github.com/gopxl/pixel/text) package
- Audio through a separate [Beep](https://github.com/faiface/beep) library.
- Simple and convenient API
- Drawing a sprite to a window is as simple as `sprite.Draw(window, matrix)`
- Wanna know where the center of a window is? `window.Bounds().Center()`
- - [...](https://godoc.org/github.com/faiface/pixel)
+ - [...](https://godoc.org/github.com/gopxl/pixel)
- Full documentation and tutorial
- Works on Linux, macOS and Windows
- Window creation and manipulation (resizing, fullscreen, multiple windows, ...)
@@ -94,7 +97,7 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea
multiplication and a few more features
- Pixel uses `float64` throughout the library, compatible with `"math"` package
- Geometry transformations with
- [Matrix](https://github.com/faiface/pixel/wiki/Moving,-scaling-and-rotating-with-Matrix)
+ [Matrix](https://github.com/gopxl/pixel/wiki/Moving,-scaling-and-rotating-with-Matrix)
- Moving, scaling, rotating
- Easy camera implementation
- Off-screen drawing to Canvas or any other target (Batch, IMDraw, ...)
@@ -104,7 +107,7 @@ Here's the list of the main features in Pixel. Although Pixel is still under hea
- Cutting holes into objects
- Much more...
- Pixel let's you draw stuff and do your job, it doesn't impose any particular style or paradigm
-- Platform and backend independent [core](https://godoc.org/github.com/faiface/pixel)
+- Platform and backend independent [core](https://godoc.org/github.com/gopxl/pixel)
- Core Target/Triangles/Picture pattern makes it easy to create new drawing targets that do
arbitrarily crazy stuff (e.g. graphical effects)
- Small codebase, ~5K lines of code, including the backend [glhf](https://github.com/faiface/glhf)
@@ -138,10 +141,10 @@ possible!
## Requirements
If you're using Windows and having trouble building Pixel, please check [this
-guide](https://github.com/faiface/pixel/wiki/Building-Pixel-on-Windows) on the
-[wiki](https://github.com/faiface/pixel/wiki).
+guide](https://github.com/gopxl/pixel/wiki/Building-Pixel-on-Windows) on the
+[wiki](https://github.com/gopxl/pixel/wiki).
-[PixelGL](https://godoc.org/github.com/faiface/pixel/pixelgl) backend uses OpenGL to render
+[PixelGL](https://godoc.org/github.com/gopxl/pixel/pixelgl) backend uses OpenGL to render
graphics. Because of that, OpenGL development libraries are needed for compilation. The dependencies
are same as for [GLFW](https://github.com/go-gl/glfw).
@@ -155,30 +158,35 @@ The OpenGL version used is **OpenGL 3.3**.
- See [here](http://www.glfw.org/docs/latest/compile.html#compile_deps) for full details.
**The combination of Go 1.8, macOS and latest XCode seems to be problematic** as mentioned in issue
-[#7](https://github.com/faiface/pixel/issues/7). This issue is probably not related to Pixel.
+[#7](https://github.com/gopxl/pixel/issues/7). This issue is probably not related to Pixel.
**Upgrading to Go 1.8.1 fixes the issue.**
## Contributing
-Join us in the [Discord Chat!](https://discord.gg/D6mQ8Pnm)
+Join us in the [Discord Chat!](https://discord.gg/n2Y8uVneK6)
-Pixel is in, let's say, mid-stage of development. Many of the important features are here, some are
-missing. That's why **contributions are very important and welcome!** All alone, I will be able to
-finish the library, but it'll take a lot of time. With your help, it'll take much less. I encourage
-everyone to contribute, even with just an idea. Especially welcome are **issues** and **pull
-requests**.
+Pixel is currently in a developmental phase, with many of its key features already in place, while others are still in the works.
+We genuinely appreciate and value contributions, as they can significantly expedite the development process. I invite everyone to
+contribute in any way they can, even if it's just sharing an idea. We especially value the submission of issues and pull requests.
-**However, I won't accept everything. Pixel is being developed with thought and care.** Each
-component was designed and re-designed multiple times. Code and API quality is very important here.
-API is focused on simplicity and expressiveness.
-
-When contributing, keep these goals in mind. It doesn't mean that I'll only accept perfect pull
-requests. It just means that I might not like your idea. Or that your pull requests could need some
-rewriting. That's perfectly fine, don't let it put you off. In the end, we'll just end up with a
-better result.
+That said, it's **important to remember that Pixel is being developed with a great deal of thought and attention to detail**. Each component
+has gone through numerous design iterations to ensure quality. We place a high premium on code and API quality, with an emphasis on simplicity and expressiveness.
+When contributing, please bear these goals in mind. This doesn't mean that only flawless pull requests will be accepted. Rather, it
+means that there may be times when a proposal might not align with our vision, or when a pull request might require some revisions.
+This is completely normal and should not discourage you. After all, our shared goal is to achieve the best end result possible.
Take a look at [CONTRIBUTING.md](CONTRIBUTING.md) for further information.
## License
[MIT](LICENSE)
+
+## Special Contributions
+
+- A significant shoutout to the original author [faiface](https://github.com/faiface) for starting and growing the Pixel community. We would not be here if not for him.
+- The active/inactive maintainers of the original Pixel repo:
+ - [dusk125](https://github.com/dusk125)
+ - [cebarks](https://github.com/cebarks)
+ - [Benjyclay](https://github.com/Benjyclay)
+ - [delp](https://github.com/delp)
+ - [roipoussiere](https://github.com/roipoussiere)
diff --git a/circle_test.go b/circle_test.go
index b6ce317..32563a6 100644
--- a/circle_test.go
+++ b/circle_test.go
@@ -5,7 +5,7 @@ import (
"reflect"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func TestC(t *testing.T) {
diff --git a/color_test.go b/color_test.go
index 2a6ad72..9012d9a 100644
--- a/color_test.go
+++ b/color_test.go
@@ -5,7 +5,7 @@ import (
"image/color"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func BenchmarkColorToRGBA(b *testing.B) {
diff --git a/data_test.go b/data_test.go
index f35d20b..f54c2ca 100644
--- a/data_test.go
+++ b/data_test.go
@@ -3,7 +3,7 @@ package pixel_test
import (
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func BenchmarkMakeTrianglesData(b *testing.B) {
diff --git a/drawer_test.go b/drawer_test.go
index f10cd5c..ffb4bb8 100644
--- a/drawer_test.go
+++ b/drawer_test.go
@@ -4,7 +4,7 @@ import (
"image"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func BenchmarkSpriteDrawBatch(b *testing.B) {
diff --git a/examples/LICENSE b/examples/LICENSE
new file mode 100644
index 0000000..9f5c309
--- /dev/null
+++ b/examples/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Michal Štrba
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000..7df2397
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,24 @@
+## Pixel 2 Examples
+
+This repository contains a few examples demonstrating [Pixel2](https://github.com/gopxl/pixel)'s functionality.
+
+**To run an example**, navigate to its directory, then `go run` the `main.go` file. For example:
+
+```
+$ cd platformer
+$ go run main.go
+```
+
+Here are some screenshots from the examples!
+
+| [Lights](lights) | [Platformer](platformer) |
+| --- | --- |
+| ![Lights](lights/screenshot.png) | ![Platformer](platformer/screenshot.png) |
+
+| [Smoke](smoke) | [Typewriter](typewriter) |
+| --- | --- |
+| ![Smoke](smoke/screenshot.png) | ![Typewriter](typewriter/screenshot.png) |
+
+| [Raycaster](community/raycaster) | [Starfield](community/starfield) |
+| --- | --- |
+| ![Raycaster](community/raycaster/screenshot.png) | ![Starfield](community/starfield/screenshot.png) |
diff --git a/examples/community/amidakuji/LICENSE b/examples/community/amidakuji/LICENSE
new file mode 100644
index 0000000..ab60297
--- /dev/null
+++ b/examples/community/amidakuji/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/community/amidakuji/Makefile b/examples/community/amidakuji/Makefile
new file mode 100644
index 0000000..e9fa78e
--- /dev/null
+++ b/examples/community/amidakuji/Makefile
@@ -0,0 +1,45 @@
+OUT := amidakuji
+ASSET_TARGET := glossary/asset.go
+ASSET_SOURCE_DIR := assets
+VERSION := $(shell git describe --always --long)
+PKG_LIST := $(shell go list ./... | grep -v /vendor/)
+GO_FILES := $(shell find . -name '*.go' | grep -v /vendor/)
+
+all: build build_windows
+
+${ASSET_TARGET}:
+ go-bindata -o "${ASSET_TARGET}" -pkg "glossary" -prefix "${ASSET_SOURCE_DIR}" ${ASSET_SOURCE_DIR}/emoji ${ASSET_SOURCE_DIR}/karaoke ${ASSET_SOURCE_DIR}/NanumBarunGothic.ttf
+
+build: ${ASSET_TARGET}
+ go build -i -v -o ${OUT} -ldflags "-w -s -X main.version=${VERSION}"
+
+build_windows: ${ASSET_TARGET}
+ go build -i -v -o ${OUT}.exe -ldflags "-w -s -X main.version=${VERSION} -H windowsgui"
+
+run: build
+ ./${OUT}
+
+test:
+ @go test -short ${PKG_LIST}
+
+vet:
+ @go vet -copylocks=false ${PKG_LIST}
+
+vet_annoying:
+ @go vet ${PKG_LIST}
+
+lint:
+ @for file in ${GO_FILES} ; do \
+ golint $$file ; \
+ done
+
+#static: vet lint
+# go build -i -v -o ${OUT}-${VERSION} -ldflags "-extldflags \"-static\" -w -s -X main.version=${VERSION}"
+
+#static_windows: vet lint
+# go build -i -v -o ${OUT}-${VERSION}.exe -ldflags "-extldflags \"-static\" -w -s -X main.version=${VERSION} -H windowsgui"
+
+clean:
+ -@rm ${ASSET_TARGET} ${OUT} ${OUT}.exe #${OUT}-*
+
+.PHONY: build build_windows run vet vet_annoying lint clean
diff --git a/examples/community/amidakuji/README.md b/examples/community/amidakuji/README.md
new file mode 100644
index 0000000..ed40bdf
--- /dev/null
+++ b/examples/community/amidakuji/README.md
@@ -0,0 +1,93 @@
+# [AMIDA KUJI](https://github.com/NaniteFactory/amidakuji/tree/1bf57c3639e4e5628d96d9171ed9e679b658fadb)
+
+> Ghost Leg (Chinese: 畫鬼腳), known in Japan as Amidakuji (阿弥陀籤, "Amida lottery", so named because the paper was folded into a fan shape resembling Amida's halo) or in Korea as Sadaritagi (사다리타기, literally "ladder climbing"), is a method of lottery designed to create random pairings between two sets of any number of things, as long as the number of elements in each set is the same. This is often used to distribute things among people, where the number of things distributed is the same as the number of people. For instance, chores or prizes could be assigned fairly and randomly this way.
+
+(Explanation from [Wikipedia](https://en.wikipedia.org/wiki/Ghost_Leg))
+
+- - -
+
+## Examples
+
+| [GIF1](examples/user_conf_sample6.json) | [GIF2](examples/user_conf_sample3.json) |
+| --- | --- |
+| ![1](examples/1.gif) | ![2](examples/2.gif) |
+
+| GIF3 |
+| --- |
+| ![3](examples/3.gif) |
+
+- - -
+
+## Control
+
+| | Action | Input |
+| --- | --- | --- |
+|🔀 | Shuffle | 1 |
+| ▶️ | Find path | 2 |
+| ⏩ | Find path (faster) | 3 |
+| ⏸ | Pause | Space |
+| ⬆️➡️⬇️⬅️ | Move camera around | Arrow keys |
+| ↩️ | Rotate camera | Enter |
+| 🔭 | Zoom in and out | Scroll |
+| 🎇 | Firework | Left click |
+| 🔬 | Inspection | Right click |
+| 🔁 | Toggle full screen mode | Tab |
+
+- - -
+
+## Build
+
+#### Windows
+
+```
+$ go get -v github.com/faiface/pixel-examples/community/amidakuji/...
+$ go get -v -u github.com/go-bindata/go-bindata/...
+```
+
+```
+$ cd $GOPATH/src/github.com/faiface/pixel-examples/community/amidakuji/
+$ make
+```
+
+#### Ubuntu
+
+```
+$ sudo apt-get clean
+$ sudo rm -r /var/lib/apt/lists/*
+$ sudo apt update
+```
+
+```
+$ sudo apt install libglib2.0-dev libpango1.0-dev libasound2-dev libgdk-pixbuf2.0-dev libgl1-mesa-dev xorg-dev libgtk2.0-dev
+```
+
+```
+$ go get -v github.com/faiface/pixel-examples/community/amidakuji/...
+$ go get -v -u github.com/go-bindata/go-bindata/...
+```
+
+```
+$ cd $GOPATH/src/github.com/faiface/pixel-examples/community/amidakuji/
+$ make
+```
+
+- - -
+
+## External sources
+
+#### Library
+- [Pixel](https://github.com/faiface/pixel/tree/7cff3ce3aed80129b7b1dd57e63439426e11b6ee)
+- [Beep](https://github.com/faiface/beep/tree/63cc6fbbac46dba1a03e55f0ebc965d6c82ca8e1)
+- [GLFW 3.2](https://github.com/go-gl/glfw/tree/513e4f2bf85c31fba0fc4907abd7895242ccbe50/v3.2/glfw)
+- [dialog](https://github.com/sqweek/dialog/tree/2f9d9e5dd848a3bad4bdd0210c73bb90c13a3791)
+
+#### Music
+- [Night Tempo - Pure Present](https://nighttempo.bandcamp.com/album/pure-present) - [08 Kikuchi Momoko - Night Cruising (Night Tempo 100% Pure Remastered)](https://nighttempo.bandcamp.com/track/kikuchi-momoko-night-cruising-night-tempo-100-pure-remastered-2)
+- [Night Tempo - Pure Present](https://nighttempo.bandcamp.com/album/pure-present) - [19 Takeuchi Mariya - Plastic Love (Night Tempo 100% Pure Remastered)](https://nighttempo.bandcamp.com/track/takeuchi-mariya-plastic-love-night-tempo-100-pure-remastered-3)
+
+#### Image
+- [Gophers...](https://github.com/egonelbre/gophers/tree/dfb1bc3e6092179bd80d2e4156a8d32dba484cc9)
+
+#### Font
+- [나눔바른고딕 (NanumBarunGothic.ttf)](https://hangeul.naver.com/2017/nanum)
+
diff --git a/examples/community/amidakuji/assets/NanumBarunGothic.ttf b/examples/community/amidakuji/assets/NanumBarunGothic.ttf
new file mode 100644
index 0000000..c314868
Binary files /dev/null and b/examples/community/amidakuji/assets/NanumBarunGothic.ttf differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-angry.png b/examples/community/amidakuji/assets/emoji/gopher-angry.png
new file mode 100644
index 0000000..6d208ea
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-angry.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-at-peace.png b/examples/community/amidakuji/assets/emoji/gopher-at-peace.png
new file mode 100644
index 0000000..bc4de32
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-at-peace.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-blushing.png b/examples/community/amidakuji/assets/emoji/gopher-blushing.png
new file mode 100644
index 0000000..4d4023a
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-blushing.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-cold-sweat.png b/examples/community/amidakuji/assets/emoji/gopher-cold-sweat.png
new file mode 100644
index 0000000..1411197
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-cold-sweat.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-confused.png b/examples/community/amidakuji/assets/emoji/gopher-confused.png
new file mode 100644
index 0000000..d60f060
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-confused.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-crying-river.png b/examples/community/amidakuji/assets/emoji/gopher-crying-river.png
new file mode 100644
index 0000000..e689296
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-crying-river.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-crying.png b/examples/community/amidakuji/assets/emoji/gopher-crying.png
new file mode 100644
index 0000000..2fc197c
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-crying.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-dead.png b/examples/community/amidakuji/assets/emoji/gopher-dead.png
new file mode 100644
index 0000000..98e1022
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-dead.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-expressionless.png b/examples/community/amidakuji/assets/emoji/gopher-expressionless.png
new file mode 100644
index 0000000..e4b14ce
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-expressionless.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-eyeroll.gif b/examples/community/amidakuji/assets/emoji/gopher-eyeroll.gif
new file mode 100644
index 0000000..f05c48a
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-eyeroll.gif differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-facepalm.png b/examples/community/amidakuji/assets/emoji/gopher-facepalm.png
new file mode 100644
index 0000000..5ae88cc
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-facepalm.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-happy.png b/examples/community/amidakuji/assets/emoji/gopher-happy.png
new file mode 100644
index 0000000..c862cd7
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-happy.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-heart-eyes.png b/examples/community/amidakuji/assets/emoji/gopher-heart-eyes.png
new file mode 100644
index 0000000..32409c0
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-heart-eyes.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-heart.png b/examples/community/amidakuji/assets/emoji/gopher-heart.png
new file mode 100644
index 0000000..18c1aec
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-heart.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-idea.png b/examples/community/amidakuji/assets/emoji/gopher-idea.png
new file mode 100644
index 0000000..a84a5e8
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-idea.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-insomnia.png b/examples/community/amidakuji/assets/emoji/gopher-insomnia.png
new file mode 100644
index 0000000..0e8fee3
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-insomnia.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-mind-blown.png b/examples/community/amidakuji/assets/emoji/gopher-mind-blown.png
new file mode 100644
index 0000000..54c314f
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-mind-blown.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-neutral.png b/examples/community/amidakuji/assets/emoji/gopher-neutral.png
new file mode 100644
index 0000000..9b88592
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-neutral.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-no-peeking.png b/examples/community/amidakuji/assets/emoji/gopher-no-peeking.png
new file mode 100644
index 0000000..035011c
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-no-peeking.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-not-sure-if.png b/examples/community/amidakuji/assets/emoji/gopher-not-sure-if.png
new file mode 100644
index 0000000..afe91cb
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-not-sure-if.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-pirate.png b/examples/community/amidakuji/assets/emoji/gopher-pirate.png
new file mode 100644
index 0000000..36cfede
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-pirate.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-sad-sweat.png b/examples/community/amidakuji/assets/emoji/gopher-sad-sweat.png
new file mode 100644
index 0000000..afd2a62
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-sad-sweat.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-sad.png b/examples/community/amidakuji/assets/emoji/gopher-sad.png
new file mode 100644
index 0000000..14545eb
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-sad.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-sick.png b/examples/community/amidakuji/assets/emoji/gopher-sick.png
new file mode 100644
index 0000000..b1218a6
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-sick.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-sleeping.png b/examples/community/amidakuji/assets/emoji/gopher-sleeping.png
new file mode 100644
index 0000000..934fc9e
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-sleeping.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-sleepy.png b/examples/community/amidakuji/assets/emoji/gopher-sleepy.png
new file mode 100644
index 0000000..784eeae
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-sleepy.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-smiling-blushing.png b/examples/community/amidakuji/assets/emoji/gopher-smiling-blushing.png
new file mode 100644
index 0000000..9d9b07a
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-smiling-blushing.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-smiling-sweat.png b/examples/community/amidakuji/assets/emoji/gopher-smiling-sweat.png
new file mode 100644
index 0000000..305b6af
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-smiling-sweat.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-smiling.png b/examples/community/amidakuji/assets/emoji/gopher-smiling.png
new file mode 100644
index 0000000..07b8d70
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-smiling.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-thinking.png b/examples/community/amidakuji/assets/emoji/gopher-thinking.png
new file mode 100644
index 0000000..482d02b
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-thinking.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-tired.png b/examples/community/amidakuji/assets/emoji/gopher-tired.png
new file mode 100644
index 0000000..bf886b5
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-tired.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-trying-hard.png b/examples/community/amidakuji/assets/emoji/gopher-trying-hard.png
new file mode 100644
index 0000000..264b2e1
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-trying-hard.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-victorious.png b/examples/community/amidakuji/assets/emoji/gopher-victorious.png
new file mode 100644
index 0000000..e9ac176
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-victorious.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-wink.png b/examples/community/amidakuji/assets/emoji/gopher-wink.png
new file mode 100644
index 0000000..887d510
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-wink.png differ
diff --git a/examples/community/amidakuji/assets/emoji/gopher-wondering.png b/examples/community/amidakuji/assets/emoji/gopher-wondering.png
new file mode 100644
index 0000000..c6b47d5
Binary files /dev/null and b/examples/community/amidakuji/assets/emoji/gopher-wondering.png differ
diff --git a/examples/community/amidakuji/assets/karaoke/kikuchimomoko-nightcruising.ogg b/examples/community/amidakuji/assets/karaoke/kikuchimomoko-nightcruising.ogg
new file mode 100644
index 0000000..b803a4f
Binary files /dev/null and b/examples/community/amidakuji/assets/karaoke/kikuchimomoko-nightcruising.ogg differ
diff --git a/examples/community/amidakuji/assets/karaoke/takeuchimariya-plasticlove.ogg b/examples/community/amidakuji/assets/karaoke/takeuchimariya-plasticlove.ogg
new file mode 100644
index 0000000..8cec80b
Binary files /dev/null and b/examples/community/amidakuji/assets/karaoke/takeuchimariya-plasticlove.ogg differ
diff --git a/examples/community/amidakuji/examples/1.gif b/examples/community/amidakuji/examples/1.gif
new file mode 100644
index 0000000..69fc08f
Binary files /dev/null and b/examples/community/amidakuji/examples/1.gif differ
diff --git a/examples/community/amidakuji/examples/2.gif b/examples/community/amidakuji/examples/2.gif
new file mode 100644
index 0000000..b4b8b7e
Binary files /dev/null and b/examples/community/amidakuji/examples/2.gif differ
diff --git a/examples/community/amidakuji/examples/3.gif b/examples/community/amidakuji/examples/3.gif
new file mode 100644
index 0000000..93cfa5e
Binary files /dev/null and b/examples/community/amidakuji/examples/3.gif differ
diff --git a/examples/community/amidakuji/examples/user_conf_sample1.json b/examples/community/amidakuji/examples/user_conf_sample1.json
new file mode 100644
index 0000000..b1b8b3d
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample1.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 600,
+ "window_height": 900,
+
+ "max_player": 4,
+ "max_level": 80,
+
+ "width": 1500,
+ "height": 1000,
+ "zoom": -3,
+ "rotate_degree": -90,
+
+ "margin_top": 50,
+ "margin_right": 100,
+ "margin_bottom": 50,
+ "margin_left": 150,
+
+ "font_size": 20,
+ "picks": ["Bulbasaur","Ivysaur","Venusaur","Charmander","Charmeleon","Charizard","Squirtle","Wartortle","Blastoise","Caterpie","Metapod","Butterfree","Weedle","Kakuna","Beedrill","Pidgey","Pidgeotto","Pidgeot","Rattata","Rattata"],
+ "prizes": ["None","Master Ball","Ultra Ball","Great Ball","Poke Ball","Safari Ball","Net Ball","Dive Ball","Nest Ball","Repeat Ball","Timer Ball","Luxury Ball","Premier Ball","Dusk Ball","Heal Ball","Quick Ball","Cherish Ball"]
+}
diff --git a/examples/community/amidakuji/examples/user_conf_sample2.json b/examples/community/amidakuji/examples/user_conf_sample2.json
new file mode 100644
index 0000000..29a0158
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample2.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 1200,
+ "window_height": 800,
+
+ "max_player": 5,
+ "max_level": 100,
+
+ "width": 1500,
+ "height": 1000,
+ "zoom": -2,
+ "rotate_degree": -360,
+
+ "margin_top": 50,
+ "margin_right": 100,
+ "margin_bottom": 50,
+ "margin_left": 150,
+
+ "font_size": 20,
+ "picks": ["Psyduck","Golduck","Mankey","Primeape","Growlithe"],
+ "prizes": ["None","Potion","Antidote","Burn Heal","Ice Heal","Awakening","Paralyze Heal","Full Restore","Max Potion","Hyper Potion","Super Potion","Full Heal","Revive","Max Revive","Fresh Water","Soda Pop","Lemonade","Moomoo Milk"]
+}
diff --git a/examples/community/amidakuji/examples/user_conf_sample3.json b/examples/community/amidakuji/examples/user_conf_sample3.json
new file mode 100644
index 0000000..bb0f23e
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample3.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 1800,
+ "window_height": 600,
+
+ "max_player": 50,
+ "max_level": 2000,
+
+ "width": 3000,
+ "height": 1000,
+ "zoom": -3,
+ "rotate_degree": -360,
+
+ "margin_top": 50,
+ "margin_right": 50,
+ "margin_bottom": 50,
+ "margin_left": 100,
+
+ "font_size": 20,
+ "picks": [],
+ "prizes": []
+}
diff --git a/examples/community/amidakuji/examples/user_conf_sample4.json b/examples/community/amidakuji/examples/user_conf_sample4.json
new file mode 100644
index 0000000..d448b09
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample4.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 1800,
+ "window_height": 600,
+
+ "max_player": 8,
+ "max_level": 300,
+
+ "width": 3000,
+ "height": 1000,
+ "zoom": -3,
+ "rotate_degree": -720,
+
+ "margin_top": 50,
+ "margin_right": 50,
+ "margin_bottom": 50,
+ "margin_left": 100,
+
+ "font_size": 20,
+ "picks": ["피카츄", "꼬부기", "깨비참", "성원숭", "파이리", "날쌩마", "뚜벅쵸", "케이시", "망나뇽", "잠만보", "파라스", "덩쿠리", "뿔카노", "버터플", "꼬마돌", "ㅁ", "ㄴ", "ㅇ", "ㄹ", "a", "s", "d", "f"],
+ "prizes": ["꽝ㅠ", "당첨", "꽝ㅠ", "꽝ㅠ", "당첨", "꽝ㅠ", "꽝ㅠ", "당첨", "꽝ㅠ", "꽝ㅠ", "당첨", "꽝ㅠ", "꽝ㅠ", "당첨", "꽝ㅠ"]
+}
diff --git a/examples/community/amidakuji/examples/user_conf_sample5.json b/examples/community/amidakuji/examples/user_conf_sample5.json
new file mode 100644
index 0000000..dd9a0a2
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample5.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 1800,
+ "window_height": 600,
+
+ "max_player": 8,
+ "max_level": 300,
+
+ "width": 3000,
+ "height": 1000,
+ "zoom": -3,
+ "rotate_degree": -360,
+
+ "margin_top": 50,
+ "margin_right": 120,
+ "margin_bottom": 50,
+ "margin_left": 150,
+
+ "font_size": 18,
+ "picks": ["Tauros", "Magikarp", "Gyarados", "Lapras", "Ditto", "Eevee", "Vaporeon", "Jolteon", "Flareon", "Porygon", "Omanyte"],
+ "prizes": ["None","Master Ball","Ultra Ball","Great Ball","Poke Ball","Safari Ball","Net Ball","Dive Ball","Nest Ball","Repeat Ball","Timer Ball","Luxury Ball","Premier Ball","Dusk Ball","Heal Ball","Quick Ball","Cherish Ball"]
+}
diff --git a/examples/community/amidakuji/examples/user_conf_sample6.json b/examples/community/amidakuji/examples/user_conf_sample6.json
new file mode 100644
index 0000000..bbb576c
--- /dev/null
+++ b/examples/community/amidakuji/examples/user_conf_sample6.json
@@ -0,0 +1,21 @@
+{
+ "window_width": 800.0,
+ "window_height": 800.0,
+
+ "max_player": 3.0,
+ "max_level": 20.0,
+
+ "width": 500.0,
+ "height": 500.0,
+ "zoom": 1.0,
+ "rotate_degree": 270.0,
+
+ "margin_top": 50.0,
+ "margin_right": 100.0,
+ "margin_bottom": 50.0,
+ "margin_left": 120.0,
+
+ "font_size": 28.0,
+ "picks": ["Tauros", "Magikarp", "Eevee", "Lapras", "Ditto", "Gyarados", "Vaporeon", "Jolteon", "Flareon", "Porygon", "Omanyte"],
+ "prizes": ["None","Master Ball","Ultra Ball","Great Ball","Poke Ball","Safari Ball","Net Ball","Dive Ball","Nest Ball","Repeat Ball","Timer Ball","Luxury Ball","Premier Ball","Dusk Ball","Heal Ball","Quick Ball","Cherish Ball"]
+}
diff --git a/examples/community/amidakuji/game.go b/examples/community/amidakuji/game.go
new file mode 100644
index 0000000..af37344
--- /dev/null
+++ b/examples/community/amidakuji/game.go
@@ -0,0 +1,774 @@
+package main
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math"
+ "math/rand"
+ "os"
+ "reflect"
+ "strings"
+ "sync"
+ "time"
+ "unsafe"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+ "github.com/faiface/pixel-examples/community/amidakuji/glossary/jukebox"
+ glfw "github.com/go-gl/glfw/v3.2/glfw"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "github.com/faiface/pixel/text"
+ "github.com/sqweek/dialog"
+ "golang.org/x/image/colornames"
+)
+
+// Actor updates and draws itself. It acts as a game object.
+type Actor interface {
+ Drawer
+ Updater
+}
+
+// Drawer draws itself.
+type Drawer interface {
+ Draw()
+}
+
+// Updater updates itself.
+type Updater interface {
+ Update()
+}
+
+// -------------------------------------------------------------------------
+// Core game
+
+// game is a path finder.
+// Also it manages and draws everything about...
+type game struct {
+ // something system, somthing runtime
+ window *pixelgl.Window // lazy init
+ bg pixel.RGBA
+ camera *gg.Camera // lazy init
+ fpsw *gg.FPSWatch
+ dtw gg.DtWatch
+ vsync <-chan time.Time // lazy init
+ // game state
+ isRefreshedLadder bool
+ isRefreshedNametags bool
+ isScalpelMode bool
+ // drawings
+ mutex sync.Mutex // It is unsafe to access any refd; ptrd object without a critical section.
+ nPlayers int
+ ladder *Ladder
+ scalpel *Scalpel
+ paths []Path
+ emojis []pixel.Sprite
+ nametagPicks Nametags
+ nametagPrizes Nametags
+ atlas *text.Atlas
+ galaxy *gg.Galaxy
+ explosions *gg.Explosions
+ // other user settings
+ fontSize float64
+ winWidth float64 // The screen width, not the game width.
+ winHeight float64
+ initialZoomLevel float64
+ initialRotateDegree float64
+}
+
+type gameConfig struct {
+ nParticipants int
+ nLevel int
+ winWidth float64
+ winHeight float64
+ width float64
+ height float64
+ initialZoomLevel float64
+ initialRotateDegree float64
+ paddingTop float64
+ paddingRight float64
+ paddingBottom float64
+ paddingLeft float64
+ fontSize float64
+ nametagPicks []string
+ nametagPrizes []string
+}
+
+// init game
+func newGame(cfg gameConfig) *game {
+
+ newEmojis := func(nParticipants int) (emojis []pixel.Sprite) {
+ emojis = make([]pixel.Sprite, nParticipants)
+ const dir = "emoji"
+ randomNames, err := gg.AssetDir(dir) // The order is random because they're from a map.
+ if err != nil {
+ return nil
+ }
+ nRandomNames := len(randomNames)
+ for participant := 0; participant < nParticipants; participant++ {
+ emojis[participant] = *gg.NewSprite(dir + "/" + randomNames[participant%nRandomNames]) // val, not ptr
+ }
+ return emojis
+ }
+
+ g := game{
+ bg: gg.RandomNiceColor(),
+ fpsw: gg.NewFPSWatchSimple(pixel.V(cfg.winWidth, cfg.winHeight), gg.Top, gg.Right),
+ isRefreshedLadder: false,
+ isRefreshedNametags: false,
+ isScalpelMode: false,
+ nPlayers: cfg.nParticipants,
+ ladder: NewLadder(
+ cfg.nParticipants, cfg.nLevel,
+ cfg.width, cfg.height,
+ cfg.paddingTop, cfg.paddingRight,
+ cfg.paddingBottom, cfg.paddingLeft,
+ ),
+ scalpel: &Scalpel{},
+ paths: make([]Path, cfg.nParticipants),
+ emojis: newEmojis(cfg.nParticipants),
+ nametagPicks: make([]Nametag, cfg.nParticipants), // val, not ptr
+ nametagPrizes: make([]Nametag, cfg.nParticipants), // val, not ptr
+ atlas: gg.NewAtlas(
+ "", cfg.fontSize,
+ []rune(strings.Join(cfg.nametagPicks, "")+strings.Join(cfg.nametagPrizes, "")),
+ ), // A prepared set of images of characters or symbols to be drawn.
+ galaxy: gg.NewGalaxy(cfg.width, cfg.height, 400),
+ explosions: gg.NewExplosions(cfg.width, cfg.width, nil, 5),
+ initialZoomLevel: cfg.initialZoomLevel,
+ initialRotateDegree: cfg.initialRotateDegree,
+ winWidth: cfg.winWidth,
+ winHeight: cfg.winHeight,
+ }
+
+ // init paths
+ g.ResetPaths()
+
+ // copy nametags
+ copyNametagPicks := func(dstNametags []Nametag, srcNames []string) {
+ positions := g.ladder.PtsAtLevelOfPicks()
+ for i := 0; i < cfg.nParticipants; i++ {
+ posAdjust := positions[i]
+ posAdjust.Y += 5
+ posAdjust.X -= 60
+ dstNametags[i] = *NewNametagSimple(
+ g.atlas, "", posAdjust,
+ gg.Middle, gg.Right,
+ ) // val, not ptr
+ if i < len(srcNames) {
+ dstNametags[i].desc = srcNames[i]
+ }
+ }
+ }
+ copyNametagPrizes := func(dstNametags []Nametag, srcNames []string) {
+ positions := g.ladder.PtsAtLevelOfPrizes()
+ for i := 0; i < cfg.nParticipants; i++ {
+ posAdjust := positions[i]
+ posAdjust.Y += 5
+ posAdjust.X += 15
+ dstNametags[i] = *NewNametagSimple(
+ g.atlas, "", posAdjust,
+ gg.Middle, gg.Left,
+ ) // val, not ptr
+ if i < len(srcNames) {
+ dstNametags[i].desc = srcNames[i]
+ }
+ }
+ }
+ copyNametagPicks(g.nametagPicks, cfg.nametagPicks)
+ copyNametagPrizes(g.nametagPrizes, cfg.nametagPrizes)
+ // log.Println(g.nametagPicks[1].desc) //
+
+ return &g
+}
+
+func (g *game) Draw() {
+ g.mutex.Lock()
+ defer g.mutex.Unlock()
+
+ // This was originally an argument of this function.
+ var t pixel.BasicTarget
+ t = g.window
+
+ // ---------------------------------------------------
+ // 1. canvas a game world
+ t.SetMatrix(g.camera.Transform())
+
+ // Draw()s in an order.
+ g.galaxy.Draw(t)
+ g.ladder.Draw(t)
+ for iPath := range g.paths {
+ g.paths[iPath].Draw(t)
+ }
+ g.nametagPicks.Draw(t)
+ g.nametagPrizes.Draw(t)
+ if g.explosions.IsExploding() {
+ g.explosions.Draw(t)
+ }
+ for iEmoji := range g.emojis {
+ g.emojis[iEmoji].Draw(
+ t, pixel.IM.
+ Scaled(pixel.ZV, 2).
+ Rotated(pixel.ZV, -g.camera.Angle()).
+ Moved(g.paths[iEmoji].PosTip()),
+ )
+ }
+ if g.isScalpelMode {
+ g.scalpel.Draw(t)
+ }
+ if g.isScalpelMode {
+ UpdateDrawUnprojekt(g.window, g.ladder.bound, colornames.Blue, g.camera.Transform())
+ UpdateDrawUnprojekt2(g.window, g.ladder.bound, colornames.Red, *g.camera)
+ }
+
+ // ---------------------------------------------------
+ // 2. canvas a screen
+ t.SetMatrix(pixel.IM)
+
+ // Draw()s in an order.
+ g.fpsw.Draw(g.window)
+ if g.isScalpelMode {
+ UpdateDrawProjekt(g.window, g.ladder.bound, colornames.Black, g.camera.Transform())
+ }
+}
+
+func (g *game) Update(dt float64) {
+ g.mutex.Lock()
+ defer g.mutex.Unlock()
+
+ // The camera would and should update every frame.
+ g.camera.Update(dt)
+
+ // Update only if there is a need.
+ // isRefreshedLadder be set to false if there was an update to the ladder or its scalpel.
+ if !g.isRefreshedLadder {
+ g.ladder.Update()
+ g.scalpel.Update(*g.ladder)
+ g.isRefreshedLadder = true
+ }
+
+ // Only update when there is a need.
+ if !g.isRefreshedNametags {
+ g.nametagPicks.Update()
+ g.nametagPrizes.Update()
+ g.isRefreshedNametags = true
+ }
+
+ // Only currently animating paths need to update each frame.
+ for iPath := range g.paths {
+ if g.paths[iPath].IsAnimating() {
+ g.paths[iPath].Update(g.ladder.colors[iPath])
+ }
+ }
+
+ // As long as it doesn't hurt the framerate.
+ if g.fpsw.GetFPS() >= 10 {
+ g.galaxy.Update(dt)
+ }
+
+ // Only update when there is at least one (animating) explosion.
+ if g.explosions.IsExploding() {
+ g.explosions.Update(dt)
+ }
+}
+
+func (g *game) OnResize(width, height float64) {
+ g.camera.SetScreenBound(pixel.R(0, 0, width, height))
+ g.fpsw.SetPos(pixel.V(width, height), gg.Top, gg.Right)
+ // g.explosions.SetBound(width, height)
+}
+
+// -------------------------------------------------------------------------
+// Single path
+
+// ClearPath of a participant.
+func (g *game) ClearPath(participant int) {
+ g.paths[participant] = *NewPathEmpty()
+}
+
+// ResetPath of a participant.
+func (g *game) ResetPath(participant int) {
+ // GeneratePath contains a path-finding algorithm. This function is used as a path finder.
+ GeneratePath := func(g *game, participant int) Path {
+ const icol int = 0 // level
+ irow := participant // participant
+ grid := g.ladder.grid
+ route := []pixel.Vec{}
+ prize := -1
+ for level := icol; level < g.ladder.nLevel; level++ {
+ route = append(route, grid[irow][level])
+ prize = irow
+ if irow+1 < g.ladder.nParticipants {
+ if g.ladder.bridges[irow][level] {
+ irow++ // cross the bridge ... to the left (south)
+ route = append(route, grid[irow][level])
+ prize = irow
+ continue
+ }
+ }
+ if irow-1 >= 0 {
+ if g.ladder.bridges[irow-1][level] {
+ irow-- // cross the bridge ... to the right (north)
+ route = append(route, grid[irow][level])
+ prize = irow
+ continue
+ }
+ }
+ }
+ // log.Println(participant, prize, irow) //
+
+ // A path found here is called a route or roads.
+ return *NewPath(route, &prize) // val, not ptr
+ }
+
+ g.paths[participant] = GeneratePath(g, participant) // path-find
+ g.paths[participant].OnPassedEachPoint = func(pt pixel.Vec, dir pixel.Vec) {
+ g.explosions.ExplodeAt(pt, dir.Scaled(2))
+ }
+}
+
+func (g *game) AnimatePath(participant int) {
+ g.paths[participant].Animate()
+}
+
+func (g *game) AnimatePathInTime(participant int, sec float64) {
+ g.paths[participant].AnimateInTime(sec)
+}
+
+// -------------------------------------------------------------------------
+// All paths
+
+func (g *game) ResetPaths() {
+ g.mutex.Lock()
+ defer g.mutex.Unlock()
+
+ for participant := 0; participant < g.nPlayers; participant++ {
+ g.ResetPath(participant)
+ }
+}
+
+// AnimatePaths in order.
+func (g *game) AnimatePaths(thunkAnimatePath func(participant int)) {
+ g.mutex.Lock()
+ defer g.mutex.Unlock()
+
+ for participant := 0; participant < g.nPlayers; participant++ {
+ participantCurr := participant
+ participantNext := participant + 1
+ prize := g.paths[participantCurr].GetPrize()
+ title := "Result"
+ caption := fmt.Sprint(
+ " 👆 Pick\t(No. ", participantCurr+1, ")\t", g.nametagPicks[participantCurr], "\t",
+ "\r\n", "\r\n",
+ " 🎁 Prize\t(No. ", prize+1, ")\t", g.nametagPrizes[prize], "\t",
+ "\r\n",
+ )
+ g.paths[participant].OnFinishedAnimation = func() {
+ if g.window.Monitor() == nil {
+ dialog.Message("%s", caption).Title(title).Info()
+ }
+ if participantNext < g.nPlayers {
+ thunkAnimatePath(participantNext)
+ }
+ g.paths[participantCurr].OnFinishedAnimation = nil
+ }
+ }
+ thunkAnimatePath(0)
+}
+
+// -------------------------------------------------------------------------
+// Game controls
+
+func (g *game) Reset() {
+ g.ladder.Reset()
+ g.ResetPaths()
+ g.isRefreshedLadder = false
+}
+
+// Shuffle in an approximate time.
+func (g *game) Shuffle(times, inMillisecond int) {
+ speed := g.galaxy.Speed()
+ g.galaxy.SetSpeed(speed * 10)
+ {
+ i := 0
+ for range time.Tick(
+ (time.Millisecond * time.Duration(inMillisecond)) / time.Duration(times),
+ ) {
+ g.bg = gg.RandomNiceColor()
+ g.Reset()
+ i++
+ if i >= times {
+ break
+ }
+ }
+ }
+ g.galaxy.SetSpeed(speed)
+}
+
+// Pause the game.
+func (g *game) Pause() {
+ for i := range g.paths {
+ if g.paths[i].IsAnimating() {
+ g.paths[i].Pause()
+ }
+ }
+}
+
+// Resume after pause.
+func (g *game) Resume() {
+ g.dtw.Dt()
+ for i := range g.paths {
+ if g.paths[i].IsAnimating() {
+ g.paths[i].Resume()
+ }
+ }
+}
+
+func (g *game) SetFullScreenMode(on bool) {
+ if on {
+ monitor := pixelgl.PrimaryMonitor()
+ width, height := monitor.Size()
+ // log.Println(monitor.VideoModes()) //
+ g.window.SetMonitor(monitor)
+ go func(width, height float64) {
+ g.OnResize(width, height)
+ }(width, height)
+ } else if !on { // off
+ g.window.SetMonitor(nil)
+ } else {
+ panic(errors.New("it may be thread"))
+ }
+}
+
+// -------------------------------------------------------------------------
+// Read only methods
+
+// WindowDeep is a hacky way to access a window in deep.
+// It returns (window *glfw.Window) which is an unexported member inside a (*pixelgl.Window).
+// Read only argument game ignores the pass lock by value warning.
+func (g game) WindowDeep() (baseWindow *glfw.Window) {
+ return *(**glfw.Window)(unsafe.Pointer(reflect.Indirect(reflect.ValueOf(g.window)).FieldByName("window").UnsafeAddr()))
+}
+
+// Read only argument game ignores the pass lock by value warning.
+func (g game) BridgesCount() (sum int) {
+ for _, row := range g.ladder.bridges {
+ for _, col := range row {
+ if col {
+ sum++
+ }
+ }
+ }
+ return sum
+}
+
+// -------------------------------------------------------------------------
+// Run on main thread
+
+// Run the game window and its event loop on main thread.
+func (g *game) Run() {
+ pixelgl.Run(func() {
+ g.RunLazyInit()
+ g.RunEventLoop()
+ })
+}
+
+func (g *game) RunLazyInit() {
+ // This window will show up as soon as it is created.
+ win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Title: title + " (" + version + ")",
+ Icon: nil,
+ Bounds: pixel.R(0, 0, g.winWidth, g.winHeight),
+ Monitor: nil,
+ Resizable: true,
+ // Undecorated: true,
+ VSync: false,
+ })
+ if err != nil {
+ panic(err)
+ }
+ win.SetSmooth(true)
+
+ MoveWindowToCenterOfPrimaryMonitor := func(win *pixelgl.Window) {
+ vmodes := pixelgl.PrimaryMonitor().VideoModes()
+ vmodesLast := vmodes[len(vmodes)-1]
+ biggestResolution := pixel.R(0, 0, float64(vmodesLast.Width), float64(vmodesLast.Height))
+ win.SetPos(biggestResolution.Center().Sub(win.Bounds().Center()))
+ }
+ MoveWindowToCenterOfPrimaryMonitor(win)
+
+ // lazy init vars
+ g.window = win
+ g.camera = gg.NewCamera(g.ladder.bound.Center(), g.window.Bounds())
+
+ // register callback
+ windowGL := g.WindowDeep()
+ windowGL.SetSizeCallback(func(_ *glfw.Window, width int, height int) {
+ g.OnResize(float64(width), float64(height))
+ })
+
+ // time manager
+ g.vsync = time.Tick(time.Second / 120)
+ g.fpsw.Start()
+ g.dtw.Start()
+
+ // so-called loading
+ {
+ g.window.Clear(colornames.Brown)
+ screenCenter := g.window.Bounds().Center()
+ txt := text.New(screenCenter, gg.NewAtlas("", 36, nil))
+ txt.WriteString("Loading...")
+ txt.Draw(g.window, pixel.IM)
+ g.window.Update()
+ }
+ g.NextFrame(g.dtw.Dt()) // Give it a blood pressure.
+ g.NextFrame(g.dtw.Dt()) // Now the oxygenated blood will start to pump through its vein.
+ // Do whatever you want after that...
+
+ // from user setting
+ g.camera.Zoom(float64(g.initialZoomLevel))
+ g.camera.Rotate(g.initialRotateDegree)
+}
+
+func (g *game) RunEventLoop() {
+ for g.window.Closed() != true { // Your average event loop in main thread.
+ // Notice that all function calls as go routine are non-blocking, but the others will block the main thread.
+
+ // ---------------------------------------------------
+ // 0. dt
+ dt := g.dtw.Dt()
+
+ // ---------------------------------------------------
+ // 1. handling events
+ g.HandlingEvents(dt)
+
+ // ---------------------------------------------------
+ // 2. move on
+ g.NextFrame(dt)
+
+ // log.Println(g.window.Closed()) //
+
+ } // for
+} // func
+
+func (g *game) HandlingEvents(dt float64) {
+ // Notice that all function calls as go routine are non-blocking, but the others will block the main thread.
+
+ // system
+ if g.window.JustReleased(pixelgl.KeyEscape) {
+ g.window.SetClosed(true)
+ }
+ if g.window.JustReleased(pixelgl.KeySpace) {
+ g.Pause()
+ dialog.Message("%s", "Pause").Title("PPAP").Info()
+ g.Resume()
+ }
+ if g.window.JustReleased(pixelgl.KeyTab) {
+ if g.window.Monitor() == nil {
+ g.SetFullScreenMode(true)
+ } else {
+ g.SetFullScreenMode(false)
+ }
+ }
+
+ // scalpel mode
+ if g.window.JustReleased(pixelgl.MouseButtonRight) {
+ go func() {
+ g.isScalpelMode = !g.isScalpelMode
+ }()
+ }
+ if g.window.JustReleased(pixelgl.MouseButtonLeft) {
+ // ---------------------------------------------------
+ if !jukebox.IsPlaying() {
+ jukebox.Play()
+ }
+
+ // ---------------------------------------------------
+ posWin := g.window.MousePosition()
+ posGame := g.camera.Unproject(posWin)
+ go func() {
+ g.explosions.ExplodeAt(pixel.V(posGame.X, posGame.Y), pixel.V(10, 10))
+ }()
+
+ // ---------------------------------------------------
+ if g.isScalpelMode {
+ // strTitle := fmt.Sprint(posGame.X, ", ", posGame.Y) //
+ strDlg := fmt.Sprint(
+ "number of bridges: ", g.BridgesCount(), "\r\n", "\r\n",
+ "camera angle in degree: ", (g.camera.Angle()/math.Pi)*180, "\r\n", "\r\n",
+ "camera coordinates: ", g.camera.XY().X, g.camera.XY().Y, "\r\n", "\r\n",
+ "game clock: ", g.dtw.GetTimeStarted(), "\r\n", "\r\n",
+ "starfield speed: ", g.galaxy.Speed(), "\r\n", "\r\n",
+ "mouse click coords in screen pos: ", posWin.X, posWin.Y, "\r\n", "\r\n",
+ "mouse click coords in game pos: ", posGame.X, posGame.Y,
+ )
+ go func() {
+ // g.window.SetTitle(strTitle) //
+ dialog.Message("%s", strDlg).Title("MouseButtonLeft").Info()
+ }()
+ }
+ }
+
+ // game ctrl
+ if g.window.JustReleased(pixelgl.Key1) { // shuffle
+ go func() {
+ g.Shuffle(10, 750)
+ }()
+ }
+ if g.window.JustReleased(pixelgl.Key2) { // find path slow
+ go func() {
+ g.ResetPaths()
+ g.AnimatePaths(g.AnimatePath)
+ }()
+ }
+ if g.window.JustReleased(pixelgl.Key3) { // find path fast
+ go func() {
+ g.ResetPaths()
+ g.AnimatePaths(func(participant int) {
+ g.AnimatePathInTime(participant, 1)
+ })
+ }()
+ }
+
+ // camera
+ if g.window.JustReleased(pixelgl.KeyEnter) {
+ go func() {
+ g.camera.Rotate(-90)
+ }()
+ }
+ if g.window.Pressed(pixelgl.KeyRight) {
+ go func(dt float64) { // This camera will go diagonal while the case is in middle of rotating the camera.
+ g.camera.Move(pixel.V(1000*dt, 0).Rotated(-g.camera.Angle()))
+ }(dt)
+ }
+ if g.window.Pressed(pixelgl.KeyLeft) {
+ go func(dt float64) {
+ g.camera.Move(pixel.V(-1000*dt, 0).Rotated(-g.camera.Angle()))
+ }(dt)
+ }
+ if g.window.Pressed(pixelgl.KeyUp) {
+ go func(dt float64) {
+ g.camera.Move(pixel.V(0, 1000*dt).Rotated(-g.camera.Angle()))
+ }(dt)
+ }
+ if g.window.Pressed(pixelgl.KeyDown) {
+ go func(dt float64) {
+ g.camera.Move(pixel.V(0, -1000*dt).Rotated(-g.camera.Angle()))
+ }(dt)
+ }
+ { // if scrolled
+ zoomLevel := g.window.MouseScroll().Y
+ go func() {
+ g.camera.Zoom(zoomLevel)
+ }()
+ }
+}
+
+func (g *game) NextFrame(dt float64) {
+ // ---------------------------------------------------
+ // 1. update - calc state of game objects each frame
+ g.Update(dt)
+ g.fpsw.Poll()
+
+ // ---------------------------------------------------
+ // 2. draw on window
+ g.window.Clear(g.bg) // clear canvas
+ g.Draw() // then draw
+
+ // ---------------------------------------------------
+ // 3. update window - always end with it
+ g.window.Update()
+ <-g.vsync
+}
+
+// -------------------------------------------------------------------------
+// On compile
+
+const title = "AMIDA KUJI"
+
+var version = "undefined"
+
+// -------------------------------------------------------------------------
+// Entry point
+
+func main() {
+ defer func() {
+ err := jukebox.Finalize()
+ if err != nil {
+ log.Fatal(err)
+ }
+ }()
+ rand.Seed(time.Now().UnixNano())
+
+ conf := askConf()
+ if conf == nil {
+ conf = map[string]interface{}{
+ "window_width": 800.0,
+ "window_height": 800.0,
+ "max_player": 10.0,
+ "max_level": 100.0,
+ "width": 1500.0,
+ "height": 1500.0,
+ "zoom": -4.0,
+ "rotate_degree": 270.0,
+ "margin_top": 50.0,
+ "margin_right": 100.0,
+ "margin_bottom": 50.0,
+ "margin_left": 200.0,
+ "font_size": 28.0,
+ "picks": []interface{}{"Bulbasaur", "Ivysaur", "Venusaur", "Charmander", "Charmeleon", "Charizard", "Squirtle", "Wartortle", "Blastoise", "Caterpie", "Metapod", "Butterfree", "Weedle", "Kakuna", "Beedrill", "Pidgey", "Pidgeotto", "Pidgeot", "Rattata"},
+ "prizes": []interface{}{"TM88", "TM89", "TM90", "TM91", "TM92", "HM01", "HM02", "HM03", "HM04", "HM05", "HM06"},
+ }
+ }
+
+ newGame(gameConfig{
+ winWidth: conf["window_width"].(float64),
+ winHeight: conf["window_height"].(float64),
+ nParticipants: int(conf["max_player"].(float64)),
+ nLevel: int(conf["max_level"].(float64)),
+ width: conf["width"].(float64),
+ height: conf["height"].(float64),
+ initialZoomLevel: conf["zoom"].(float64),
+ initialRotateDegree: conf["rotate_degree"].(float64),
+ paddingTop: conf["margin_top"].(float64),
+ paddingRight: conf["margin_right"].(float64),
+ paddingBottom: conf["margin_bottom"].(float64),
+ paddingLeft: conf["margin_left"].(float64),
+ fontSize: conf["font_size"].(float64),
+ nametagPicks: gg.ItfsToStrs(conf["picks"].([]interface{})),
+ nametagPrizes: gg.ItfsToStrs(conf["prizes"].([]interface{})),
+ }).Run()
+}
+
+func askConf() (conf map[string]interface{}) {
+ for { // Load JSON
+ cwd, _ := os.Getwd()
+ filepath, err := dialog.File().Title("Load User Settings").
+ Filter("JSON Format (*.json)", "json").
+ Filter("All Files (*.*)", "*").
+ SetStartDir(cwd).Load()
+ if err != nil {
+ if err.Error() == "Cancelled" {
+ conf = nil
+ break
+ }
+ dialog.Message("%s", "Invalid file path."+"\r\n"+"\r\n"+fmt.Sprint(err)).Title("Failed to load JSON").Error()
+ continue
+ }
+ bytes, err := ioutil.ReadFile(filepath)
+ if err != nil {
+ dialog.Message("%s", "Could not read the file."+"\r\n"+"\r\n"+fmt.Sprint(err)).Title("Failed to load JSON").Error()
+ continue
+ }
+ err = json.Unmarshal(bytes, &conf)
+ if err != nil {
+ dialog.Message("%s", "The file is not valid JSON format."+"\r\n"+"\r\n"+fmt.Sprint(err)).Title("Failed to load JSON").Error()
+ continue
+ }
+ break
+ }
+ return
+}
diff --git a/examples/community/amidakuji/glossary/cam.go b/examples/community/amidakuji/glossary/cam.go
new file mode 100644
index 0000000..cfbf386
--- /dev/null
+++ b/examples/community/amidakuji/glossary/cam.go
@@ -0,0 +1,150 @@
+package glossary
+
+import (
+ "math"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "golang.org/x/image/colornames"
+)
+
+// Camera is a tool to get the screen center to be able to follow a certain point on a plane.
+type Camera struct {
+ anglePhysic float64 // Angle in radians (math.Pi)
+ angleFollow float64 // Angle expected to be in the near future.
+ zoomPosPhysic float64 // Z
+ zoomPosFollow float64 // Z expected to be in the near future.
+ planePosPhysic pixel.Vec // X, Y
+ planePosFollow pixel.Vec // X, Y expected to be in the near future.
+ screenBound pixel.Rect
+ moveSmooth bool
+}
+
+// NewCamera is a constructor.
+func NewCamera(_pos pixel.Vec, _screenBound pixel.Rect) *Camera {
+ return &Camera{
+ anglePhysic: 0,
+ angleFollow: 0,
+ zoomPosPhysic: 1.0,
+ zoomPosFollow: 1.0,
+ planePosPhysic: _pos,
+ planePosFollow: _pos,
+ screenBound: _screenBound,
+ moveSmooth: true,
+ }
+}
+
+// -------------------------------------------------------------------------
+// Read only
+
+// Transform returns a transformation matrix of a camera.
+// Use Transform().Project() to convert a game position to a screen position.
+// To do the inverse operation, it is recommended to use Camera#Unproject() rather than Transform().Unproject()
+func (camera Camera) Transform() pixel.Matrix {
+ return pixel.IM. // This transformation order is significant.
+ // ScaledXY(camera.planePos, pixel.V(camera.zoomPos, camera.zoomPos)).
+ Scaled(camera.planePosPhysic, camera.zoomPosPhysic). // Scaling
+ Rotated(camera.planePosPhysic, camera.anglePhysic). // Rotatation
+ Moved(camera.screenBound.Center().Sub(camera.planePosPhysic)) // Translation
+}
+
+// Unproject converts a screen position to a game position.
+// This method is a replacement of Transform().Unproject() which might return a bit off position.
+func (camera Camera) Unproject(screenPosition pixel.Vec) (gamePosition pixel.Vec) {
+ matrix1 := pixel.IM.
+ Scaled(camera.planePosPhysic, camera.zoomPosPhysic). // Scaling
+ Moved(camera.screenBound.Center().Sub(camera.planePosPhysic)) // Translation
+ matrix2 := pixel.IM.
+ Rotated(camera.planePosPhysic, -camera.anglePhysic) // Rotatation
+ return matrix2.Project(matrix1.Unproject(screenPosition))
+}
+
+// Angle returns the angle of a camera in radians.
+func (camera Camera) Angle() float64 {
+ return camera.anglePhysic
+}
+
+// XYZ returns a camera's coordinates value X, Y, and Z in a current physical state.
+func (camera Camera) XYZ() (float64, float64, float64) {
+ return camera.planePosPhysic.X, camera.planePosPhysic.Y, camera.zoomPosPhysic
+}
+
+// XY returns the X and Y of a camera as a vector.
+func (camera Camera) XY() pixel.Vec {
+ return camera.planePosPhysic
+}
+
+// Z returns the zoom depth of a camera.
+func (camera Camera) Z() float64 {
+ return camera.zoomPosPhysic
+}
+
+// -------------------------------------------------------------------------
+// Read and Write
+
+// Update a camera's current physical state (physics)
+// by calculating coordinates X, Y, Z and its angle after delta time in seconds.
+func (camera *Camera) Update(dt float64) {
+ if camera.moveSmooth { // lerp the camera position towards the target
+ angle := pixel.V(camera.anglePhysic, 0)
+ angleFollow := pixel.V(camera.angleFollow, 0)
+ angle = pixel.Lerp(angle, angleFollow, 1-math.Pow(1.0/128, dt))
+ camera.anglePhysic = angle.X
+ camera.planePosPhysic = pixel.Lerp(camera.planePosPhysic, camera.planePosFollow, 1-math.Pow(1.0/128, dt))
+ zoomPos := pixel.V(camera.zoomPosPhysic, 0)
+ zoomFollow := pixel.V(camera.zoomPosFollow, 0)
+ zoomPos = pixel.Lerp(zoomPos, zoomFollow, 1-math.Pow(1.0/128, dt))
+ camera.zoomPosPhysic = zoomPos.X
+ } else {
+ camera.anglePhysic = camera.angleFollow
+ camera.planePosPhysic = camera.planePosFollow
+ camera.zoomPosPhysic = camera.zoomPosFollow
+ }
+}
+
+// Rotate a camera by certain degrees.
+// + ) Counterclockwise
+// - ) Clockwise
+func (camera *Camera) Rotate(degree float64) {
+ camera.angleFollow += degree * math.Pi / 180
+}
+
+// Zoom in and out with a camera by certain levels.
+// + ) Zoom in
+// - ) Zoom out
+func (camera *Camera) Zoom(byLevel float64) {
+ const zoomAmount = 1.2
+ camera.zoomPosFollow *= math.Pow(zoomAmount, byLevel)
+}
+
+// Move camera a specified distance.
+func (camera *Camera) Move(distance pixel.Vec) {
+ camera.planePosFollow = camera.planePosFollow.Add(distance)
+}
+
+// MoveTo () moves a camera to a point on a plane.
+func (camera *Camera) MoveTo(posAim pixel.Vec) {
+ camera.planePosFollow = posAim
+}
+
+// SetScreenBound of a camera.
+func (camera *Camera) SetScreenBound(screenBound pixel.Rect) {
+ camera.screenBound = screenBound
+}
+
+// -------------------------------------------------------------------
+// Unnecessary
+
+// Aim for experiments.
+type Aim struct {
+ pos pixel.Vec
+}
+
+// Draw aim as a dot.
+func (aim Aim) Draw(t pixel.Target) {
+ imd := imdraw.New(nil)
+ imd.Color = colornames.Red
+ imd.Push(aim.pos)
+ imd.Circle(10, 0)
+ imd.Draw(t)
+}
diff --git a/examples/community/amidakuji/glossary/dtchk.go b/examples/community/amidakuji/glossary/dtchk.go
new file mode 100644
index 0000000..cf22d2c
--- /dev/null
+++ b/examples/community/amidakuji/glossary/dtchk.go
@@ -0,0 +1,67 @@
+package glossary
+
+import (
+ "errors"
+ "time"
+)
+
+// DtWatch is a delta time checker.
+type DtWatch struct {
+ init *time.Time
+ last *time.Time
+}
+
+// Start () is required in order to call other methods of a DtWatch.
+func (watch *DtWatch) Start() {
+ byVal1 := time.Now()
+ byVal2 := byVal1
+ watch.init = &byVal1
+ watch.last = &byVal2
+}
+
+// IsStarted () determines whether it has started or not.
+// .Start() is required in order to call this method.
+func (watch DtWatch) IsStarted() bool {
+ if watch.init == nil {
+ return false
+ } else if watch.init != nil {
+ return true
+ } else {
+ panic(errors.New("It might be thread"))
+ }
+}
+
+// GetTimeStarted gets the time it started.
+// .Start() must be called prior to calling this method.
+func (watch DtWatch) GetTimeStarted() time.Time {
+ return *watch.init
+}
+
+// SetTimeStarted sets the time it started.
+// .Start() must be called prior to calling this method.
+func (watch *DtWatch) SetTimeStarted(t time.Time) {
+ *watch.init = t
+}
+
+// Dt since last Dt() or DtNano().
+// .Start() must be called prior to calling this method.
+func (watch *DtWatch) Dt() (deltaTimeInSeconds float64) {
+ deltaTimeInSeconds = time.Since(time.Time(*watch.last)).Seconds()
+ *watch.last = time.Now()
+ return
+}
+
+// DtNano since last Dt() or DtNano().
+// It returns a time instance with nanosecond precision.
+// .Start() must be called prior to calling this method.
+func (watch *DtWatch) DtNano() (deltaTimeInNanosec time.Duration) {
+ deltaTimeInNanosec = time.Since(time.Time(*watch.last))
+ *watch.last = time.Now()
+ return
+}
+
+// DtSinceStart is dt since last Start().
+// .Start() must be called prior to calling this method.
+func (watch DtWatch) DtSinceStart() (deltaTimeInSeconds float64) {
+ return time.Since(time.Time(*watch.init)).Seconds()
+}
diff --git a/examples/community/amidakuji/glossary/explosive.go b/examples/community/amidakuji/glossary/explosive.go
new file mode 100644
index 0000000..d2cc69e
--- /dev/null
+++ b/examples/community/amidakuji/glossary/explosive.go
@@ -0,0 +1,198 @@
+package glossary
+
+import (
+ "image/color"
+ "math/rand"
+ "sync"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+)
+
+// -------------------------------------------------------------------------
+// explosive.go
+// - Original idea: "github.com/faiface/pixel-examples/community/bouncing"
+
+// --------------------------------------------------------------------
+
+// Explosions is an imdraw and a manager of all particles.
+type Explosions struct {
+ imd *imdraw.IMDraw
+ mutex sync.Mutex // It is unsafe to access any refd; ptrd object without a critical section.
+ //
+ *colorPicker
+ width float64
+ height float64
+ particles []*particle
+ precision int
+}
+
+// NewExplosions is a constructor.
+// The 3rd argument colors can be nil. Then it will use its default value of a color set.
+func NewExplosions(width, height float64, colors []color.Color, precision int) *Explosions {
+ return &Explosions{
+ nil, sync.Mutex{},
+ newColorPicker(colors),
+ width, height, nil,
+ precision,
+ }
+}
+
+// SetBound of particles. All particles bounce when they meet this bound.
+func (e *Explosions) SetBound(width, height float64) {
+ e.width = width
+ e.height = height
+}
+
+// IsExploding determines whether this Explosions is about to be updated or not.
+// Pass lock by value warning from (e Explosions) should be ignored,
+// because an Explosions here is just passed as a read only argument.
+func (e Explosions) IsExploding() bool {
+ e.mutex.Lock()
+ defer e.mutex.Unlock()
+
+ return e.particles != nil
+}
+
+// Draw guarantees the thread safety, though it's not a necessary condition.
+// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
+func (e *Explosions) Draw(t pixel.Target) {
+ e.mutex.Lock()
+ defer e.mutex.Unlock()
+
+ if e.imd == nil || len(e.particles) <= 0 { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ e.imd.Draw(t)
+}
+
+// Update animates an Explosions. An Explosions is drawn on an imdraw.
+func (e *Explosions) Update(dt float64) {
+ e.mutex.Lock()
+ defer e.mutex.Unlock()
+
+ // physics
+ aliveParticles := []*particle{}
+ for _, particle := range e.particles {
+ particle.update(dt, e.width, e.height)
+ if particle.life > 0 {
+ aliveParticles = append(aliveParticles, particle)
+ }
+ }
+ e.particles = aliveParticles
+
+ // imdraw (a state machine)
+ if e.imd == nil { // lazy creation
+ e.imd = imdraw.New(nil)
+ e.imd.EndShape = imdraw.RoundEndShape
+ e.imd.Precision = e.precision
+ }
+ imd := e.imd
+ imd.Clear()
+
+ // draw
+ for _, particle := range e.particles {
+ imd.Color = particle.color
+ imd.Push(particle.pos)
+ imd.Circle(16*particle.life, 0)
+ }
+}
+
+// ExplodeAt generates an explosion at given point.
+func (e *Explosions) ExplodeAt(pos, vel pixel.Vec) {
+ e.mutex.Lock()
+ defer e.mutex.Unlock()
+
+ e.next()
+ e.particles = append(e.particles,
+ newParticleAt(pos, vel.Rotated(1).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(2).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(3).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(4).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(5).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(6).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(7).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(8).Scaled(rand.Float64()), e.here()),
+ newParticleAt(pos, vel.Rotated(9).Scaled(rand.Float64()), e.here()),
+
+ newParticleAt(pos, vel.Rotated(10).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(20).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(30).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(40).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(50).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(60).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(70).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(80).Scaled(rand.Float64()+1), e.here()),
+ newParticleAt(pos, vel.Rotated(90).Scaled(rand.Float64()+1), e.here()),
+ )
+}
+
+// --------------------------------------------------------------------
+
+type particle struct {
+ pos pixel.Vec
+ vel pixel.Vec
+ color color.RGBA
+ life float64
+}
+
+func newParticleAt(pos, vel pixel.Vec, color color.RGBA) *particle {
+ color.A = 5
+ return &particle{pos, vel, color, rand.Float64() * 1.5}
+}
+
+func (p *particle) update(dt, width, height float64) {
+ p.pos = p.pos.Add(p.vel)
+ p.life -= 3 * dt
+ switch {
+ case p.pos.Y < 0 || p.pos.Y >= height:
+ p.vel.Y *= (-10 * dt)
+ case p.pos.X < 0 || p.pos.X >= width:
+ p.vel.X *= (-10 * dt)
+ }
+}
+
+// --------------------------------------------------------------------
+
+type colorPicker struct {
+ colors []color.RGBA
+ index int
+}
+
+func newColorPicker(_colors []color.Color) *colorPicker {
+ if _colors == nil {
+ _colors = []color.Color{
+ color.RGBA{190, 38, 51, 255},
+ color.RGBA{224, 111, 139, 255},
+ color.RGBA{73, 60, 43, 255},
+ color.RGBA{164, 100, 34, 255},
+ color.RGBA{235, 137, 49, 255},
+ color.RGBA{247, 226, 107, 255},
+ color.RGBA{47, 72, 78, 255},
+ color.RGBA{68, 137, 26, 255},
+ color.RGBA{163, 206, 39, 255},
+ color.RGBA{0, 87, 132, 255},
+ color.RGBA{49, 162, 242, 255},
+ color.RGBA{178, 220, 239, 255},
+ }
+ }
+ colors := []color.RGBA{}
+ for _, v := range _colors {
+ if c, ok := v.(color.RGBA); ok {
+ colors = append(colors, c)
+ }
+ }
+ return &colorPicker{colors, 0}
+}
+
+func (colorPicker *colorPicker) next() color.RGBA {
+ if colorPicker.index++; colorPicker.index >= len(colorPicker.colors) {
+ colorPicker.index = 0
+ }
+ return colorPicker.colors[colorPicker.index]
+}
+
+func (colorPicker *colorPicker) here() color.RGBA {
+ return colorPicker.colors[colorPicker.index]
+}
diff --git a/examples/community/amidakuji/glossary/fpschk.go b/examples/community/amidakuji/glossary/fpschk.go
new file mode 100644
index 0000000..513a16c
--- /dev/null
+++ b/examples/community/amidakuji/glossary/fpschk.go
@@ -0,0 +1,134 @@
+package glossary
+
+import (
+ "fmt"
+ "image/color"
+ "sync"
+ "time"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/text"
+ "golang.org/x/image/colornames"
+)
+
+// FPSWatch measures the real-time frame rates and displays it on a target canvas.
+type FPSWatch struct {
+ txt *text.Text // shared variable
+ atlas *text.Atlas // borrowed atlas for txt
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+ //
+ fps int // The FPS evaluated every second.
+ frames int // Frames count before the FPS update.
+ seccer <-chan time.Time // Ticks time every second.
+ //
+ desc string
+ pos pixel.Vec
+ anchorX AnchorX
+ anchorY AnchorY
+ colorBg color.Color
+ colorTxt color.Color
+}
+
+// NewFPSWatch is a constructor.
+func NewFPSWatch(
+ additionalCaption string, _pos pixel.Vec,
+ _anchorY AnchorY, _anchorX AnchorX, // This is because the order is usually Y then X in spoken language.
+ _colorBg, _colorTxt color.Color,
+) (watch *FPSWatch) {
+ return &FPSWatch{
+ atlas: AtlasASCII(),
+ fps: 0,
+ frames: 0,
+ seccer: nil,
+ desc: additionalCaption,
+ pos: _pos,
+ anchorX: _anchorX,
+ anchorY: _anchorY,
+ colorBg: _colorBg,
+ colorTxt: _colorTxt,
+ }
+}
+
+// NewFPSWatchSimple is a constructor.
+func NewFPSWatchSimple(_pos pixel.Vec, _anchorY AnchorY, _anchorX AnchorX) *FPSWatch {
+ return NewFPSWatch("", _pos, _anchorY, _anchorX, colornames.Black, colornames.White)
+}
+
+// Start ticking every second.
+func (watch *FPSWatch) Start() {
+ watch.seccer = time.Tick(time.Second)
+}
+
+// Poll () should be called only once and in every single frame. (Obligatory)
+// This is an extended behavior of Update() like funcs.
+func (watch *FPSWatch) Poll() {
+ watch.frames++
+ select {
+ case <-watch.seccer:
+ watch.fps = watch.frames
+ watch.frames = 0
+ go watch._Update()
+ default:
+ }
+}
+
+// SetPos to a position in screen coords.
+func (watch *FPSWatch) SetPos(pos pixel.Vec, anchorY AnchorY, anchorX AnchorX) {
+ watch.pos = pos
+ watch.anchorX = anchorX
+ watch.anchorY = anchorY
+}
+
+// GetFPS returns the most recent FPS recorded.
+// A non-ptr FPSWatch as a read only argument passes lock by value within itself but that seems totally fine.
+func (watch FPSWatch) GetFPS() int {
+ return watch.fps
+}
+
+// Draw FPSWatch.
+func (watch *FPSWatch) Draw(t pixel.Target) {
+ // lock before accessing txt & imdraw
+ watch.mutex.Lock()
+ defer watch.mutex.Unlock()
+
+ if watch.imd == nil && watch.txt == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ watch.imd.Draw(t)
+ watch.txt.Draw(t, pixel.IM)
+}
+
+// unexported
+func (watch *FPSWatch) _Update() {
+ // lock before txt & imdraw update
+ watch.mutex.Lock()
+ defer watch.mutex.Unlock()
+
+ // text label (a state machine)
+ if watch.txt == nil { // lazy creation
+ watch.txt = text.New(pixel.ZV, watch.atlas)
+ }
+ txt := watch.txt
+ txt.Clear()
+
+ str := fmt.Sprint("FPS: ", watch.fps, " ", watch.desc)
+ AnchorTxt(txt, watch.pos, watch.anchorX, watch.anchorY, str)
+ txt.Color = watch.colorTxt
+ txt.Dot.X -= 1.0
+ txt.Dot.Y += 5.0
+ txt.WriteString(str)
+
+ // imdraw (a state machine)
+ if watch.imd == nil { // lazy creation
+ watch.imd = imdraw.New(nil)
+ }
+ imd := watch.imd
+ imd.Clear()
+
+ imd.Color = watch.colorBg
+ imd.Push(VerticesOfRect(txt.Bounds())...)
+ imd.Polygon(0)
+}
diff --git a/examples/community/amidakuji/glossary/jukebox/music.go b/examples/community/amidakuji/glossary/jukebox/music.go
new file mode 100644
index 0000000..6c6da80
--- /dev/null
+++ b/examples/community/amidakuji/glossary/jukebox/music.go
@@ -0,0 +1,130 @@
+package jukebox
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "sync"
+ "time"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+
+ "github.com/faiface/beep"
+ "github.com/faiface/beep/speaker"
+ "github.com/faiface/beep/vorbis"
+)
+
+// -------------------------------------------------------------------------
+
+const nMusics = 2
+
+var (
+ mutex sync.Mutex
+ isPlaying bool
+ musics [nMusics]*_Music
+)
+
+// -------------------------------------------------------------------------
+
+// singleton
+func init() {
+ // city pop favorites
+ musics[0] = _NewMusicFromAsset("nighttempo-purepresent1", "karaoke/kikuchimomoko-nightcruising.ogg")
+ musics[1] = _NewMusicFromAsset("nighttempo-purepresent2", "karaoke/takeuchimariya-plasticlove.ogg")
+
+ // speaker on
+ speaker.Init(musics[0].format.SampleRate, musics[0].format.SampleRate.N(time.Second))
+ speaker.Play(beep.Iterate(func() (soundtrack beep.Streamer) {
+ musics[0].stream.Seek(0)
+ musics[1].stream.Seek(0)
+ return beep.Seq(musics[0].stream, musics[1].stream)
+ }))
+ speaker.Lock()
+}
+
+// IsPlaying determines whether the soundtrack is currently playing or not.
+func IsPlaying() bool {
+ return isPlaying
+}
+
+// Play unlocks the speaker.
+func Play() {
+ mutex.Lock()
+ defer mutex.Unlock()
+ if !isPlaying {
+ isPlaying = true
+ speaker.Unlock()
+ }
+ return
+}
+
+// Pause locks the speaker.
+func Pause() {
+ mutex.Lock()
+ defer mutex.Unlock()
+ if isPlaying {
+ isPlaying = false
+ speaker.Lock()
+ }
+ return
+}
+
+// Finalize should be called on program exit.
+// This function deletes the temporary music file its package generates.
+func Finalize() error {
+ errs := ""
+ for _, music := range musics {
+ music.Close()
+ err := music._Destory()
+ if err != nil {
+ errs += " " + err.Error()
+ }
+ }
+ if errs != "" {
+ return errors.New(errs)
+ }
+ return nil
+}
+
+// -------------------------------------------------------------------------
+
+// NewMusicFromAsset is a constructor.
+func _NewMusicFromAsset(nameMusic, nameAsset string) *_Music {
+ asset, err := gg.Asset(nameAsset)
+ if err != nil {
+ // log.Fatal(err) //
+ }
+ return _NewMusic(nameMusic, asset)
+}
+
+// Music is a temporary file to play a single background music. It should be destroyed on program exit.
+type _Music struct {
+ os.File
+ stream beep.StreamSeekCloser
+ format beep.Format
+}
+
+// NewMusic creates an instance of Music, a temporary file from which the speaker plays a music.
+// speaker.Lock() to pause.
+// speaker.Unlock() to resume/play.
+func _NewMusic(name string, asset []byte) *_Music {
+ tmpfile, err := ioutil.TempFile("", name)
+ if err != nil {
+ // log.Fatal(err) //
+ }
+ // log.Println(tmpfile.Name()) //
+ _, err = tmpfile.Write(asset)
+ if err != nil {
+ // log.Fatal(err) //
+ }
+ stream, format, err := vorbis.Decode(tmpfile)
+ if err != nil {
+ // log.Fatal(err) //
+ }
+ return &_Music{*tmpfile, stream, format}
+}
+
+// Destory deletes the temporary music file.
+func (music *_Music) _Destory() error {
+ return os.Remove(music.Name())
+}
diff --git a/examples/community/amidakuji/glossary/starfield.go b/examples/community/amidakuji/glossary/starfield.go
new file mode 100644
index 0000000..86c086a
--- /dev/null
+++ b/examples/community/amidakuji/glossary/starfield.go
@@ -0,0 +1,166 @@
+package glossary
+
+import (
+ "image/color"
+ "math/rand"
+ "sync"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+)
+
+// -------------------------------------------------------------------------
+// Reusable modified starfiled
+// - Original: "github.com/faiface/pixel-examples/community/starfield"
+// - Encapsulated by nanitefactory
+
+// -------------------------------------------------------------------------
+// Galaxy
+
+type star struct {
+ Pos pixel.Vec // x, y
+ Z float64 // z
+ P float64 // prev z
+ C color.RGBA // color
+}
+
+// Galaxy is an imd of stars.
+type Galaxy struct {
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+ //
+ width float64
+ height float64
+ speed float64
+ stars [1024]*star
+}
+
+// NewGalaxy is a constructor.
+func NewGalaxy(_width, _height, _speed float64) *Galaxy {
+ return &Galaxy{
+ width: _width,
+ height: _height,
+ speed: _speed,
+ }
+}
+
+// Speed is a getter.
+// Pass lock by value warning from (galaxy Galaxy) should be ignored,
+// because a galaxy here is just passed as a read only argument.
+func (galaxy Galaxy) Speed() float64 {
+ return galaxy.speed
+}
+
+// SetSpeed is a setter.
+func (galaxy *Galaxy) SetSpeed(_speed float64) {
+ galaxy.speed = _speed
+}
+
+// Draw guarantees the thread safety, though it's not a necessary condition.
+// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
+func (galaxy *Galaxy) Draw(t pixel.Target) {
+ galaxy.mutex.Lock()
+ defer galaxy.mutex.Unlock()
+
+ if galaxy.imd == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ galaxy.imd.Draw(t)
+}
+
+// Update animates a galaxy.
+func (galaxy *Galaxy) Update(dt float64) {
+ // random()
+ random := func(min, max float64) float64 {
+ return rand.Float64()*(max-min) + min
+ }
+
+ // newStar()
+ newStar := func() *star {
+ starColors := []color.RGBA{
+ color.RGBA{157, 180, 255, 255},
+ color.RGBA{162, 185, 255, 255},
+ color.RGBA{167, 188, 255, 255},
+ color.RGBA{170, 191, 255, 255},
+ color.RGBA{175, 195, 255, 255},
+ color.RGBA{186, 204, 255, 255},
+ color.RGBA{192, 209, 255, 255},
+ color.RGBA{202, 216, 255, 255},
+ color.RGBA{228, 232, 255, 255},
+ color.RGBA{237, 238, 255, 255},
+ color.RGBA{251, 248, 255, 255},
+ color.RGBA{255, 249, 249, 255},
+ color.RGBA{255, 245, 236, 255},
+ color.RGBA{255, 244, 232, 255},
+ color.RGBA{255, 241, 223, 255},
+ color.RGBA{255, 235, 209, 255},
+ color.RGBA{255, 215, 174, 255},
+ color.RGBA{255, 198, 144, 255},
+ color.RGBA{255, 190, 127, 255},
+ color.RGBA{255, 187, 123, 255},
+ color.RGBA{255, 187, 123, 255},
+ } // Colors based on stellar types listed at // http://www.vendian.org/mncharity/dir3/starcolor/
+ return &star{
+ Pos: pixel.V(random(-galaxy.width, galaxy.width), random(-galaxy.height, galaxy.height)),
+ Z: random(0, galaxy.width),
+ P: 0,
+ C: starColors[rand.Intn(len(starColors))],
+ }
+ }
+
+ // lock before imdraw update
+ galaxy.mutex.Lock()
+ defer galaxy.mutex.Unlock()
+
+ // imdraw (a state machine)
+ if galaxy.imd == nil { // lazy creation
+ galaxy.imd = imdraw.New(nil)
+ galaxy.imd.SetMatrix(pixel.IM.Moved(pixel.V(galaxy.width/2, galaxy.height/2)))
+ }
+ imd := galaxy.imd
+ imd.Clear()
+ imd.Precision = 7
+
+ // now update all stars in this galaxy
+ for i, s := range galaxy.stars {
+ if s == nil {
+ galaxy.stars[i] = newStar()
+ s = galaxy.stars[i]
+ }
+
+ scale := func(unscaledNum, min, max, minAllowed, maxAllowed float64) float64 {
+ return (maxAllowed-minAllowed)*(unscaledNum-min)/(max-min) + minAllowed
+ }
+
+ s.P = s.Z
+ s.Z -= dt * galaxy.speed
+
+ if s.Z < 0 {
+ s.Pos.X = random(-galaxy.width, galaxy.width)
+ s.Pos.Y = random(-galaxy.height, galaxy.height)
+ s.Z = galaxy.width
+ s.P = s.Z
+ }
+
+ p := pixel.V(
+ scale(s.Pos.X/s.Z, 0, 1, 0, galaxy.width),
+ scale(s.Pos.Y/s.Z, 0, 1, 0, galaxy.height),
+ )
+
+ o := pixel.V(
+ scale(s.Pos.X/s.P, 0, 1, 0, galaxy.width),
+ scale(s.Pos.Y/s.P, 0, 1, 0, galaxy.height),
+ )
+
+ r := scale(s.Z, 0, galaxy.width, 11, 0)
+
+ galaxy.imd.Color = s.C
+ if p.Sub(o).Len() > 6 {
+ galaxy.imd.Push(p, o)
+ galaxy.imd.Line(r)
+ }
+ galaxy.imd.Push(p)
+ galaxy.imd.Circle(r, 0)
+ }
+}
diff --git a/examples/community/amidakuji/glossary/util.go b/examples/community/amidakuji/glossary/util.go
new file mode 100644
index 0000000..a3e7b04
--- /dev/null
+++ b/examples/community/amidakuji/glossary/util.go
@@ -0,0 +1,215 @@
+package glossary
+
+import (
+ "bytes"
+ "fmt"
+ "image"
+ "io/ioutil"
+ "math"
+ "math/rand"
+ "os"
+
+ // Relevant packages of target format for a decoder must be initialized to register.
+ _ "image/gif"
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/text"
+ "github.com/golang/freetype/truetype"
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/basicfont"
+)
+
+var atlasASCII *text.Atlas
+
+func init() {
+ atlasASCII = NewAtlas("", 18, nil)
+}
+
+// AtlasASCII returns an atlas which allows you to draw only ASCII characters.
+// Atlas is a set of generated textures for glyphs in a specific font.
+func AtlasASCII() *text.Atlas {
+ return atlasASCII
+}
+
+// NewAtlas newly loads and prepares a set of images of characters or symbols to be drawn.
+// Arg runeSet would be set to nil if non-ASCII characters are not in use.
+func NewAtlas(nameAssetTTF string, size float64, runeSet []rune) *text.Atlas {
+ if nameAssetTTF == "" {
+ nameAssetTTF = "NanumBarunGothic.ttf"
+ }
+
+ var face font.Face
+ asset, err := Asset(nameAssetTTF)
+ if err == nil {
+ face, err = LoadTrueTypeFont(asset, size)
+ }
+ if err != nil {
+ face = basicfont.Face7x13
+ }
+ return text.NewAtlas(face, text.ASCII, runeSet)
+}
+
+// NewSprite converts an asset (resource) into a sprite. Returns nil if there is an error.
+// AssetNames() or AssetDir() might be helpful when utilizing this function.
+func NewSprite(nameAsset string) *pixel.Sprite {
+ asset, err := Asset(nameAsset)
+ if err != nil {
+ // log.Println("1", err) //
+ return nil
+ }
+ pic, err := LoadPicture(asset)
+ if err != nil {
+ // log.Println("2", err) //
+ return nil
+ }
+ // log.Println("3", "success yay") //
+ return pixel.NewSprite(pic, pic.Bounds())
+}
+
+// LoadTrueTypeFontFromFile creates and returns a font face.
+func LoadTrueTypeFontFromFile(path string, size float64) (font.Face, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ bytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+
+ face, err := LoadTrueTypeFont(bytes, size)
+ if err != nil {
+ return nil, err
+ }
+
+ return face, nil
+}
+
+// LoadPictureFromFile decodes an image that has been encoded in a registered format. (png, jpg, etc.)
+// Format registration is typically done by an init function in the codec-specific package. (with underscore import)
+func LoadPictureFromFile(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+// LoadTrueTypeFont creates and returns a font face.
+func LoadTrueTypeFont(bytes []byte, size float64) (font.Face, error) {
+ font, err := truetype.Parse(bytes)
+ if err != nil {
+ return nil, err
+ }
+ return truetype.NewFace(font, &truetype.Options{
+ Size: size,
+ GlyphCacheEntries: 1,
+ }), nil
+}
+
+// LoadPicture decodes an image that has been encoded in a registered format. (png, jpg, etc.)
+// Format registration is typically done by an init function in the codec-specific package. (with underscore import)
+func LoadPicture(_bytes []byte) (pixel.Picture, error) {
+ img, _, err := image.Decode(bytes.NewReader(_bytes))
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+// RandomNiceColor from Platformer.
+// Is not completely random without rand.Seed().
+func RandomNiceColor() pixel.RGBA {
+again:
+ r := rand.Float64()
+ g := rand.Float64()
+ b := rand.Float64()
+ len := math.Sqrt(r*r + g*g + b*b)
+ if len == 0 {
+ goto again
+ }
+ return pixel.RGB(r/len, g/len, b/len)
+}
+
+// VerticesOfRect returns 4 vertices of a rectangle in a form of a slice of vectors.
+func VerticesOfRect(r pixel.Rect) []pixel.Vec {
+ return []pixel.Vec{
+ r.Min,
+ pixel.V(r.Max.X, r.Min.Y),
+ r.Max,
+ pixel.V(r.Min.X, r.Max.Y),
+ }
+}
+
+// ItfsToStrs converts []interface{} to []string.
+func ItfsToStrs(itfs []interface{}) (strs []string) {
+ strs = make([]string, len(itfs))
+ for i, v := range itfs {
+ strs[i] = fmt.Sprint(v)
+ }
+ return strs
+}
+
+// Direction returns a direction as a normalized vector. This vector always has a length of 1.
+func Direction(from, to pixel.Vec) (dirVecNormalized pixel.Vec) {
+ vec := to.Sub(from)
+ if vec.X == 0 && vec.Y == 0 {
+ return vec
+ }
+ return vec.Unit()
+}
+
+// -------------------------------------------------------------------------
+// Anchors
+
+// AnchorY - Top, Middle, Bottom
+type AnchorY int
+
+// enum AnchorY
+const (
+ Top AnchorY = 1 + iota
+ Middle
+ Bottom
+)
+
+// AnchorX - Left, Center, Right
+type AnchorX int
+
+// enum AnchorX
+const (
+ Left AnchorX = 1 + iota
+ Center
+ Right
+)
+
+// AnchorTxt positions a text.Text label with an anchor alignment.
+func AnchorTxt(txt *text.Text, pos pixel.Vec, anchorX AnchorX, anchorY AnchorY, desc string) {
+ txt.Orig = pos
+ txt.Dot = pos
+ switch anchorX {
+ case Left:
+ txt.Dot.X -= 0
+ case Center:
+ txt.Dot.X -= (txt.BoundsOf(desc).W() / 2)
+ case Right:
+ txt.Dot.X -= txt.BoundsOf(desc).W()
+ }
+ switch anchorY {
+ case Top:
+ txt.Dot.Y -= txt.BoundsOf(desc).H()
+ case Middle:
+ txt.Dot.Y -= (txt.BoundsOf(desc).H() / 2)
+ case Bottom:
+ txt.Dot.Y -= 0
+ }
+ txt.Dot.X += 0
+ txt.Dot.Y += 0
+}
diff --git a/examples/community/amidakuji/ladder.go b/examples/community/amidakuji/ladder.go
new file mode 100644
index 0000000..70b2aa6
--- /dev/null
+++ b/examples/community/amidakuji/ladder.go
@@ -0,0 +1,334 @@
+package main
+
+import (
+ "math/rand"
+ "sync"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "golang.org/x/image/colornames"
+)
+
+// Ladder is an imdraw that does not animate at all,
+// hence does not need to be modified every frame.
+// Something kinda static and bone-like.
+type Ladder struct {
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+ //
+ bound pixel.Rect
+ grid [][]pixel.Vec
+ bridges [][]bool
+ nParticipants int
+ nLevel int
+ paddingTop float64
+ paddingRight float64
+ paddingBottom float64
+ paddingLeft float64
+ colors []pixel.RGBA
+}
+
+// NewLadder is a constructor.
+func NewLadder(_nParticipants, _nLevel int,
+ _width, _height,
+ _paddingTop, _paddingRight,
+ _paddingBottom, _paddingLeft float64) *Ladder {
+
+ // get random colors
+ colors := []pixel.RGBA{}
+ for i := 0; i < _nParticipants; i++ {
+ colors = append(colors, gg.RandomNiceColor())
+ }
+
+ // new grid
+ newGrid := func(nRow, nCol int) [][]pixel.Vec {
+ arr := make([][]pixel.Vec, nRow)
+ for i := range arr {
+ arr[i] = make([]pixel.Vec, nCol)
+ }
+ // log.Println(arr) //
+ return arr
+ }
+
+ // new bridges
+ newBridges := func(nParticipants, nLevel int) [][]bool {
+ nRow := nParticipants - 1
+ nCol := nLevel
+ arr := make([][]bool, nRow)
+ for i := range arr {
+ arr[i] = make([]bool, nCol)
+ }
+ return arr
+ }
+
+ // init ladder
+ l := Ladder{
+ imd: imdraw.New(nil),
+ bound: pixel.R(0, 0, _width, _height),
+ grid: newGrid(_nParticipants, _nLevel),
+ bridges: newBridges(_nParticipants, _nLevel),
+ nParticipants: _nParticipants,
+ nLevel: _nLevel,
+ paddingTop: _paddingTop,
+ paddingBottom: _paddingBottom,
+ paddingRight: _paddingRight,
+ paddingLeft: _paddingLeft,
+ colors: colors,
+ }
+
+ // init grid
+ updateGrid := func(l *Ladder) {
+ for participant := range l.grid { // row
+ for level := range l.grid[participant] { // col
+ y := l.Height() - (float64(participant) * l.DistParticipant()) // reverse
+ x := float64(level) * l.DistLevel()
+ y -= l.paddingTop
+ x += l.paddingLeft
+ l.grid[participant][level] = pixel.V(x, y)
+ }
+ }
+ } // Indices would not be aligned with the screen coordinates. (Reverse Y - Rows)
+ updateGrid(&l)
+
+ // log.Println(l.grid) //
+ return &l
+}
+
+// -------------------------------------------------------------------------
+// Important methods
+
+// Draw guarantees the thread safety, though it's not a necessary condition.
+// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
+func (l *Ladder) Draw(t pixel.Target) {
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+
+ if l.imd == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ l.imd.Draw(t)
+}
+
+// Update draws a ladder on an imdraw.
+func (l *Ladder) Update() {
+ ptsMovedAbout := func(sub pixel.Vec, pts ...pixel.Vec) (ptsMoved []pixel.Vec) {
+ ptsMoved = make([]pixel.Vec, len(pts))
+ copy(ptsMoved, pts)
+ for i, vec := range ptsMoved {
+ ptsMoved[i] = vec.Sub(sub)
+ }
+ // log.Println(ptsMoved) // debug
+ // log.Println(pts) // debug
+ return ptsMoved
+ }
+
+ ptsStart := l.PtsAtLevelOfPicks()
+ ptsEnd := l.PtsAtLevelOfPrizes()
+
+ circleRadius := 20.0
+ circlePts := ptsMovedAbout(pixel.V(circleRadius+10, 0), ptsStart...)
+
+ // lock shared imdraw access
+ l.mutex.Lock()
+ defer l.mutex.Unlock()
+
+ // imdraw (a state machine)
+ if l.imd == nil { // lazy creation
+ l.imd = imdraw.New(nil)
+ }
+ imd := l.imd
+ imd.Clear()
+
+ // draw lanes
+ imd.Color = colornames.White
+ imd.EndShape = imdraw.RoundEndShape
+ for i := range ptsStart {
+ imd.Push(ptsStart[i], ptsEnd[i])
+ imd.Line(13)
+ }
+
+ // draw bridges
+ imd.Color = colornames.White
+ imd.EndShape = imdraw.RoundEndShape
+ for nrow, row := range l.bridges {
+ for ncol, e := range row {
+ if e {
+ imd.Push(l.grid[nrow][ncol], l.grid[nrow+1][ncol])
+ imd.Line(13)
+ }
+ }
+ }
+
+ // draw start points
+ imd.EndShape = imdraw.RoundEndShape
+ for i := range ptsStart {
+ imd.Color = l.colors[i]
+ imd.Push(circlePts[i])
+ }
+ imd.Circle(circleRadius, 0)
+}
+
+// -------------------------------------------------------------------------
+// Read only methods
+
+// Height returns the height of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) Height() float64 {
+ return l.bound.H()
+}
+
+// Width returns the width of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) Width() float64 {
+ return l.bound.W()
+}
+
+// DistLevel returns the distance between each level.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) DistLevel() float64 {
+ return (l.Width() - (l.paddingLeft + l.paddingRight)) / float64(l.nLevel-1)
+}
+
+// DistParticipant returns the distance between each lane.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) DistParticipant() float64 {
+ return (l.Height() - (l.paddingTop + l.paddingBottom)) / float64(l.nParticipants-1)
+}
+
+// PtsAtLevelOfPicks returns all starting points of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) PtsAtLevelOfPicks() (ret []pixel.Vec) {
+ const levelOfDraw int = 0 // where it starts
+ ret = make([]pixel.Vec, l.nParticipants, l.nParticipants)
+
+ for participant := range l.grid { // row
+ ret[participant] = l.grid[participant][levelOfDraw]
+ }
+
+ // log.Println(len(ret), ret) //
+ return ret //[:l.nParticipants] //
+}
+
+// PtAtLevelOfPicks returns a starting point of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) PtAtLevelOfPicks(participant int) pixel.Vec {
+ const levelOfDraw int = 0 // where it starts
+ return l.grid[participant][levelOfDraw]
+}
+
+// PtsAtLevelOfPrizes returns all end points of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) PtsAtLevelOfPrizes() (ret []pixel.Vec) {
+ levelOfPrize := l.nLevel - 1 // where it ends
+ ret = make([]pixel.Vec, l.nParticipants, l.nParticipants)
+
+ for participant := range l.grid { // row
+ ret[participant] = l.grid[participant][levelOfPrize]
+ }
+
+ // log.Println(len(ret), ret) //
+ return ret //[:l.nParticipants] //
+}
+
+// PtAtLevelOfPrizes returns an end point of a ladder.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (l Ladder) PtAtLevelOfPrizes(participant int) pixel.Vec {
+ levelOfPrize := l.nLevel - 1 // where it ends
+ return l.grid[participant][levelOfPrize]
+}
+
+// -------------------------------------------------------------------
+// Methods that write to itself
+
+// ClearBridges of a ladder.
+// Only values are changed, not the pointers.
+func (l *Ladder) ClearBridges() {
+ for _, row := range l.bridges {
+ for i := range row {
+ row[i] = false
+ }
+ }
+}
+
+// GenerateRandomBridges of an approximate amount.
+// Only values are changed, not the pointers.
+func (l *Ladder) GenerateRandomBridges(amountApprox int) {
+ pickOneBridgeInRandom := func(l *Ladder) {
+ nRow := len(l.bridges)
+ nCol := l.nLevel
+ row := rand.Intn(int(nRow)) // participant
+ col := rand.Intn(int(nCol)) // level
+ // check right
+ isOkRight := func(rowRight, col int) bool {
+ includeLowerBound := func(rowRight int) bool {
+ return rowRight >= 0
+ }
+ if !includeLowerBound(rowRight) { // out of bound
+ return true
+ }
+ if !l.bridges[rowRight][col] {
+ return true
+ }
+ return false
+ }
+ // check left
+ isOkLeft := func(rowLeft, col int) bool {
+ excludeUpperBound := func(rowLeft int) bool {
+ return rowLeft < len(l.bridges)
+ }
+ if !excludeUpperBound(rowLeft) { // out of bound
+ return true
+ }
+ if !l.bridges[rowLeft][col] {
+ return true
+ }
+ return false
+ }
+ rowRight := row - 1
+ rowLeft := row + 1
+ if isOkRight(rowRight, col) && isOkLeft(rowLeft, col) {
+ l.bridges[row][col] = true
+ }
+ } // func
+
+ // repeat
+ for i := 0; i < amountApprox; i++ {
+ pickOneBridgeInRandom(l)
+ }
+} // method
+
+// RegenerateRandomBridges clears out all bridges and then GenerateRandomBridges() an approximate amount.
+// Only values are changed, not the pointers.
+func (l *Ladder) RegenerateRandomBridges(amountApprox int) {
+ l.ClearBridges()
+ l.GenerateRandomBridges(amountApprox)
+}
+
+// RegenerateRandomColors sets all colors of a ladder random for each.
+// Only values are changed, not the pointers.
+func (l *Ladder) RegenerateRandomColors() {
+ for i := range l.colors {
+ l.colors[i] = gg.RandomNiceColor()
+ }
+}
+
+// Reset all bridges and colors.
+// Only values are changed, not the pointers.
+func (l *Ladder) Reset() {
+ aboutTwo := l.nParticipants * (l.nLevel - 1) * 2
+ aboutOne := l.nParticipants * (l.nLevel - 1)
+ aboutHalf := (l.nParticipants * (l.nLevel - 1)) / 2
+ //
+ var pick [4]int
+ pick[0] = aboutTwo
+ pick[1] = aboutOne
+ pick[2] = aboutHalf
+ i := rand.Intn(3)
+ // log.Println(i) //
+ //
+ l.RegenerateRandomBridges(pick[i])
+ l.RegenerateRandomColors()
+}
diff --git a/examples/community/amidakuji/nametag.go b/examples/community/amidakuji/nametag.go
new file mode 100644
index 0000000..0cd4825
--- /dev/null
+++ b/examples/community/amidakuji/nametag.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+ "image/color"
+ "sync"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/text"
+ "golang.org/x/image/colornames"
+)
+
+// Nametags is a list(slice) of nametags.
+type Nametags []Nametag
+
+// Update nametags.
+func (updaters Nametags) Update() {
+ for i := range updaters {
+ updaters[i].Update()
+ }
+}
+
+// Draw nametags.
+func (updaters Nametags) Draw(t pixel.Target) {
+ for i := range updaters {
+ updaters[i].Draw(t)
+ }
+}
+
+// Nametag for each nametag.
+type Nametag struct {
+ txt *text.Text // shared variable
+ atlas *text.Atlas // borrowed atlas for txt
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+ //
+ desc string
+ pos pixel.Vec
+ anchorX gg.AnchorX
+ anchorY gg.AnchorY
+ colorBg color.Color
+ colorTxt color.Color
+}
+
+// NewNametag is a constructor.
+func NewNametag(
+ _atlas *text.Atlas,
+ _desc string, _pos pixel.Vec,
+ _anchorY gg.AnchorY, // This is because the order is usually Y then X in spoken language.
+ _anchorX gg.AnchorX,
+ _colorBg, _colorTxt color.Color) *Nametag {
+ atlas := _atlas
+ if atlas == nil {
+ atlas = gg.AtlasASCII()
+ }
+ return &Nametag{
+ atlas: atlas,
+ desc: _desc,
+ pos: _pos,
+ anchorX: _anchorX,
+ anchorY: _anchorY,
+ colorBg: _colorBg,
+ colorTxt: _colorTxt,
+ }
+}
+
+// NewNametagSimple is a constructor.
+func NewNametagSimple(
+ _atlas *text.Atlas,
+ _desc string, _pos pixel.Vec,
+ _anchorY gg.AnchorY,
+ _anchorX gg.AnchorX,
+) *Nametag {
+ return NewNametag(_atlas, _desc, _pos, _anchorY, _anchorX, colornames.Wheat, colornames.Black)
+}
+
+// String of a nametag.
+// A getter and a callback which allows a nametag to be passed to a function as a string.
+// A non-ptr Nametag as a read only argument passes lock by value within itself but that seems totally fine.
+func (n Nametag) String() string {
+ return n.desc
+}
+
+// Draw a nametag.
+func (n *Nametag) Draw(t pixel.Target) {
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ if n.imd == nil && n.txt == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ n.imd.Draw(t)
+ n.txt.Draw(t, pixel.IM)
+}
+
+// Update a nametag.
+func (n *Nametag) Update() {
+ // lock before txt & imdraw update
+ n.mutex.Lock()
+ defer n.mutex.Unlock()
+
+ // text label (a state machine)
+ if n.txt == nil { // lazy creation
+ n.txt = text.New(pixel.ZV, n.atlas)
+ }
+ txt := n.txt
+ txt.Clear()
+
+ gg.AnchorTxt(txt, n.pos, n.anchorX, n.anchorY, n.desc)
+ txt.Color = n.colorTxt
+ txt.WriteString(n.desc)
+
+ // imdraw (a state machine)
+ if n.imd == nil { // lazy creation
+ n.imd = imdraw.New(nil)
+ }
+ imd := n.imd
+ imd.Clear()
+
+ imd.Color = n.colorBg
+ imd.Push(gg.VerticesOfRect(txt.Bounds())...)
+ imd.Polygon(0)
+}
diff --git a/examples/community/amidakuji/path.go b/examples/community/amidakuji/path.go
new file mode 100644
index 0000000..31b29b5
--- /dev/null
+++ b/examples/community/amidakuji/path.go
@@ -0,0 +1,281 @@
+package main
+
+import (
+ "math"
+ "sync"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+)
+
+// Path is for animating a path to the prize in a ladder.
+type Path struct {
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+ //
+ roads []pixel.Vec // A list of vectors - each vector for a position where a road starts.
+ prize *int
+ tip *pixel.Vec
+ tipDir pixel.Vec
+ iroad int
+ //
+ watchAnim gg.DtWatch // When it started to animate.
+ timeLimitAnimInSec float64
+ animateInTime bool
+ isAnimating bool
+
+ // -----------------------------------------------------------
+ // exported callbacks(listeners) regarding animation
+
+ // callback on reaching the prize level of a ladder.
+ OnFinishedAnimation func()
+
+ // callback when the animating 'tip' passes a point of a road.
+ // pt: a point(road) just passed.
+ // dir: ...
+ // dir is a normalized vector. (pixel.ZV) is passed if the direction can't be found.
+ // dir can be different depending on how fast this Path is updated.
+ OnPassedEachPoint func(pt pixel.Vec, dir pixel.Vec)
+}
+
+// NewPath is a contructor.
+func NewPath(_roads []pixel.Vec, _prize *int) *Path {
+ newTip := func() *pixel.Vec {
+ if _roads != nil {
+ if len(_roads) > 0 {
+ v := _roads[0]
+ return &v
+ }
+ }
+ return nil
+ }
+ return &Path{
+ roads: _roads,
+ prize: _prize,
+ tip: newTip(),
+ tipDir: pixel.ZV,
+ }
+}
+
+// NewPathEmpty is a contructor.
+func NewPathEmpty() *Path {
+ return &Path{}
+}
+
+// -------------------------------------------------------------------------
+// Important methods
+
+// Draw guarantees the thread safety, though it's not a necessary condition.
+// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
+func (path *Path) Draw(t pixel.Target) {
+ path.mutex.Lock()
+ defer path.mutex.Unlock()
+
+ if path.imd == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ path.imd.Draw(t)
+}
+
+// Update animates a path. A path is drawn on an imdraw.
+func (path *Path) Update(color pixel.RGBA) {
+ var (
+ iroad = len(path.roads) - 1
+ from = path.roads[len(path.roads)-1]
+ to = path.roads[len(path.roads)-1]
+ dir = pixel.ZV
+ )
+
+ if path.isAnimating {
+ // get where it is abstract // get a scalar
+ dt := path.watchAnim.DtSinceStart()
+ const distPerSec = 500
+ scalarProgress := dt * distPerSec
+ // log.Println(dt, path.Len(), scalarProgress) //
+
+ if path.animateInTime { // overwrite scalarProgress
+ fromLot := pixel.V(0, 0)
+ toPrize := pixel.V(path.Len(), 0)
+ percentagePointPerSec := 1 / path.timeLimitAnimInSec
+ scalarProgress = pixel.Lerp(fromLot, toPrize, dt*percentagePointPerSec).X
+ // log.Println(dt, path.Len(), scalarProgress, dt*percentagePointPerSec) //
+ }
+
+ // get where it is concrete // turn a scalar into a set of vectors
+ iroad, from, to, dir = path.FindRoadByDist(scalarProgress)
+ if iroad > path.iroad {
+ if path.OnPassedEachPoint != nil {
+ go path.OnPassedEachPoint(from, dir)
+ }
+ }
+ path.iroad = iroad
+ // log.Println(iroad, len(path.roads), iroad == len(path.roads)-1, path.isAnimating) //
+ if iroad >= len(path.roads)-1 { // the end
+ path.isAnimating = false
+ // log.Println(path.Len(), dt) //
+ if path.OnFinishedAnimation != nil {
+ go path.OnFinishedAnimation()
+ } // callback
+ }
+ }
+
+ // lock before imdraw update
+ path.mutex.Lock()
+ defer path.mutex.Unlock()
+
+ // imdraw (a state machine)
+ if path.imd == nil { // lazy creation
+ path.imd = imdraw.New(nil)
+ }
+ imd := path.imd
+ imd.Clear()
+
+ // draw path
+ imd.Color = color
+ imd.EndShape = imdraw.RoundEndShape
+ for i := 0; i < iroad; i++ {
+ imd.Push(path.roads[i], path.roads[i+1])
+ imd.Line(9)
+ }
+ imd.Push(from, to)
+ imd.Line(9)
+
+ // save where the tip is
+ path.tip = &to
+}
+
+// -------------------------------------------------------------------------
+// Read only methods
+
+// IsAnimating determines whether this Path is about to be updated or not.
+// Pass lock by value warning from (path Path) should be ignored,
+// because a Path here is just passed as a read only argument.
+func (path Path) IsAnimating() bool {
+ return path.isAnimating
+}
+
+// GetPrize is just an average getter.
+// It returns -1 if the receiver is not initialized with that member(prize).
+// Pass lock by value warning from (path Path) should be ignored,
+// because a Path here is just passed as a read only argument.
+func (path Path) GetPrize() int {
+ if path.prize == nil {
+ return -1
+ }
+ return *path.prize
+}
+
+// PosTip returns a vector that tells you how far the animating path currently has reached.
+// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
+func (path Path) PosTip() (v pixel.Vec) {
+ return *path.tip
+}
+
+// Len returns the total length of all roads.
+// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
+func (path Path) Len() (sum float64) {
+ for i := 0; i < len(path.roads)-1; i++ {
+ sum += math.Abs(path.roads[i].Sub(path.roads[i+1]).Len())
+ }
+ return
+}
+
+// FindRoadByDist converts a scalar into a set of vectors.
+// A non-ptr Path as a read only argument passes lock by value within itself but that seems totally fine.
+//
+// Returns
+// iroad: The index of a road found.
+// road: The vector representation of a road found. A road is a line from pt A to B, and that vector points to where pt A is.
+// pos: A position(point) found which is in the middle of that found road(line).
+// dirVecNormalized: A direction as a normalized vector. This vector always has a length of 1.
+func (path Path) FindRoadByDist(distProgress float64) (iroad int, road pixel.Vec, pos pixel.Vec, dirVecNormalized pixel.Vec) {
+ lengthOfTraveledRoads := float64(0.0)
+ for iroad = 0; iroad < len(path.roads)-1; iroad++ {
+ var lengthOfThisRoad float64
+ iroadNext := iroad + 1
+ lengthOfThisRoad = math.Abs(path.roads[iroad].Sub(path.roads[iroadNext]).Len())
+ lengthOfTraveledRoads += lengthOfThisRoad
+ // For loop breaker: distProgress is somewhere between the total length of a path.
+ if lengthOfTraveledRoads > distProgress {
+ scalar := lengthOfThisRoad - (lengthOfTraveledRoads - distProgress)
+ if path.roads[iroad].Y == path.roads[iroadNext].Y &&
+ path.roads[iroad].X < path.roads[iroadNext].X { // to the bottom (east)
+ pos = path.roads[iroad]
+ pos.X += scalar
+ dirVecNormalized = pixel.V(1, 0)
+ } else if path.roads[iroad].X == path.roads[iroadNext].X &&
+ path.roads[iroad].Y > path.roads[iroadNext].Y { // to the left (south)
+ pos = path.roads[iroad]
+ pos.Y -= scalar
+ dirVecNormalized = pixel.V(0, -1)
+ } else if path.roads[iroad].X == path.roads[iroadNext].X &&
+ path.roads[iroad].Y < path.roads[iroadNext].Y { // to the right (north)
+ pos = path.roads[iroad]
+ pos.Y += scalar
+ dirVecNormalized = pixel.V(0, 1)
+ } else if path.roads[iroad].Y == path.roads[iroadNext].Y &&
+ path.roads[iroad].X > path.roads[iroadNext].X { // to the top (west)
+ // Placed at the end of an elif statement,
+ // since this case is of no possibility unless the path finding is going reverse.
+ pos = path.roads[iroad]
+ pos.X -= scalar
+ dirVecNormalized = pixel.V(-1, 0)
+ } else {
+ panic("unhandled exception: it may be a diagonal bridge")
+ } // elif
+ return iroad, path.roads[iroad], pos, dirVecNormalized
+ } // if - for loop breaker
+ } // for
+
+ // coming down to here means that the case is (road == pos)
+ from := iroad - 1
+ to := iroad
+ if iroad == 0 {
+ from = iroad
+ to = iroad + 1
+ }
+ if from < 0 || to >= len(path.roads) {
+ dirVecNormalized = pixel.ZV
+ } else {
+ dirVecNormalized = gg.Direction(path.roads[from], path.roads[to])
+ }
+ return iroad, path.roads[iroad], path.roads[iroad], dirVecNormalized
+}
+
+// -------------------------------------------------------------------------
+// Methods that write to itself
+
+// Animate a path.
+func (path *Path) Animate() {
+ path.watchAnim.Start()
+ path.animateInTime = false
+ path.isAnimating = true
+}
+
+// AnimateInTime animates a path in given time.
+func (path *Path) AnimateInTime(sec float64) {
+ path.watchAnim.Start()
+ path.timeLimitAnimInSec = sec
+ path.animateInTime = true
+ path.isAnimating = true
+}
+
+// Pause a path's clock.
+func (path *Path) Pause() {
+ if path.watchAnim.IsStarted() {
+ path.watchAnim.Dt()
+ }
+}
+
+// Resume after pause.
+func (path *Path) Resume() {
+ if path.watchAnim.IsStarted() {
+ started := path.watchAnim.GetTimeStarted()
+ dtPause := path.watchAnim.DtNano()
+ path.watchAnim.SetTimeStarted(started.Add(dtPause))
+ // log.Println(dtPause, started, path.watchAnim.GetTimeStarted()) //
+ }
+}
diff --git a/examples/community/amidakuji/scalpel.go b/examples/community/amidakuji/scalpel.go
new file mode 100644
index 0000000..97ad6dd
--- /dev/null
+++ b/examples/community/amidakuji/scalpel.go
@@ -0,0 +1,118 @@
+package main
+
+import (
+ "image/color"
+ "sync"
+
+ gg "github.com/faiface/pixel-examples/community/amidakuji/glossary"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "golang.org/x/image/colornames"
+)
+
+// Scalpel is a surgical knife for dissection and surgery. And for debugging purposes sometimes.
+type Scalpel struct {
+ imd *imdraw.IMDraw // shared variable
+ mutex sync.Mutex // synchronize
+}
+
+// Draw guarantees the thread safety, though it's not a necessary condition.
+// It is quite dangerous to access this struct's member (imdraw) directly from outside these methods.
+func (s *Scalpel) Draw(t pixel.Target) {
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ if s.imd == nil { // isInvisible set to true.
+ return // An empty image is drawn.
+ }
+
+ s.imd.Draw(t)
+}
+
+// Update dissects a ladder. The anatomy of a ladder is drawn on an imdraw.
+// A non-ptr Ladder as a read only argument passes lock by value within itself but that seems totally fine.
+func (s *Scalpel) Update(l Ladder) {
+ ptsEnd := l.PtsAtLevelOfPrizes()
+
+ // lock shared imdraw access
+ s.mutex.Lock()
+ defer s.mutex.Unlock()
+
+ // imdraw (a state machine)
+ if s.imd == nil { // lazy creation
+ s.imd = imdraw.New(nil)
+ }
+ imd := s.imd
+ imd.Clear()
+
+ // draw bounds
+ imd.Color = colornames.Black
+ imd.EndShape = imdraw.NoEndShape
+ imd.Push(gg.VerticesOfRect(l.bound)...)
+ imd.Polygon(4)
+
+ // draw end points
+ imd.Color = colornames.Blueviolet
+ imd.Push(ptsEnd...)
+ imd.Circle(10, 0)
+
+ // draw grid
+ imd.Color = colornames.Red
+ for nrow, row := range l.grid {
+ for ncol := range row {
+ imd.Push(l.grid[nrow][ncol])
+ }
+ }
+ imd.Circle(5, 0)
+}
+
+// -------------------------------------------------------------------------
+
+// UpdateDrawProjekt has nothing to do with scalpel.
+func UpdateDrawProjekt(t pixel.Target, rekt pixel.Rect, color color.Color, matrix pixel.Matrix) {
+ imd := imdraw.New(nil)
+
+ imd.Color = color
+ imd.EndShape = imdraw.NoEndShape
+ vertices := gg.VerticesOfRect(rekt)
+ for i, v := range vertices {
+ vertices[i] = matrix.Project(v)
+ }
+ imd.Push(vertices...)
+ imd.Polygon(10)
+
+ imd.Draw(t)
+}
+
+// UpdateDrawUnprojekt has nothing to do with scalpel.
+func UpdateDrawUnprojekt(t pixel.Target, rekt pixel.Rect, color color.Color, matrix pixel.Matrix) {
+ imd := imdraw.New(nil)
+
+ imd.Color = color
+ imd.EndShape = imdraw.NoEndShape
+ vertices := gg.VerticesOfRect(rekt)
+ for i, v := range vertices {
+ vertices[i] = matrix.Unproject(v)
+ }
+ imd.Push(vertices...)
+ imd.Polygon(10)
+
+ imd.Draw(t)
+}
+
+// UpdateDrawUnprojekt2 has nothing to do with scalpel.
+func UpdateDrawUnprojekt2(t pixel.Target, rekt pixel.Rect, color color.Color, camera gg.Camera) {
+ imd := imdraw.New(nil)
+
+ imd.Color = color
+ imd.EndShape = imdraw.NoEndShape
+ vertices := gg.VerticesOfRect(rekt)
+ for i, v := range vertices {
+ vertices[i] = camera.Unproject(v)
+ }
+ imd.Push(vertices...)
+ imd.Polygon(10)
+
+ imd.Draw(t)
+}
diff --git a/examples/community/bouncing/README.md b/examples/community/bouncing/README.md
new file mode 100644
index 0000000..cb1c7f1
--- /dev/null
+++ b/examples/community/bouncing/README.md
@@ -0,0 +1,16 @@
+# bouncing
+
+Bouncing particles using the [imdraw](https://godoc.org/github.com/faiface/pixel/imdraw) package.
+
+Made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments)
+
+## Screenshots
+
+![bouncing animation](https://user-images.githubusercontent.com/565124/32401910-7cd87fb2-c119-11e7-8121-7fb46e5e11a8.gif)
+
+![bouncing screenshot](screenshot.png)
+
+## Links
+
+ - https://github.com/peterhellberg/pixel-experiments/tree/master/bouncing
+ - https://gist.github.com/peterhellberg/674f32a15a7d2d249e634ce781f333e8
diff --git a/examples/community/bouncing/bouncing.go b/examples/community/bouncing/bouncing.go
new file mode 100644
index 0000000..b2b43f2
--- /dev/null
+++ b/examples/community/bouncing/bouncing.go
@@ -0,0 +1,303 @@
+package main
+
+import (
+ "image/color"
+ "math"
+ "math/rand"
+ "time"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+var (
+ w, h, s, scale = float64(640), float64(360), float64(2.3), float64(32)
+
+ p, bg = newPalette(Colors), color.RGBA{32, p.color().G, 32, 255}
+
+ balls = []*ball{
+ newRandomBall(scale),
+ newRandomBall(scale),
+ }
+)
+
+func run() {
+ win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Bounds: pixel.R(0, 0, w, h),
+ VSync: true,
+ Undecorated: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ imd := imdraw.New(nil)
+
+ imd.EndShape = imdraw.RoundEndShape
+ imd.Precision = 3
+
+ go func() {
+ start := time.Now()
+
+ for range time.Tick(16 * time.Millisecond) {
+ bg = color.RGBA{32 + (p.color().R/128)*4, 32 + (p.color().G/128)*4, 32 + (p.color().B/128)*4, 255}
+ s = pixel.V(math.Sin(time.Since(start).Seconds())*0.8, 0).Len()*2 - 1
+ scale = 64 + 15*s
+ imd.Intensity = 1.2 * s
+ }
+ }()
+
+ for !win.Closed() {
+ win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
+
+ if win.JustPressed(pixelgl.KeySpace) {
+ for _, ball := range balls {
+ ball.color = ball.palette.next()
+ }
+ }
+
+ if win.JustPressed(pixelgl.KeyEnter) {
+ for _, ball := range balls {
+ ball.pos = center()
+ ball.vel = randomVelocity()
+ }
+ }
+
+ imd.Clear()
+
+ for _, ball := range balls {
+ imd.Color = ball.color
+ imd.Push(ball.pos)
+ }
+
+ imd.Polygon(scale)
+
+ for _, ball := range balls {
+ imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128 - uint8(128*s)}
+ imd.Push(ball.pos)
+ }
+
+ imd.Polygon(scale * s)
+
+ for _, ball := range balls {
+ aliveParticles := []*particle{}
+
+ for _, particle := range ball.particles {
+ if particle.life > 0 {
+ aliveParticles = append(aliveParticles, particle)
+ }
+ }
+
+ for _, particle := range aliveParticles {
+ imd.Color = particle.color
+ imd.Push(particle.pos)
+ imd.Circle(16*particle.life, 0)
+ }
+ }
+
+ win.Clear(bg)
+ imd.Draw(win)
+ win.Update()
+ }
+}
+
+func main() {
+ rand.Seed(4)
+
+ go func() {
+ for range time.Tick(32 * time.Millisecond) {
+ for _, ball := range balls {
+ go ball.update()
+
+ for _, particle := range ball.particles {
+ go particle.update()
+ }
+ }
+ }
+ }()
+
+ pixelgl.Run(run)
+}
+
+func newParticleAt(pos, vel pixel.Vec) *particle {
+ c := p.color()
+ c.A = 5
+
+ return &particle{pos, vel, c, rand.Float64() * 1.5}
+}
+
+func newRandomBall(radius float64) *ball {
+ return &ball{
+ center(), randomVelocity(),
+ math.Pi * (radius * radius),
+ radius, p.random(), p, []*particle{},
+ }
+}
+
+func center() pixel.Vec {
+ return pixel.V(w/2, h/2)
+}
+
+func randomVelocity() pixel.Vec {
+ return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 4)
+}
+
+type particle struct {
+ pos pixel.Vec
+ vel pixel.Vec
+ color color.RGBA
+ life float64
+}
+
+func (p *particle) update() {
+ p.pos = p.pos.Add(p.vel)
+ p.life -= 0.03
+
+ switch {
+ case p.pos.Y < 0 || p.pos.Y >= h:
+ p.vel.Y *= -1.0
+ case p.pos.X < 0 || p.pos.X >= w:
+ p.vel.X *= -1.0
+ }
+}
+
+type ball struct {
+ pos pixel.Vec
+ vel pixel.Vec
+ mass float64
+ radius float64
+ color color.RGBA
+ palette *Palette
+ particles []*particle
+}
+
+func (b *ball) update() {
+ b.pos = b.pos.Add(b.vel)
+
+ var bounced bool
+
+ switch {
+ case b.pos.Y <= b.radius || b.pos.Y >= h-b.radius:
+ b.vel.Y *= -1.0
+ bounced = true
+
+ if b.pos.Y < b.radius {
+ b.pos.Y = b.radius
+ } else {
+ b.pos.Y = h - b.radius
+ }
+ case b.pos.X <= b.radius || b.pos.X >= w-b.radius:
+ b.vel.X *= -1.0
+ bounced = true
+
+ if b.pos.X < b.radius {
+ b.pos.X = b.radius
+ } else {
+ b.pos.X = w - b.radius
+ }
+ }
+
+ for _, a := range balls {
+ if a != b {
+ d := a.pos.Sub(b.pos)
+
+ if d.Len() > a.radius+b.radius {
+ continue
+ }
+
+ pen := d.Unit().Scaled(a.radius + b.radius - d.Len())
+
+ a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass)))
+ b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass)))
+
+ u := d.Unit()
+ v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass)
+
+ a.vel = a.vel.Sub(u.Scaled(v * b.mass))
+ b.vel = b.vel.Add(u.Scaled(v * a.mass))
+
+ bounced = true
+ }
+ }
+
+ if bounced {
+ b.color = p.next()
+ b.particles = append(b.particles,
+ newParticleAt(b.pos, b.vel.Rotated(1).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(2).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(3).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(4).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(5).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(6).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(7).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(8).Scaled(rand.Float64())),
+ newParticleAt(b.pos, b.vel.Rotated(9).Scaled(rand.Float64())),
+
+ newParticleAt(b.pos, b.vel.Rotated(10).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(20).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(30).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(40).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(50).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(60).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(70).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(80).Scaled(rand.Float64()+1)),
+ newParticleAt(b.pos, b.vel.Rotated(90).Scaled(rand.Float64()+1)),
+ )
+ }
+}
+
+func newPalette(cc []color.Color) *Palette {
+ colors := []color.RGBA{}
+
+ for _, v := range cc {
+ if c, ok := v.(color.RGBA); ok {
+ colors = append(colors, c)
+ }
+ }
+
+ return &Palette{colors, len(colors), 0}
+}
+
+type Palette struct {
+ colors []color.RGBA
+ size int
+ index int
+}
+
+func (p *Palette) clone() *Palette {
+ return &Palette{p.colors, p.size, p.index}
+}
+
+func (p *Palette) next() color.RGBA {
+ if p.index++; p.index >= p.size {
+ p.index = 0
+ }
+
+ return p.colors[p.index]
+}
+
+func (p *Palette) color() color.RGBA {
+ return p.colors[p.index]
+}
+
+func (p *Palette) random() color.RGBA {
+ p.index = rand.Intn(p.size)
+
+ return p.colors[p.index]
+}
+
+var Colors = []color.Color{
+ color.RGBA{190, 38, 51, 255},
+ color.RGBA{224, 111, 139, 255},
+ color.RGBA{73, 60, 43, 255},
+ color.RGBA{164, 100, 34, 255},
+ color.RGBA{235, 137, 49, 255},
+ color.RGBA{247, 226, 107, 255},
+ color.RGBA{47, 72, 78, 255},
+ color.RGBA{68, 137, 26, 255},
+ color.RGBA{163, 206, 39, 255},
+ color.RGBA{0, 87, 132, 255},
+ color.RGBA{49, 162, 242, 255},
+ color.RGBA{178, 220, 239, 255},
+}
diff --git a/examples/community/bouncing/screenshot.png b/examples/community/bouncing/screenshot.png
new file mode 100644
index 0000000..72d0fc0
Binary files /dev/null and b/examples/community/bouncing/screenshot.png differ
diff --git a/examples/community/game_of_life/README.md b/examples/community/game_of_life/README.md
new file mode 100644
index 0000000..92d1aad
--- /dev/null
+++ b/examples/community/game_of_life/README.md
@@ -0,0 +1,20 @@
+# Conway's Game of Lfe
+
+Created by [Nathan Leniz](https://github.com/terakilobyte).
+Inspired by and heavily uses [the doc](https://golang.org/doc/play/life.go)
+
+> The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves, or, for advanced "players", by creating patterns with particular properties. The Game has been reprogrammed multiple times in various coding languages.
+
+For more information, please see the [wikipedia](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) article.
+
+## Use
+
+ go run main.go -h
+ -frameRate duration
+ The framerate in milliseconds (default 33ms)
+ -size int
+ The size of each cell (default 5)
+ -windowSize float
+ The pixel size of one side of the grid (default 800)
+
+![life](life.png)
diff --git a/examples/community/game_of_life/life.png b/examples/community/game_of_life/life.png
new file mode 100644
index 0000000..5b8807a
Binary files /dev/null and b/examples/community/game_of_life/life.png differ
diff --git a/examples/community/game_of_life/life/grid.go b/examples/community/game_of_life/life/grid.go
new file mode 100644
index 0000000..e7b0a14
--- /dev/null
+++ b/examples/community/game_of_life/life/grid.go
@@ -0,0 +1,74 @@
+package life
+
+import (
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "golang.org/x/image/colornames"
+)
+
+// Shamelessly taken/inspired by https://golang.org/doc/play/life.go
+// Grid is the structure in which the cellular automota live
+type Grid struct {
+ h int
+ cellSize int
+ Cells [][]bool
+}
+
+// NewGrid constructs a new Grid
+func NewGrid(h, size int) *Grid {
+ cells := make([][]bool, h)
+ for i := 0; i < h; i++ {
+ cells[i] = make([]bool, h)
+ }
+ return &Grid{h: h, cellSize: size, Cells: cells}
+}
+
+// Alive returns whether the specified position is alive
+func (g *Grid) Alive(x, y int) bool {
+ x += g.h
+ x %= g.h
+ y += g.h
+ y %= g.h
+ return g.Cells[y][x]
+}
+
+// Set sets the state of a specific location
+func (g *Grid) Set(x, y int, state bool) {
+ g.Cells[y][x] = state
+}
+
+// Draw draws the grid
+func (g *Grid) Draw(imd *imdraw.IMDraw) {
+ for i := 0; i < g.h; i++ {
+ for j := 0; j < g.h; j++ {
+ if g.Alive(i, j) {
+ imd.Color = colornames.Black
+ } else {
+ imd.Color = colornames.White
+ }
+ imd.Push(
+ pixel.V(float64(i*g.cellSize), float64(j*g.cellSize)),
+ pixel.V(float64(i*g.cellSize+g.cellSize), float64(j*g.cellSize+g.cellSize)),
+ )
+ imd.Rectangle(0)
+ }
+ }
+}
+
+// Next returns the next state
+func (g *Grid) Next(x, y int) bool {
+ // Count the adjacent cells that are alive.
+ alive := 0
+ for i := -1; i <= 1; i++ {
+ for j := -1; j <= 1; j++ {
+ if (j != 0 || i != 0) && g.Alive(x+i, y+j) {
+ alive++
+ }
+ }
+ }
+ // Return next state according to the game rules:
+ // exactly 3 neighbors: on,
+ // exactly 2 neighbors: maintain current state,
+ // otherwise: off.
+ return alive == 3 || alive == 2 && g.Alive(x, y)
+}
diff --git a/examples/community/game_of_life/life/life.go b/examples/community/game_of_life/life/life.go
new file mode 100644
index 0000000..26d2823
--- /dev/null
+++ b/examples/community/game_of_life/life/life.go
@@ -0,0 +1,35 @@
+// Package life manages the "game" state
+// Shamelessly taken from https://golang.org/doc/play/life.go
+package life
+
+import "math/rand"
+
+// Life stores the state of a round of Conway's Game of Life.
+type Life struct {
+ A, b *Grid
+ h int
+}
+
+// NewLife returns a new Life game state with a random initial state.
+func NewLife(h, size int) *Life {
+ a := NewGrid(h, size)
+ for i := 0; i < (h * h / 2); i++ {
+ a.Set(rand.Intn(h), rand.Intn(h), true)
+ }
+ return &Life{
+ A: a, b: NewGrid(h, size),
+ h: h,
+ }
+}
+
+// Step advances the game by one instant, recomputing and updating all cells.
+func (l *Life) Step() {
+ // Update the state of the next field (b) from the current field (a).
+ for y := 0; y < l.h; y++ {
+ for x := 0; x < l.h; x++ {
+ l.b.Set(x, y, l.A.Next(x, y))
+ }
+ }
+ // Swap fields a and b.
+ l.A, l.b = l.b, l.A
+}
diff --git a/examples/community/game_of_life/main.go b/examples/community/game_of_life/main.go
new file mode 100644
index 0000000..aef7c31
--- /dev/null
+++ b/examples/community/game_of_life/main.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "flag"
+ "math/rand"
+ "time"
+
+ "golang.org/x/image/colornames"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel-examples/community/game_of_life/life"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+var (
+ size *int
+ windowSize *float64
+ frameRate *time.Duration
+)
+
+func init() {
+ rand.Seed(time.Now().UnixNano())
+ size = flag.Int("size", 5, "The size of each cell")
+ windowSize = flag.Float64("windowSize", 800, "The pixel size of one side of the grid")
+ frameRate = flag.Duration("frameRate", 33*time.Millisecond, "The framerate in milliseconds")
+ flag.Parse()
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func run() {
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, *windowSize, *windowSize),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ win.Clear(colornames.White)
+
+ // since the game board is square, rows and cols will be the same
+ rows := int(*windowSize) / *size
+
+ gridDraw := imdraw.New(nil)
+ game := life.NewLife(rows, *size)
+ tick := time.Tick(*frameRate)
+ for !win.Closed() {
+ // game loop
+ select {
+ case <-tick:
+ gridDraw.Clear()
+ game.A.Draw(gridDraw)
+ gridDraw.Draw(win)
+ game.Step()
+ }
+ win.Update()
+ }
+}
diff --git a/examples/community/go-jetpack/README.md b/examples/community/go-jetpack/README.md
new file mode 100644
index 0000000..2f4eb31
--- /dev/null
+++ b/examples/community/go-jetpack/README.md
@@ -0,0 +1,7 @@
+# Go Jetpack
+by [Branson Camp](https://github.com/bcamp1) using [Pixel](https://github.com/faiface/pixel)
+
+Welcome to Go-Jetpack, where you explore the skies using WASD or Arrow Keys.
+This example showcases most of the fundamental pixel elements described in the [Pixel Wiki](https://github.com/faiface/pixel/wiki)
+
+![Screenshot](https://github.com/bcamp1/pixel/blob/master/examples/community/go-jetpack/screenshot.png)
diff --git a/examples/community/go-jetpack/intuitive.ttf b/examples/community/go-jetpack/intuitive.ttf
new file mode 100644
index 0000000..9039d7b
Binary files /dev/null and b/examples/community/go-jetpack/intuitive.ttf differ
diff --git a/examples/community/go-jetpack/jetpack-on.png b/examples/community/go-jetpack/jetpack-on.png
new file mode 100644
index 0000000..2df103f
Binary files /dev/null and b/examples/community/go-jetpack/jetpack-on.png differ
diff --git a/examples/community/go-jetpack/jetpack-on2.png b/examples/community/go-jetpack/jetpack-on2.png
new file mode 100644
index 0000000..2bfb514
Binary files /dev/null and b/examples/community/go-jetpack/jetpack-on2.png differ
diff --git a/examples/community/go-jetpack/jetpack.go b/examples/community/go-jetpack/jetpack.go
new file mode 100644
index 0000000..17c7e1e
--- /dev/null
+++ b/examples/community/go-jetpack/jetpack.go
@@ -0,0 +1,208 @@
+package main
+
+import (
+ "fmt"
+ "image"
+ "io/ioutil"
+ "os"
+
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "github.com/faiface/pixel/text"
+ "github.com/golang/freetype/truetype"
+ "golang.org/x/image/colornames"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func loadSprite(path string) (pixel.Sprite, error) {
+ pic, err := loadPicture(path)
+ if err != nil {
+ return *pixel.NewSprite(pic, pic.Bounds()), err
+ }
+ sprite := pixel.NewSprite(pic, pic.Bounds())
+ return *sprite, nil
+}
+
+func loadTTF(path string, size float64, origin pixel.Vec) *text.Text {
+ file, err := os.Open(path)
+ if err != nil {
+ panic(err)
+ }
+ defer file.Close()
+
+ bytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ panic(err)
+ }
+
+ font, err := truetype.Parse(bytes)
+ if err != nil {
+ panic(err)
+ }
+
+ face := truetype.NewFace(font, &truetype.Options{
+ Size: size,
+ GlyphCacheEntries: 1,
+ })
+
+ atlas := text.NewAtlas(face, text.ASCII)
+
+ txt := text.New(origin, atlas)
+
+ return txt
+
+}
+
+func run() {
+ // Set up window configs
+ cfg := pixelgl.WindowConfig{ // Default: 1024 x 768
+ Title: "Golang Jetpack!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ // Importantr variables
+ var jetX, jetY, velX, velY, radians float64
+ flipped := 1.0
+ jetpackOn := false
+ gravity := 0.6 // Default: 0.004
+ jetAcc := 0.9 // Default: 0.008
+ tilt := 0.01 // Default: 0.001
+ whichOn := false
+ onNumber := 0
+ jetpackOffName := "jetpack.png"
+ jetpackOn1Name := "jetpack-on.png"
+ jetpackOn2Name := "jetpack-on2.png"
+ camVector := win.Bounds().Center()
+
+ bg, _ := loadSprite("sky.png")
+
+ // Jetpack - Rendering
+ jetpackOff, err := loadSprite(jetpackOffName)
+ if err != nil {
+ panic(err)
+ }
+ jetpackOn1, err := loadSprite(jetpackOn1Name)
+ if err != nil {
+ panic(err)
+ }
+ jetpackOn2, err := loadSprite(jetpackOn2Name)
+ if err != nil {
+ panic(err)
+ }
+
+ // Tutorial Text
+ txt := loadTTF("intuitive.ttf", 50, pixel.V(win.Bounds().Center().X-450, win.Bounds().Center().Y-200))
+ fmt.Fprintf(txt, "Explore the Skies with WASD or Arrow Keys!")
+
+ currentSprite := jetpackOff
+
+ // Game Loop
+ for !win.Closed() {
+ win.Update()
+ win.Clear(colornames.Green)
+
+ // Jetpack - Controls
+ jetpackOn = win.Pressed(pixelgl.KeyUp) || win.Pressed(pixelgl.KeyW)
+
+ if win.Pressed(pixelgl.KeyRight) || win.Pressed(pixelgl.KeyD) {
+ jetpackOn = true
+ flipped = -1
+ radians -= tilt
+ velX += tilt * 30
+ } else if win.Pressed(pixelgl.KeyLeft) || win.Pressed(pixelgl.KeyA) {
+ jetpackOn = true
+ flipped = 1
+ radians += tilt
+ velX -= tilt * 30
+ } else {
+ if velX < 0 {
+ radians -= tilt / 3
+ velX += tilt * 10
+ } else if velX > 0 {
+ radians += tilt / 3
+ velX -= tilt * 10
+ }
+ }
+ if jetY < 0 {
+ jetY = 0
+ velY = -0.3 * velY
+ }
+
+ if jetpackOn {
+ velY += jetAcc
+ whichOn = !whichOn
+ onNumber++
+ if onNumber == 5 { // every 5 frames, toggle anijetMation
+ onNumber = 0
+ if whichOn {
+ currentSprite = jetpackOn1
+ } else {
+ currentSprite = jetpackOn2
+ }
+ }
+ } else {
+ currentSprite = jetpackOff
+ velY -= gravity
+ }
+
+ positionVector := pixel.V(win.Bounds().Center().X+jetX, win.Bounds().Center().Y+jetY-372)
+ jetMat := pixel.IM
+ jetMat = jetMat.Scaled(pixel.ZV, 4)
+ jetMat = jetMat.Moved(positionVector)
+ jetMat = jetMat.ScaledXY(positionVector, pixel.V(flipped, 1))
+ jetMat = jetMat.Rotated(positionVector, radians)
+
+ jetX += velX
+ jetY += velY
+
+ // Camera
+ camVector.X += (positionVector.X - camVector.X) * 0.2
+ camVector.Y += (positionVector.Y - camVector.Y) * 0.2
+
+ if camVector.X > 25085 {
+ camVector.X = 25085
+ } else if camVector.X < -14843 {
+ camVector.X = -14843
+ }
+
+ if camVector.Y > 22500 {
+ camVector.Y = 22500
+ }
+
+ cam := pixel.IM.Moved(win.Bounds().Center().Sub(camVector))
+
+ win.SetMatrix(cam)
+
+ // Drawing to the screen
+ win.SetSmooth(true)
+ bg.Draw(win, pixel.IM.Moved(pixel.V(win.Bounds().Center().X, win.Bounds().Center().Y+766)).Scaled(pixel.ZV, 10))
+ txt.Draw(win, pixel.IM)
+ win.SetSmooth(false)
+ currentSprite.Draw(win, jetMat)
+
+ }
+
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/go-jetpack/jetpack.png b/examples/community/go-jetpack/jetpack.png
new file mode 100644
index 0000000..4ea39e0
Binary files /dev/null and b/examples/community/go-jetpack/jetpack.png differ
diff --git a/examples/community/go-jetpack/screenshot.png b/examples/community/go-jetpack/screenshot.png
new file mode 100644
index 0000000..a41e269
Binary files /dev/null and b/examples/community/go-jetpack/screenshot.png differ
diff --git a/examples/community/go-jetpack/sky.png b/examples/community/go-jetpack/sky.png
new file mode 100644
index 0000000..ab5fa36
Binary files /dev/null and b/examples/community/go-jetpack/sky.png differ
diff --git a/examples/community/gophermark/README.md b/examples/community/gophermark/README.md
new file mode 100644
index 0000000..bf029fe
--- /dev/null
+++ b/examples/community/gophermark/README.md
@@ -0,0 +1,15 @@
+# Gophermark
+
+Simple Bunnymark for Pixel featuring the Gopher. For improvements just make your own pull request or write me a message.
+
+Made by [David Linus Briemann](https://github.com/dbriemann/).
+
+## Controls
+
+ESC - Quit
+
+Press left mouse button - add many gophers.
+
+## Screenie
+
+![screenshot](screen.jpg)
\ No newline at end of file
diff --git a/examples/community/gophermark/gopher.png b/examples/community/gophermark/gopher.png
new file mode 100644
index 0000000..5dbdb76
Binary files /dev/null and b/examples/community/gophermark/gopher.png differ
diff --git a/examples/community/gophermark/gophermark.go b/examples/community/gophermark/gophermark.go
new file mode 100644
index 0000000..5c3c6a9
--- /dev/null
+++ b/examples/community/gophermark/gophermark.go
@@ -0,0 +1,131 @@
+package main
+
+import (
+ "fmt"
+ "image"
+ "math"
+ "math/rand"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+type gopher struct {
+ pos pixel.Vec
+ vel pixel.Vec
+ // angle float32
+}
+
+// newGopher creates a gopher with given position and random velocity.
+func newGopher(p pixel.Vec) gopher {
+ g := gopher{
+ pos: p,
+ }
+ v := pixel.V(1, 0)
+ v = v.Rotated(rand.Float64() * 2 * math.Pi)
+ g.vel = v.Scaled(rand.Float64()*100 + 50)
+ return g
+}
+
+// update updates the gophers position and ensures he stays on screen.
+func (g *gopher) update(dt float64) {
+ g.pos = g.pos.Add(g.vel.Scaled(dt))
+ if g.pos.X <= pic.Bounds().W()/2 {
+ g.vel.X *= -1
+ } else if g.pos.X > win.Bounds().Max.X-pic.Bounds().W()/2 {
+ g.vel.X *= -1
+ }
+ if g.pos.Y <= pic.Bounds().H()/2 {
+ g.vel.Y *= -1
+ } else if g.pos.Y > win.Bounds().Max.Y-pic.Bounds().H()/2 {
+ g.vel.Y *= -1
+ }
+}
+
+var (
+ win *pixelgl.Window
+ pic pixel.Picture
+ sprite *pixel.Sprite
+ gophers []gopher
+ frames = 0
+ second = time.Tick(time.Second)
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ rand.Seed(time.Now().UnixNano())
+ cfg := pixelgl.WindowConfig{
+ Title: "Gophermark",
+ Bounds: pixel.R(0, 0, 1000, 800),
+ }
+ w, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ win = w
+
+ p, err := loadPicture("gopher.png")
+ if err != nil {
+ panic(err)
+ }
+ pic = p
+
+ batch := pixel.NewBatch(&pixel.TrianglesData{}, pic)
+ sprite = pixel.NewSprite(pic, pic.Bounds())
+
+ for i := 0; i < 1000; i++ {
+ gophers = append(gophers, newGopher(pixel.V(win.Bounds().W()/2, win.Bounds().H()/2)))
+ }
+
+ last := time.Now()
+ for !win.Closed() && !win.JustPressed(pixelgl.KeyEscape) {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ if win.Pressed(pixelgl.MouseButtonLeft) {
+ mouse := win.MousePosition()
+ for i := 0; i < 10; i++ {
+ gophers = append(gophers, newGopher(mouse))
+ }
+ }
+
+ batch.Clear()
+ for i := 0; i < len(gophers); i++ {
+ gophers[i].update(dt)
+ sprite.Draw(batch, pixel.IM.Moved(gophers[i].pos))
+ }
+
+ win.Clear(colornames.Black)
+ batch.Draw(win)
+ win.Update()
+
+ frames++
+ select {
+ case <-second:
+ win.SetTitle(fmt.Sprintf("%s | Gophers: %d | FPS: %d", cfg.Title, len(gophers), frames))
+ frames = 0
+ default:
+ }
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/gophermark/screen.jpg b/examples/community/gophermark/screen.jpg
new file mode 100644
index 0000000..a875cfb
Binary files /dev/null and b/examples/community/gophermark/screen.jpg differ
diff --git a/examples/community/isometric-basics/README.md b/examples/community/isometric-basics/README.md
new file mode 100644
index 0000000..035ef05
--- /dev/null
+++ b/examples/community/isometric-basics/README.md
@@ -0,0 +1,13 @@
+# Isometric view basics
+
+Created by [Sergio Vera](https://github.com/svera).
+
+Isometric view is a display method used to create an illusion of 3D for an otherwise 2D game - sometimes referred to as pseudo 3D or 2.5D.
+
+Implementing an isometric view can be done in many ways, but for the sake of simplicity we'll implement a tile-based approach, which is the most efficient and widely used method.
+
+In the tile-based approach, each visual element is broken down into smaller pieces, called tiles, of a standard size. These tiles will be arranged to form the game world according to pre-determined level data - usually a 2D array.
+
+For a detailed explanation about the maths behind this, read [http://clintbellanger.net/articles/isometric_math/](http://clintbellanger.net/articles/isometric_math/).
+
+![Result](result.png)
diff --git a/examples/community/isometric-basics/castle.png b/examples/community/isometric-basics/castle.png
new file mode 100644
index 0000000..190fd9e
Binary files /dev/null and b/examples/community/isometric-basics/castle.png differ
diff --git a/examples/community/isometric-basics/main.go b/examples/community/isometric-basics/main.go
new file mode 100644
index 0000000..246f9e4
--- /dev/null
+++ b/examples/community/isometric-basics/main.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+ "image"
+ "os"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+
+ _ "image/png"
+)
+
+const (
+ windowWidth = 800
+ windowHeight = 800
+ // sprite tiles are squared, 64x64 size
+ tileSize = 64
+ f = 0 // floor identifier
+ w = 1 // wall identifier
+)
+
+var levelData = [][]uint{
+ {f, f, f, f, f, f}, // This row will be rendered in the lower left part of the screen (closer to the viewer)
+ {w, f, f, f, f, w},
+ {w, f, f, f, f, w},
+ {w, f, f, f, f, w},
+ {w, f, f, f, f, w},
+ {w, w, w, w, w, w}, // And this in the upper right
+}
+var win *pixelgl.Window
+var offset = pixel.V(400, 325)
+var floorTile, wallTile *pixel.Sprite
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ var err error
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Isometric demo",
+ Bounds: pixel.R(0, 0, windowWidth, windowHeight),
+ VSync: true,
+ }
+ win, err = pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ pic, err := loadPicture("castle.png")
+ if err != nil {
+ panic(err)
+ }
+
+ wallTile = pixel.NewSprite(pic, pixel.R(0, 448, tileSize, 512))
+ floorTile = pixel.NewSprite(pic, pixel.R(0, 128, tileSize, 192))
+
+ depthSort()
+
+ for !win.Closed() {
+ win.Update()
+ }
+}
+
+// Draw level data tiles to window, from farthest to closest.
+// In order to achieve the depth effect, we need to render tiles up to down, being lower
+// closer to the viewer (see painter's algorithm). To do that, we need to process levelData in reverse order,
+// so its first row is rendered last, as OpenGL considers its origin to be in the lower left corner of the display.
+func depthSort() {
+ for x := len(levelData) - 1; x >= 0; x-- {
+ for y := len(levelData[x]) - 1; y >= 0; y-- {
+ isoCoords := cartesianToIso(pixel.V(float64(x), float64(y)))
+ mat := pixel.IM.Moved(offset.Add(isoCoords))
+ // Not really needed, just put to show bigger blocks
+ mat = mat.ScaledXY(win.Bounds().Center(), pixel.V(2, 2))
+ tileType := levelData[x][y]
+ if tileType == f {
+ floorTile.Draw(win, mat)
+ } else {
+ wallTile.Draw(win, mat)
+ }
+ }
+ }
+}
+
+func cartesianToIso(pt pixel.Vec) pixel.Vec {
+ return pixel.V((pt.X-pt.Y)*(tileSize/2), (pt.X+pt.Y)*(tileSize/4))
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/isometric-basics/result.png b/examples/community/isometric-basics/result.png
new file mode 100644
index 0000000..5eca0fe
Binary files /dev/null and b/examples/community/isometric-basics/result.png differ
diff --git a/examples/community/line-collisions/README.md b/examples/community/line-collisions/README.md
new file mode 100644
index 0000000..399034d
--- /dev/null
+++ b/examples/community/line-collisions/README.md
@@ -0,0 +1,12 @@
+# Line Collisions
+Line collisions is a basic example of how to detect line collisions with a rectangle.
+
+![Screenshot](lineCollisionScreenshot.png)
+
+## Instructions
+You can set the rectangle position using a left-click of the mouse.
+
+You can set the line position with two right-clicks of the mouse; the first right-click will set the beginning of the
+line the second will set the end of the line.
+
+Any intersection points where the rectangle and line intersection will appear as red dots.
diff --git a/examples/community/line-collisions/lineCollisionScreenshot.png b/examples/community/line-collisions/lineCollisionScreenshot.png
new file mode 100644
index 0000000..d6af306
Binary files /dev/null and b/examples/community/line-collisions/lineCollisionScreenshot.png differ
diff --git a/examples/community/line-collisions/main.go b/examples/community/line-collisions/main.go
new file mode 100644
index 0000000..2cc0ce6
--- /dev/null
+++ b/examples/community/line-collisions/main.go
@@ -0,0 +1,89 @@
+package main
+
+import (
+ "image/color"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+// These hold the state of whether we're placing the first or second point of the line.
+const (
+ clickLineA = iota
+ clickLineB
+)
+
+var (
+ winBounds = pixel.R(0, 0, 1024, 768)
+
+ r = pixel.R(10, 10, 70, 50)
+ l = pixel.L(pixel.V(20, 20), pixel.V(100, 30))
+
+ clickLine = clickLineA
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Line collision",
+ Bounds: winBounds,
+ VSync: true,
+ }
+
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ imd := imdraw.New(nil)
+
+ for !win.Closed() {
+ win.Clear(color.RGBA{R: 23, G: 39, B: 58, A: 125})
+ imd.Clear()
+
+ // When mouse left-click, move the rectangle so its' center is at the mouse position
+ if win.JustPressed(pixelgl.MouseButtonLeft) {
+ rectToMouse := r.Center().To(win.MousePosition())
+ r = r.Moved(rectToMouse)
+ }
+
+ // When mouse right-click, set either the beginning or end of the line.
+ if win.JustPressed(pixelgl.MouseButtonRight) {
+ if clickLine == clickLineA {
+ // Set the beginning of the line to the mouse position.
+ // To make it clearer to the user, set the end position 1 pixel (in each direction) away from the first
+ // point.
+ l = pixel.L(win.MousePosition(), win.MousePosition().Add(pixel.V(1, 1)))
+ clickLine = clickLineB
+ } else {
+ // Set the end point of the line.
+ l = pixel.L(l.A, win.MousePosition())
+ clickLine = clickLineA
+ }
+ }
+
+ // Draw the rectangle.
+ imd.Color = color.Black
+ imd.Push(r.Min, r.Max)
+ imd.Rectangle(3)
+
+ // Draw the line.
+ imd.Color = color.RGBA{R: 10, G: 10, B: 250, A: 255}
+ imd.Push(l.A, l.B)
+ imd.Line(3)
+
+ imd.Color = color.RGBA{R: 250, G: 10, B: 10, A: 255}
+ // Draw any intersection points.
+ for _, i := range r.IntersectionPoints(l) {
+ imd.Push(i)
+ imd.Circle(4, 0)
+ }
+
+ imd.Draw(win)
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/maze/LICENSE b/examples/community/maze/LICENSE
new file mode 100644
index 0000000..1326e36
--- /dev/null
+++ b/examples/community/maze/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 stephen
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/community/maze/README.md b/examples/community/maze/README.md
new file mode 100644
index 0000000..27b344a
--- /dev/null
+++ b/examples/community/maze/README.md
@@ -0,0 +1,19 @@
+# Maze generator in Go
+
+Created by [Stephen Chavez](https://github.com/redragonx)
+
+This uses the game engine: Pixel. Install it here: https://github.com/faiface/pixel
+
+I made this to improve my understanding of Go and some game concepts with some basic maze generating algorithms.
+
+Controls: Press 'R' to restart the maze.
+
+Optional command-line arguments: `go run ./maze-generator.go`
+ - `-w` sets the maze's width in pixels.
+ - `-h` sets the maze's height in pixels.
+ - `-c` sets the maze cell's size in pixels.
+
+Code based on the Recursive backtracker algorithm.
+- https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/community/maze/maze-generator.go b/examples/community/maze/maze-generator.go
new file mode 100644
index 0000000..0ddbdac
--- /dev/null
+++ b/examples/community/maze/maze-generator.go
@@ -0,0 +1,317 @@
+package main
+
+// Code based on the Recursive backtracker algorithm.
+// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
+// See https://youtu.be/HyK_Q5rrcr4 as an example
+// YouTube example ported to Go for the Pixel library.
+
+// Created by Stephen Chavez
+
+import (
+ "crypto/rand"
+ "errors"
+ "flag"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel-examples/community/maze/stack"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+
+ "github.com/pkg/profile"
+ "golang.org/x/image/colornames"
+)
+
+var visitedColor = pixel.RGB(0.5, 0, 1).Mul(pixel.Alpha(0.35))
+var hightlightColor = pixel.RGB(0.3, 0, 0).Mul(pixel.Alpha(0.45))
+var debug = false
+
+type cell struct {
+ walls [4]bool // Wall order: top, right, bottom, left
+
+ row int
+ col int
+ visited bool
+}
+
+func (c *cell) Draw(imd *imdraw.IMDraw, wallSize int) {
+ drawCol := c.col * wallSize // x
+ drawRow := c.row * wallSize // y
+
+ imd.Color = colornames.White
+ if c.walls[0] {
+ // top line
+ imd.Push(pixel.V(float64(drawCol), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow)))
+ imd.Line(3)
+ }
+ if c.walls[1] {
+ // right Line
+ imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
+ imd.Line(3)
+ }
+ if c.walls[2] {
+ // bottom line
+ imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow+wallSize)))
+ imd.Line(3)
+ }
+ if c.walls[3] {
+ // left line
+ imd.Push(pixel.V(float64(drawCol), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow)))
+ imd.Line(3)
+ }
+ imd.EndShape = imdraw.SharpEndShape
+
+ if c.visited {
+ imd.Color = visitedColor
+ imd.Push(pixel.V(float64(drawCol), (float64(drawRow))), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
+ imd.Rectangle(0)
+ }
+}
+
+func (c *cell) GetNeighbors(grid []*cell, cols int, rows int) ([]*cell, error) {
+ neighbors := []*cell{}
+ j := c.row
+ i := c.col
+
+ top, _ := getCellAt(i, j-1, cols, rows, grid)
+ right, _ := getCellAt(i+1, j, cols, rows, grid)
+ bottom, _ := getCellAt(i, j+1, cols, rows, grid)
+ left, _ := getCellAt(i-1, j, cols, rows, grid)
+
+ if top != nil && !top.visited {
+ neighbors = append(neighbors, top)
+ }
+ if right != nil && !right.visited {
+ neighbors = append(neighbors, right)
+ }
+ if bottom != nil && !bottom.visited {
+ neighbors = append(neighbors, bottom)
+ }
+ if left != nil && !left.visited {
+ neighbors = append(neighbors, left)
+ }
+
+ if len(neighbors) == 0 {
+ return nil, errors.New("We checked all cells...")
+ }
+ return neighbors, nil
+}
+
+func (c *cell) GetRandomNeighbor(grid []*cell, cols int, rows int) (*cell, error) {
+ neighbors, err := c.GetNeighbors(grid, cols, rows)
+ if neighbors == nil {
+ return nil, err
+ }
+ nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors))))
+ if err != nil {
+ panic(err)
+ }
+ randomIndex := nBig.Int64()
+ return neighbors[randomIndex], nil
+}
+
+func (c *cell) hightlight(imd *imdraw.IMDraw, wallSize int) {
+ x := c.col * wallSize
+ y := c.row * wallSize
+
+ imd.Color = hightlightColor
+ imd.Push(pixel.V(float64(x), float64(y)), pixel.V(float64(x+wallSize), float64(y+wallSize)))
+ imd.Rectangle(0)
+}
+
+func newCell(col int, row int) *cell {
+ newCell := new(cell)
+ newCell.row = row
+ newCell.col = col
+
+ for i := range newCell.walls {
+ newCell.walls[i] = true
+ }
+ return newCell
+}
+
+// Creates the inital maze slice for use.
+func initGrid(cols, rows int) []*cell {
+ grid := []*cell{}
+ for j := 0; j < rows; j++ {
+ for i := 0; i < cols; i++ {
+ newCell := newCell(i, j)
+ grid = append(grid, newCell)
+ }
+ }
+ return grid
+}
+
+func setupMaze(cols, rows int) ([]*cell, *stack.Stack, *cell) {
+ // Make an empty grid
+ grid := initGrid(cols, rows)
+ backTrackStack := stack.NewStack(len(grid))
+ currentCell := grid[0]
+
+ return grid, backTrackStack, currentCell
+}
+
+func cellIndex(i, j, cols, rows int) int {
+ if i < 0 || j < 0 || i > cols-1 || j > rows-1 {
+ return -1
+ }
+ return i + j*cols
+}
+
+func getCellAt(i int, j int, cols int, rows int, grid []*cell) (*cell, error) {
+ possibleIndex := cellIndex(i, j, cols, rows)
+
+ if possibleIndex == -1 {
+ return nil, fmt.Errorf("cellIndex: CellIndex is a negative number %d", possibleIndex)
+ }
+ return grid[possibleIndex], nil
+}
+
+func removeWalls(a *cell, b *cell) {
+ x := a.col - b.col
+
+ if x == 1 {
+ a.walls[3] = false
+ b.walls[1] = false
+ } else if x == -1 {
+ a.walls[1] = false
+ b.walls[3] = false
+ }
+
+ y := a.row - b.row
+
+ if y == 1 {
+ a.walls[0] = false
+ b.walls[2] = false
+ } else if y == -1 {
+ a.walls[2] = false
+ b.walls[0] = false
+ }
+}
+
+func run() {
+ // unsiged integers, because easier parsing error checks.
+ // We must convert these to intergers, as done below...
+ uScreenWidth, uScreenHeight, uWallSize := parseArgs()
+
+ var (
+ // In pixels
+ // Defualt is 800x800x40 = 20x20 wallgrid
+ screenWidth = int(uScreenWidth)
+ screenHeight = int(uScreenHeight)
+ wallSize = int(uWallSize)
+
+ frames = 0
+ second = time.Tick(time.Second)
+
+ grid = []*cell{}
+ cols = screenWidth / wallSize
+ rows = screenHeight / wallSize
+ currentCell = new(cell)
+ backTrackStack = stack.NewStack(1)
+ )
+
+ // Set game FPS manually
+ fps := time.Tick(time.Second / 60)
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks! - Maze example",
+ Bounds: pixel.R(0, 0, float64(screenHeight), float64(screenWidth)),
+ }
+
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ grid, backTrackStack, currentCell = setupMaze(cols, rows)
+
+ gridIMDraw := imdraw.New(nil)
+
+ for !win.Closed() {
+ if win.JustReleased(pixelgl.KeyR) {
+ fmt.Println("R pressed")
+ grid, backTrackStack, currentCell = setupMaze(cols, rows)
+ }
+
+ win.Clear(colornames.Gray)
+ gridIMDraw.Clear()
+
+ for i := range grid {
+ grid[i].Draw(gridIMDraw, wallSize)
+ }
+
+ // step 1
+ // Make the initial cell the current cell and mark it as visited
+ currentCell.visited = true
+ currentCell.hightlight(gridIMDraw, wallSize)
+
+ // step 2.1
+ // If the current cell has any neighbours which have not been visited
+ // Choose a random unvisited cell
+ nextCell, _ := currentCell.GetRandomNeighbor(grid, cols, rows)
+ if nextCell != nil && !nextCell.visited {
+ // step 2.2
+ // Push the current cell to the stack
+ backTrackStack.Push(currentCell)
+
+ // step 2.3
+ // Remove the wall between the current cell and the chosen cell
+
+ removeWalls(currentCell, nextCell)
+
+ // step 2.4
+ // Make the chosen cell the current cell and mark it as visited
+ nextCell.visited = true
+ currentCell = nextCell
+ } else if backTrackStack.Len() > 0 {
+ currentCell = backTrackStack.Pop().(*cell)
+ }
+
+ gridIMDraw.Draw(win)
+ win.Update()
+ <-fps
+ updateFPSDisplay(win, &cfg, &frames, grid, second)
+ }
+}
+
+// Parses the maze arguments, all of them are optional.
+// Uses uint as implicit error checking :)
+func parseArgs() (uint, uint, uint) {
+ var mazeWidthPtr = flag.Uint("w", 800, "w sets the maze's width in pixels.")
+ var mazeHeightPtr = flag.Uint("h", 800, "h sets the maze's height in pixels.")
+ var wallSizePtr = flag.Uint("c", 40, "c sets the maze cell's size in pixels.")
+
+ flag.Parse()
+
+ // If these aren't default values AND if they're not the same values.
+ // We should warn the user that the maze will look funny.
+ if *mazeWidthPtr != 800 || *mazeHeightPtr != 800 {
+ if *mazeWidthPtr != *mazeHeightPtr {
+ fmt.Printf("WARNING: maze width: %d and maze height: %d don't match. \n", *mazeWidthPtr, *mazeHeightPtr)
+ fmt.Println("Maze will look funny because the maze size is bond to the window size!")
+ }
+ }
+
+ return *mazeWidthPtr, *mazeHeightPtr, *wallSizePtr
+}
+
+func updateFPSDisplay(win *pixelgl.Window, cfg *pixelgl.WindowConfig, frames *int, grid []*cell, second <-chan time.Time) {
+ *frames++
+ select {
+ case <-second:
+ win.SetTitle(fmt.Sprintf("%s | FPS: %d with %d Cells", cfg.Title, *frames, len(grid)))
+ *frames = 0
+ default:
+ }
+
+}
+
+func main() {
+ if debug {
+ defer profile.Start().Stop()
+ }
+ pixelgl.Run(run)
+}
diff --git a/examples/community/maze/screenshot.png b/examples/community/maze/screenshot.png
new file mode 100644
index 0000000..ce5bfd2
Binary files /dev/null and b/examples/community/maze/screenshot.png differ
diff --git a/examples/community/maze/stack/stack.go b/examples/community/maze/stack/stack.go
new file mode 100644
index 0000000..50a7a46
--- /dev/null
+++ b/examples/community/maze/stack/stack.go
@@ -0,0 +1,86 @@
+package stack
+
+type Stack struct {
+ top *Element
+ size int
+ max int
+}
+
+type Element struct {
+ value interface{}
+ next *Element
+}
+
+func NewStack(max int) *Stack {
+ return &Stack{max: max}
+}
+
+// Return the stack's length
+func (s *Stack) Len() int {
+ return s.size
+}
+
+// Return the stack's max
+func (s *Stack) Max() int {
+ return s.max
+}
+
+// Push a new element onto the stack
+func (s *Stack) Push(value interface{}) {
+ if s.size+1 > s.max {
+ if last := s.PopLast(); last == nil {
+ panic("Unexpected nil in stack")
+ }
+ }
+ s.top = &Element{value, s.top}
+ s.size++
+}
+
+// Remove the top element from the stack and return it's value
+// If the stack is empty, return nil
+func (s *Stack) Pop() (value interface{}) {
+ if s.size > 0 {
+ value, s.top = s.top.value, s.top.next
+ s.size--
+ return
+ }
+ return nil
+}
+
+func (s *Stack) PopLast() (value interface{}) {
+ if lastElem := s.popLast(s.top); lastElem != nil {
+ return lastElem.value
+ }
+ return nil
+}
+
+//Peek returns a top without removing it from list
+func (s *Stack) Peek() (value interface{}, exists bool) {
+ exists = false
+ if s.size > 0 {
+ value = s.top.value
+ exists = true
+ }
+
+ return
+}
+
+func (s *Stack) popLast(elem *Element) *Element {
+ if elem == nil {
+ return nil
+ }
+ // not last because it has next and a grandchild
+ if elem.next != nil && elem.next.next != nil {
+ return s.popLast(elem.next)
+ }
+
+ // current elem is second from bottom, as next elem has no child
+ if elem.next != nil && elem.next.next == nil {
+ last := elem.next
+ // make current elem bottom of stack by removing its next element
+ elem.next = nil
+ s.size--
+ return last
+ }
+ return nil
+}
diff --git a/examples/community/parallax-scrolling-background/README.md b/examples/community/parallax-scrolling-background/README.md
new file mode 100644
index 0000000..ceda9ed
--- /dev/null
+++ b/examples/community/parallax-scrolling-background/README.md
@@ -0,0 +1,9 @@
+# Parallax scrolling demo
+
+Created by [Sergio Vera](https://github.com/svera)
+
+This example shows how to implement an infinite side scrolling background with a depth effect, using [parallax scrolling](https://en.wikipedia.org/wiki/Parallax_scrolling). Code is based in the [infinite scrolling background](https://github.com/faiface/pixel/tree/master/examples/community/scrolling-background) demo.
+
+Credits to [Peter Hellberg](https://github.com/peterhellberg) for the improved background images.
+
+![Parallax scrolling background](result.png)
diff --git a/examples/community/parallax-scrolling-background/background.png b/examples/community/parallax-scrolling-background/background.png
new file mode 100644
index 0000000..97359fc
Binary files /dev/null and b/examples/community/parallax-scrolling-background/background.png differ
diff --git a/examples/community/parallax-scrolling-background/foreground.png b/examples/community/parallax-scrolling-background/foreground.png
new file mode 100644
index 0000000..7328cd8
Binary files /dev/null and b/examples/community/parallax-scrolling-background/foreground.png differ
diff --git a/examples/community/parallax-scrolling-background/main.go b/examples/community/parallax-scrolling-background/main.go
new file mode 100644
index 0000000..9add2b9
--- /dev/null
+++ b/examples/community/parallax-scrolling-background/main.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+ "image"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+const (
+ windowWidth = 600
+ windowHeight = 450
+ foregroundHeight = 149
+ // This is the scrolling speed (pixels per second)
+ // Negative values will make background to scroll to the left,
+ // positive to the right.
+ backgroundSpeed = -60
+ foregroundSpeed = -120
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Parallax scrolling demo",
+ Bounds: pixel.R(0, 0, windowWidth, windowHeight),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ // Pic must have double the width of the window, as it will scroll to the left or right
+ picBackground, err := loadPicture("background.png")
+ if err != nil {
+ panic(err)
+ }
+ picForeground, err := loadPicture("foreground.png")
+ if err != nil {
+ panic(err)
+ }
+
+ background := NewScrollingBackground(picBackground, windowWidth, windowHeight, backgroundSpeed)
+ foreground := NewScrollingBackground(picForeground, windowWidth, foregroundHeight, foregroundSpeed)
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+ background.Update(win, dt)
+ foreground.Update(win, dt)
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/parallax-scrolling-background/result.png b/examples/community/parallax-scrolling-background/result.png
new file mode 100644
index 0000000..3236c67
Binary files /dev/null and b/examples/community/parallax-scrolling-background/result.png differ
diff --git a/examples/community/parallax-scrolling-background/scrolling_background.go b/examples/community/parallax-scrolling-background/scrolling_background.go
new file mode 100644
index 0000000..32c5bf4
--- /dev/null
+++ b/examples/community/parallax-scrolling-background/scrolling_background.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "math"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+// ScrollingBackground stores all needed information to scroll a background
+// to the left or right
+type ScrollingBackground struct {
+ width float64
+ height float64
+ displacement float64
+ speed float64
+ backgrounds [2]*pixel.Sprite
+ positions [2]pixel.Vec
+}
+
+// NewScrollingBackground construct and returns a new instance of scrollingBackground,
+// positioning the background images according to the speed value
+func NewScrollingBackground(pic pixel.Picture, width, height, speed float64) *ScrollingBackground {
+ sb := &ScrollingBackground{
+ width: width,
+ height: height,
+ speed: speed,
+ backgrounds: [2]*pixel.Sprite{
+ pixel.NewSprite(pic, pixel.R(0, 0, width, height)),
+ pixel.NewSprite(pic, pixel.R(width, 0, width*2, height)),
+ },
+ }
+
+ sb.positionImages()
+ return sb
+}
+
+// If scrolling speed > 0, put second background image ouside the screen,
+// at the left side, otherwise put it at the right side.
+func (sb *ScrollingBackground) positionImages() {
+ if sb.speed > 0 {
+ sb.positions = [2]pixel.Vec{
+ pixel.V(sb.width/2, sb.height/2),
+ pixel.V((sb.width/2)-sb.width, sb.height/2),
+ }
+ } else {
+ sb.positions = [2]pixel.Vec{
+ pixel.V(sb.width/2, sb.height/2),
+ pixel.V(sb.width+(sb.width/2), sb.height/2),
+ }
+ }
+}
+
+// Update will move backgrounds certain pixels, depending of the amount of time passed
+func (sb *ScrollingBackground) Update(win *pixelgl.Window, dt float64) {
+ if math.Abs(sb.displacement) >= sb.width {
+ sb.displacement = 0
+ sb.positions[0], sb.positions[1] = sb.positions[1], sb.positions[0]
+ }
+ d := pixel.V(sb.displacement, 0)
+ sb.backgrounds[0].Draw(win, pixel.IM.Moved(sb.positions[0].Add(d)))
+ sb.backgrounds[1].Draw(win, pixel.IM.Moved(sb.positions[1].Add(d)))
+ sb.displacement += sb.speed * dt
+}
diff --git a/examples/community/procedural-terrain-1d/README.md b/examples/community/procedural-terrain-1d/README.md
new file mode 100644
index 0000000..922714f
--- /dev/null
+++ b/examples/community/procedural-terrain-1d/README.md
@@ -0,0 +1,12 @@
+# Procedural 1D terrain generator
+
+Created by [Sergio Vera](https://github.com/svera).
+
+This is a demo of a 1D terrain generator using [Perlin noise](https://en.wikipedia.org/wiki/Perlin_noise) algorithm.
+Press *space* to generate a random terrain.
+
+Uses [Go-Perlin](https://github.com/aquilax/go-perlin).
+
+Texture by [hh316](https://hhh316.deviantart.com/art/Seamless-stone-cliff-face-mountain-texture-377076626).
+
+![Randomly generated 1D terrain](result.png)
diff --git a/examples/community/procedural-terrain-1d/main.go b/examples/community/procedural-terrain-1d/main.go
new file mode 100644
index 0000000..f8cf53d
--- /dev/null
+++ b/examples/community/procedural-terrain-1d/main.go
@@ -0,0 +1,104 @@
+package main
+
+import (
+ "image"
+ "math/rand"
+ "os"
+ "time"
+
+ _ "image/jpeg"
+
+ perlin "github.com/aquilax/go-perlin"
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+const (
+ width = 800
+ height = 600
+ // Top of the mountain must be around the half of the screen height
+ verticalOffset = height / 2
+ // Perlin noise provides variations in values between -1 and 1,
+ // we multiply those so they're visible on screen
+ scale = 100
+ waveLength = 100
+ alpha = 2.
+ beta = 2.
+ n = 3
+ maximumSeedValue = 100
+)
+
+func init() {
+ rand.Seed(time.Now().UTC().UnixNano())
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Procedural terrain 1D",
+ Bounds: pixel.R(0, 0, width, height),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ pic, err := loadPicture("stone.jpg")
+ if err != nil {
+ panic(err)
+ }
+ imd := imdraw.New(pic)
+
+ drawTerrain(win, imd)
+
+ for !win.Closed() {
+ if win.JustPressed(pixelgl.KeySpace) {
+ drawTerrain(win, imd)
+ }
+ win.Update()
+ }
+}
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func drawTerrain(win *pixelgl.Window, imd *imdraw.IMDraw) {
+ var seed = rand.Int63n(maximumSeedValue)
+ p := perlin.NewPerlin(alpha, beta, n, seed)
+
+ imd.Clear()
+ win.Clear(colornames.Skyblue)
+ for x := 0.; x < width; x++ {
+ y := p.Noise1D(x/waveLength)*scale + verticalOffset
+ renderTexturedLine(x, y, imd)
+ }
+ imd.Draw(win)
+}
+
+// Render a textured line in position x with a height y.
+// Note that the textured line is just a 1 px width rectangle.
+// We push the opposite vertices of that rectangle and specify the points of the
+// texture we want to apply to them. Pixel will fill the rest of the rectangle interpolating the texture.
+func renderTexturedLine(x, y float64, imd *imdraw.IMDraw) {
+ imd.Intensity = 1.
+ imd.Picture = pixel.V(x, 0)
+ imd.Push(pixel.V(x, 0))
+ imd.Picture = pixel.V(x+1, y)
+ imd.Push(pixel.V(x+1, y))
+ imd.Rectangle(0)
+}
diff --git a/examples/community/procedural-terrain-1d/result.png b/examples/community/procedural-terrain-1d/result.png
new file mode 100644
index 0000000..b90fb29
Binary files /dev/null and b/examples/community/procedural-terrain-1d/result.png differ
diff --git a/examples/community/procedural-terrain-1d/stone.jpg b/examples/community/procedural-terrain-1d/stone.jpg
new file mode 100644
index 0000000..9899939
Binary files /dev/null and b/examples/community/procedural-terrain-1d/stone.jpg differ
diff --git a/examples/community/raycaster/README.md b/examples/community/raycaster/README.md
new file mode 100644
index 0000000..56eba27
--- /dev/null
+++ b/examples/community/raycaster/README.md
@@ -0,0 +1,22 @@
+# raycaster
+
+A raycaster made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments).
+
+Based on Lode’s article on [raycasting](http://lodev.org/cgtutor/raycasting.html).
+
+## Controls
+
+WASD for strafing and arrow keys for rotation.
+
+Place blocks using the number keys.
+
+## Screenshots
+
+![raycaster animation](https://user-images.githubusercontent.com/565124/31828029-798e6620-b5b9-11e7-96b7-fda540755745.gif)
+
+![raycaster screenshot](screenshot.png)
+
+## Links
+
+ - https://github.com/peterhellberg/pixel-experiments/tree/master/raycaster
+ - https://gist.github.com/peterhellberg/835eccabf95800555120cc8f0c9e16c2
diff --git a/examples/community/raycaster/raycaster.go b/examples/community/raycaster/raycaster.go
new file mode 100644
index 0000000..598abcb
--- /dev/null
+++ b/examples/community/raycaster/raycaster.go
@@ -0,0 +1,550 @@
+package main
+
+import (
+ "bytes"
+ "flag"
+ "image"
+ "image/color"
+ "image/draw"
+ "image/png"
+ "math"
+ "time"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+const texSize = 64
+
+var (
+ fullscreen = false
+ showMap = true
+ width = 320
+ height = 200
+ scale = 3.0
+ wallDistance = 8.0
+
+ as actionSquare
+
+ pos, dir, plane pixel.Vec
+
+ textures = loadTextures()
+)
+
+func setup() {
+ pos = pixel.V(12.0, 14.5)
+ dir = pixel.V(-1.0, 0.0)
+ plane = pixel.V(0.0, 0.66)
+}
+
+var world = [24][24]int{
+ {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1},
+ {1, 0, 0, 0, 2, 7, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 2, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 3, 0, 7, 0, 3, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 2, 2, 2, 2, 0, 2, 2, 0, 0, 0, 0, 3, 0, 3, 0, 3, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 0, 6, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 5, 0, 0, 0, 1},
+ {1, 0, 6, 0, 4, 0, 7, 0, 4, 0, 0, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 0, 1},
+ {1, 0, 0, 0, 4, 0, 0, 0, 4, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 0, 0, 0, 1},
+ {1, 4, 4, 4, 4, 4, 4, 0, 4, 0, 0, 0, 5, 5, 0, 5, 5, 5, 0, 5, 5, 0, 0, 1},
+ {1, 4, 0, 0, 0, 0, 0, 0, 4, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 0, 1},
+ {1, 4, 0, 4, 0, 0, 0, 0, 4, 0, 0, 5, 0, 5, 5, 5, 5, 5, 5, 5, 0, 5, 0, 1},
+ {1, 4, 0, 4, 4, 4, 4, 4, 4, 0, 0, 5, 0, 5, 0, 0, 0, 0, 0, 5, 0, 5, 0, 1},
+ {1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 0, 5, 5, 0, 0, 0, 0, 1},
+ {1, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
+ {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
+}
+
+func loadTextures() *image.RGBA {
+ p, err := png.Decode(bytes.NewReader(textureData))
+ if err != nil {
+ panic(err)
+ }
+
+ m := image.NewRGBA(p.Bounds())
+
+ draw.Draw(m, m.Bounds(), p, image.ZP, draw.Src)
+
+ return m
+}
+
+func getTexNum(x, y int) int {
+ return world[x][y]
+}
+
+func getColor(x, y int) color.RGBA {
+ switch world[x][y] {
+ case 0:
+ return color.RGBA{43, 30, 24, 255}
+ case 1:
+ return color.RGBA{100, 89, 73, 255}
+ case 2:
+ return color.RGBA{110, 23, 0, 255}
+ case 3:
+ return color.RGBA{45, 103, 171, 255}
+ case 4:
+ return color.RGBA{123, 84, 33, 255}
+ case 5:
+ return color.RGBA{158, 148, 130, 255}
+ case 6:
+ return color.RGBA{203, 161, 47, 255}
+ case 7:
+ return color.RGBA{255, 107, 0, 255}
+ case 9:
+ return color.RGBA{0, 0, 0, 0}
+ default:
+ return color.RGBA{255, 194, 32, 255}
+ }
+}
+
+func frame() *image.RGBA {
+ m := image.NewRGBA(image.Rect(0, 0, width, height))
+
+ for x := 0; x < width; x++ {
+ var (
+ step image.Point
+ sideDist pixel.Vec
+ perpWallDist float64
+ hit, side bool
+
+ rayPos, worldX, worldY = pos, int(pos.X), int(pos.Y)
+
+ cameraX = 2*float64(x)/float64(width) - 1
+
+ rayDir = pixel.V(
+ dir.X+plane.X*cameraX,
+ dir.Y+plane.Y*cameraX,
+ )
+
+ deltaDist = pixel.V(
+ math.Sqrt(1.0+(rayDir.Y*rayDir.Y)/(rayDir.X*rayDir.X)),
+ math.Sqrt(1.0+(rayDir.X*rayDir.X)/(rayDir.Y*rayDir.Y)),
+ )
+ )
+
+ if rayDir.X < 0 {
+ step.X = -1
+ sideDist.X = (rayPos.X - float64(worldX)) * deltaDist.X
+ } else {
+ step.X = 1
+ sideDist.X = (float64(worldX) + 1.0 - rayPos.X) * deltaDist.X
+ }
+
+ if rayDir.Y < 0 {
+ step.Y = -1
+ sideDist.Y = (rayPos.Y - float64(worldY)) * deltaDist.Y
+ } else {
+ step.Y = 1
+ sideDist.Y = (float64(worldY) + 1.0 - rayPos.Y) * deltaDist.Y
+ }
+
+ for !hit {
+ if sideDist.X < sideDist.Y {
+ sideDist.X += deltaDist.X
+ worldX += step.X
+ side = false
+ } else {
+ sideDist.Y += deltaDist.Y
+ worldY += step.Y
+ side = true
+ }
+
+ if world[worldX][worldY] > 0 {
+ hit = true
+ }
+ }
+
+ var wallX float64
+
+ if side {
+ perpWallDist = (float64(worldY) - rayPos.Y + (1-float64(step.Y))/2) / rayDir.Y
+ wallX = rayPos.X + perpWallDist*rayDir.X
+ } else {
+ perpWallDist = (float64(worldX) - rayPos.X + (1-float64(step.X))/2) / rayDir.X
+ wallX = rayPos.Y + perpWallDist*rayDir.Y
+ }
+
+ if x == width/2 {
+ wallDistance = perpWallDist
+ }
+
+ wallX -= math.Floor(wallX)
+
+ texX := int(wallX * float64(texSize))
+
+ lineHeight := int(float64(height) / perpWallDist)
+
+ if lineHeight < 1 {
+ lineHeight = 1
+ }
+
+ drawStart := -lineHeight/2 + height/2
+ if drawStart < 0 {
+ drawStart = 0
+ }
+
+ drawEnd := lineHeight/2 + height/2
+ if drawEnd >= height {
+ drawEnd = height - 1
+ }
+
+ if !side && rayDir.X > 0 {
+ texX = texSize - texX - 1
+ }
+
+ if side && rayDir.Y < 0 {
+ texX = texSize - texX - 1
+ }
+
+ texNum := getTexNum(worldX, worldY)
+
+ for y := drawStart; y < drawEnd+1; y++ {
+ d := y*256 - height*128 + lineHeight*128
+ texY := ((d * texSize) / lineHeight) / 256
+
+ c := textures.RGBAAt(
+ texX+texSize*(texNum),
+ texY%texSize,
+ )
+
+ if side {
+ c.R = c.R / 2
+ c.G = c.G / 2
+ c.B = c.B / 2
+ }
+
+ m.Set(x, y, c)
+ }
+
+ var floorWall pixel.Vec
+
+ if !side && rayDir.X > 0 {
+ floorWall.X = float64(worldX)
+ floorWall.Y = float64(worldY) + wallX
+ } else if !side && rayDir.X < 0 {
+ floorWall.X = float64(worldX) + 1.0
+ floorWall.Y = float64(worldY) + wallX
+ } else if side && rayDir.Y > 0 {
+ floorWall.X = float64(worldX) + wallX
+ floorWall.Y = float64(worldY)
+ } else {
+ floorWall.X = float64(worldX) + wallX
+ floorWall.Y = float64(worldY) + 1.0
+ }
+
+ distWall, distPlayer := perpWallDist, 0.0
+
+ for y := drawEnd + 1; y < height; y++ {
+ currentDist := float64(height) / (2.0*float64(y) - float64(height))
+
+ weight := (currentDist - distPlayer) / (distWall - distPlayer)
+
+ currentFloor := pixel.V(
+ weight*floorWall.X+(1.0-weight)*pos.X,
+ weight*floorWall.Y+(1.0-weight)*pos.Y,
+ )
+
+ fx := int(currentFloor.X*float64(texSize)) % texSize
+ fy := int(currentFloor.Y*float64(texSize)) % texSize
+
+ m.Set(x, y, textures.At(fx, fy))
+
+ m.Set(x, height-y-1, textures.At(fx+(4*texSize), fy))
+ m.Set(x, height-y, textures.At(fx+(4*texSize), fy))
+ }
+ }
+
+ return m
+}
+
+func minimap() *image.RGBA {
+ m := image.NewRGBA(image.Rect(0, 0, 24, 26))
+
+ for x, row := range world {
+ for y, _ := range row {
+ c := getColor(x, y)
+ if c.A == 255 {
+ c.A = 96
+ }
+ m.Set(x, y, c)
+ }
+ }
+
+ m.Set(int(pos.X), int(pos.Y), color.RGBA{255, 0, 0, 255})
+
+ if as.active {
+ m.Set(as.X, as.Y, color.RGBA{255, 255, 255, 255})
+ } else {
+ m.Set(as.X, as.Y, color.RGBA{64, 64, 64, 255})
+ }
+
+ return m
+}
+
+func getActionSquare() actionSquare {
+ pt := image.Pt(int(pos.X)+1, int(pos.Y))
+
+ a := dir.Angle()
+
+ switch {
+ case a > 2.8 || a < -2.8:
+ pt = image.Pt(int(pos.X)-1, int(pos.Y))
+ case a > -2.8 && a < -2.2:
+ pt = image.Pt(int(pos.X)-1, int(pos.Y)-1)
+ case a > -2.2 && a < -1.4:
+ pt = image.Pt(int(pos.X), int(pos.Y)-1)
+ case a > -1.4 && a < -0.7:
+ pt = image.Pt(int(pos.X)+1, int(pos.Y)-1)
+ case a > 0.4 && a < 1.0:
+ pt = image.Pt(int(pos.X)+1, int(pos.Y)+1)
+ case a > 1.0 && a < 1.7:
+ pt = image.Pt(int(pos.X), int(pos.Y)+1)
+ case a > 1.7:
+ pt = image.Pt(int(pos.X)-1, int(pos.Y)+1)
+ }
+
+ block := -1
+ active := pt.X > 0 && pt.X < 23 && pt.Y > 0 && pt.Y < 23
+
+ if active {
+ block = world[pt.X][pt.Y]
+ }
+
+ return actionSquare{
+ X: pt.X,
+ Y: pt.Y,
+ active: active,
+ block: block,
+ }
+}
+
+type actionSquare struct {
+ X int
+ Y int
+ block int
+ active bool
+}
+
+func (as actionSquare) toggle(n int) {
+ if as.active {
+ if world[as.X][as.Y] == 0 {
+ world[as.X][as.Y] = n
+ } else {
+ world[as.X][as.Y] = 0
+ }
+ }
+}
+
+func (as actionSquare) set(n int) {
+ if as.active {
+ world[as.X][as.Y] = n
+ }
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Bounds: pixel.R(0, 0, float64(width)*scale, float64(height)*scale),
+ VSync: true,
+ Undecorated: true,
+ }
+
+ if fullscreen {
+ cfg.Monitor = pixelgl.PrimaryMonitor()
+ }
+
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ c := win.Bounds().Center()
+
+ last := time.Now()
+
+ mapRot := -1.6683362599999894
+
+ for !win.Closed() {
+ if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) {
+ return
+ }
+
+ win.Clear(color.Black)
+
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ as = getActionSquare()
+
+ if win.Pressed(pixelgl.KeyUp) || win.Pressed(pixelgl.KeyW) {
+ moveForward(3.5 * dt)
+ }
+
+ if win.Pressed(pixelgl.KeyA) {
+ moveLeft(3.5 * dt)
+ }
+
+ if win.Pressed(pixelgl.KeyDown) || win.Pressed(pixelgl.KeyS) {
+ moveBackwards(3.5 * dt)
+ }
+
+ if win.Pressed(pixelgl.KeyD) {
+ moveRight(3.5 * dt)
+ }
+
+ if win.Pressed(pixelgl.KeyRight) {
+ turnRight(1.2 * dt)
+ }
+
+ if win.Pressed(pixelgl.KeyLeft) {
+ turnLeft(1.2 * dt)
+ }
+
+ if win.JustPressed(pixelgl.KeyM) {
+ showMap = !showMap
+ }
+
+ if win.JustPressed(pixelgl.Key1) {
+ as.set(1)
+ }
+
+ if win.JustPressed(pixelgl.Key2) {
+ as.set(2)
+ }
+
+ if win.JustPressed(pixelgl.Key3) {
+ as.set(3)
+ }
+
+ if win.JustPressed(pixelgl.Key4) {
+ as.set(4)
+ }
+
+ if win.JustPressed(pixelgl.Key5) {
+ as.set(5)
+ }
+
+ if win.JustPressed(pixelgl.Key6) {
+ as.set(6)
+ }
+
+ if win.JustPressed(pixelgl.Key7) {
+ as.set(7)
+ }
+
+ if win.JustPressed(pixelgl.Key0) {
+ as.set(0)
+ }
+
+ if win.JustPressed(pixelgl.KeySpace) {
+ as.toggle(3)
+ }
+
+ p := pixel.PictureDataFromImage(frame())
+
+ pixel.NewSprite(p, p.Bounds()).
+ Draw(win, pixel.IM.Moved(c).Scaled(c, scale))
+
+ if showMap {
+ m := pixel.PictureDataFromImage(minimap())
+
+ mc := m.Bounds().Min.Add(pixel.V(-m.Rect.W(), m.Rect.H()))
+
+ pixel.NewSprite(m, m.Bounds()).
+ Draw(win, pixel.IM.
+ Moved(mc).
+ Rotated(mc, mapRot).
+ ScaledXY(pixel.ZV, pixel.V(-scale*2, scale*2)))
+ }
+
+ win.Update()
+ }
+}
+
+func moveForward(s float64) {
+ if wallDistance > 0.3 {
+ if world[int(pos.X+dir.X*s)][int(pos.Y)] == 0 {
+ pos.X += dir.X * s
+ }
+
+ if world[int(pos.X)][int(pos.Y+dir.Y*s)] == 0 {
+ pos.Y += dir.Y * s
+ }
+ }
+}
+
+func moveLeft(s float64) {
+ if world[int(pos.X-plane.X*s)][int(pos.Y)] == 0 {
+ pos.X -= plane.X * s
+ }
+
+ if world[int(pos.X)][int(pos.Y-plane.Y*s)] == 0 {
+ pos.Y -= plane.Y * s
+ }
+}
+
+func moveBackwards(s float64) {
+ if world[int(pos.X-dir.X*s)][int(pos.Y)] == 0 {
+ pos.X -= dir.X * s
+ }
+
+ if world[int(pos.X)][int(pos.Y-dir.Y*s)] == 0 {
+ pos.Y -= dir.Y * s
+ }
+}
+
+func moveRight(s float64) {
+ if world[int(pos.X+plane.X*s)][int(pos.Y)] == 0 {
+ pos.X += plane.X * s
+ }
+
+ if world[int(pos.X)][int(pos.Y+plane.Y*s)] == 0 {
+ pos.Y += plane.Y * s
+ }
+}
+
+func turnRight(s float64) {
+ oldDirX := dir.X
+
+ dir.X = dir.X*math.Cos(-s) - dir.Y*math.Sin(-s)
+ dir.Y = oldDirX*math.Sin(-s) + dir.Y*math.Cos(-s)
+
+ oldPlaneX := plane.X
+
+ plane.X = plane.X*math.Cos(-s) - plane.Y*math.Sin(-s)
+ plane.Y = oldPlaneX*math.Sin(-s) + plane.Y*math.Cos(-s)
+}
+
+func turnLeft(s float64) {
+ oldDirX := dir.X
+
+ dir.X = dir.X*math.Cos(s) - dir.Y*math.Sin(s)
+ dir.Y = oldDirX*math.Sin(s) + dir.Y*math.Cos(s)
+
+ oldPlaneX := plane.X
+
+ plane.X = plane.X*math.Cos(s) - plane.Y*math.Sin(s)
+ plane.Y = oldPlaneX*math.Sin(s) + plane.Y*math.Cos(s)
+}
+
+func main() {
+ flag.BoolVar(&fullscreen, "f", fullscreen, "fullscreen")
+ flag.IntVar(&width, "w", width, "width")
+ flag.IntVar(&height, "h", height, "height")
+ flag.Float64Var(&scale, "s", scale, "scale")
+ flag.Parse()
+
+ setup()
+
+ pixelgl.Run(run)
+}
+
+var textureData = []byte{137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 2, 0, 0, 0, 0, 64, 8, 3, 0, 0, 0, 91, 97, 63, 141, 0, 0, 2, 100, 80, 76, 84, 69, 182, 182, 182, 48, 38, 29, 33, 23, 15, 191, 146, 37, 7, 3, 1, 72, 64, 49, 40, 28, 20, 37, 26, 19, 42, 31, 22, 110, 100, 84, 64, 54, 40, 55, 46, 32, 142, 131, 114, 74, 40, 10, 22, 14, 7, 97, 86, 69, 66, 35, 6, 99, 60, 20, 102, 91, 75, 16, 9, 3, 120, 27, 1, 27, 18, 11, 184, 140, 33, 83, 72, 57, 131, 119, 103, 114, 73, 27, 128, 87, 34, 48, 23, 2, 159, 149, 134, 185, 185, 185, 6, 37, 82, 68, 59, 45, 130, 188, 209, 111, 72, 8, 129, 32, 4, 134, 93, 38, 150, 139, 123, 46, 33, 27, 80, 68, 54, 199, 156, 43, 87, 75, 61, 43, 43, 43, 123, 111, 95, 60, 50, 38, 92, 55, 17, 119, 79, 31, 170, 159, 142, 82, 46, 14, 170, 125, 23, 146, 135, 118, 164, 119, 20, 189, 189, 189, 31, 80, 138, 115, 104, 89, 203, 162, 47, 1, 0, 0, 127, 115, 99, 87, 49, 15, 193, 193, 193, 1, 27, 64, 40, 96, 165, 83, 83, 83, 54, 42, 36, 42, 100, 171, 154, 143, 128, 118, 108, 92, 57, 57, 56, 109, 69, 25, 137, 127, 108, 206, 167, 50, 89, 80, 63, 224, 211, 196, 0, 9, 27, 255, 255, 255, 79, 44, 1, 3, 31, 71, 92, 82, 66, 71, 71, 71, 22, 61, 110, 19, 54, 100, 198, 198, 198, 189, 176, 161, 1, 22, 57, 117, 78, 9, 93, 19, 0, 29, 76, 132, 99, 22, 1, 111, 26, 2, 35, 87, 150, 27, 71, 126, 78, 69, 57, 127, 127, 127, 160, 114, 18, 0, 5, 16, 1, 18, 49, 0, 12, 38, 134, 124, 105, 178, 133, 28, 34, 33, 33, 105, 25, 2, 139, 123, 109, 135, 38, 7, 27, 67, 116, 105, 66, 24, 37, 91, 157, 72, 58, 46, 174, 129, 26, 59, 30, 5, 33, 83, 144, 132, 125, 112, 124, 83, 32, 148, 130, 114, 45, 104, 178, 78, 62, 51, 195, 152, 40, 23, 54, 90, 153, 137, 123, 81, 20, 2, 38, 15, 1, 184, 173, 157, 175, 163, 148, 9, 44, 95, 172, 119, 47, 28, 63, 102, 210, 171, 54, 95, 56, 3, 94, 84, 72, 83, 73, 66, 207, 195, 180, 201, 189, 173, 179, 168, 152, 28, 10, 0, 155, 155, 155, 97, 87, 80, 139, 100, 42, 152, 55, 18, 144, 46, 11, 164, 155, 138, 123, 123, 123, 55, 27, 4, 203, 203, 203, 214, 203, 187, 87, 86, 86, 164, 112, 46, 155, 103, 39, 50, 116, 197, 106, 106, 106, 57, 133, 225, 44, 107, 183, 52, 121, 205, 86, 49, 3, 54, 126, 214, 147, 103, 16, 147, 95, 36, 68, 16, 1, 140, 98, 16, 153, 109, 18, 145, 102, 45, 169, 150, 136, 194, 181, 166, 48, 112, 190, 169, 73, 36, 84, 7, 0, 19, 47, 78, 169, 120, 61, 114, 114, 114, 131, 91, 14, 123, 84, 13, 169, 170, 170, 64, 63, 62, 137, 137, 137, 100, 64, 7, 56, 11, 0, 100, 100, 100, 209, 209, 209, 178, 130, 76, 50, 50, 50, 43, 87, 171, 95, 95, 95, 9, 51, 110, 87, 79, 70, 110, 159, 225, 105, 146, 167, 15, 39, 62, 13, 31, 46, 174, 175, 175, 213, 144, 111, 95, 143, 212, 189, 90, 48, 164, 164, 163, 66, 148, 245, 70, 114, 191, 116, 160, 179, 141, 143, 144, 220, 155, 122, 222, 224, 225, 154, 198, 214, 60, 139, 234, 125, 53, 24, 237, 242, 244, 140, 175, 189, 188, 217, 227, 191, 150, 127, 218, 172, 148, 46, 0, 54, 145, 0, 0, 51, 110, 73, 68, 65, 84, 120, 218, 164, 89, 251, 83, 27, 85, 20, 206, 46, 44, 201, 238, 38, 89, 8, 132, 141, 36, 146, 9, 133, 52, 36, 72, 0, 27, 11, 104, 90, 94, 166, 140, 6, 104, 10, 82, 26, 106, 11, 229, 209, 2, 125, 88, 30, 133, 14, 211, 240, 40, 200, 163, 128, 14, 227, 116, 176, 160, 72, 145, 74, 45, 48, 90, 113, 208, 81, 199, 225, 23, 255, 46, 191, 187, 187, 233, 110, 124, 160, 213, 143, 189, 143, 239, 158, 123, 218, 41, 223, 185, 231, 62, 170, 243, 165, 228, 229, 81, 255, 21, 167, 242, 104, 49, 192, 222, 200, 189, 113, 195, 27, 53, 68, 121, 171, 143, 97, 246, 99, 99, 226, 24, 195, 39, 235, 157, 252, 254, 244, 244, 62, 195, 241, 62, 234, 20, 179, 63, 54, 54, 166, 181, 171, 94, 86, 43, 173, 167, 173, 86, 243, 149, 83, 85, 81, 96, 63, 202, 36, 96, 76, 237, 70, 85, 251, 55, 249, 87, 136, 159, 222, 106, 77, 206, 126, 73, 188, 171, 224, 174, 140, 236, 251, 5, 5, 5, 254, 50, 2, 211, 253, 234, 172, 99, 159, 2, 31, 43, 64, 55, 1, 202, 208, 177, 172, 106, 141, 143, 115, 194, 49, 60, 62, 49, 190, 120, 102, 113, 124, 124, 98, 113, 157, 61, 179, 56, 49, 49, 62, 129, 114, 102, 241, 204, 153, 51, 109, 158, 197, 197, 241, 137, 245, 8, 161, 139, 139, 145, 145, 54, 46, 178, 56, 62, 57, 82, 56, 17, 89, 95, 92, 95, 159, 24, 55, 114, 95, 74, 248, 232, 163, 143, 200, 223, 248, 145, 68, 18, 1, 19, 16, 111, 49, 231, 51, 9, 241, 169, 85, 255, 19, 186, 165, 105, 223, 41, 21, 223, 160, 188, 12, 168, 60, 166, 162, 116, 110, 178, 210, 102, 140, 25, 56, 222, 202, 240, 220, 216, 24, 148, 226, 248, 116, 74, 220, 143, 137, 49, 72, 102, 153, 142, 49, 188, 24, 139, 49, 90, 187, 234, 53, 182, 191, 63, 22, 165, 104, 243, 253, 59, 59, 143, 30, 237, 236, 176, 101, 143, 100, 160, 143, 129, 59, 211, 126, 149, 171, 246, 29, 251, 253, 177, 125, 56, 82, 116, 114, 195, 79, 223, 127, 255, 61, 10, 169, 127, 66, 139, 250, 104, 46, 23, 16, 224, 215, 95, 179, 239, 103, 125, 248, 225, 103, 50, 238, 167, 166, 166, 165, 93, 173, 174, 126, 5, 120, 237, 53, 148, 155, 248, 209, 0, 195, 213, 213, 87, 211, 210, 82, 83, 53, 62, 12, 116, 214, 192, 114, 230, 140, 102, 96, 125, 164, 84, 37, 139, 227, 195, 195, 94, 126, 60, 97, 186, 145, 73, 125, 144, 154, 154, 250, 14, 240, 16, 197, 132, 90, 234, 43, 13, 62, 66, 240, 17, 200, 163, 169, 18, 250, 0, 169, 19, 172, 10, 214, 17, 160, 150, 155, 170, 58, 165, 39, 65, 33, 160, 42, 135, 236, 224, 178, 9, 1, 96, 188, 17, 219, 143, 97, 173, 142, 253, 39, 232, 153, 66, 129, 189, 97, 9, 59, 68, 202, 160, 183, 242, 20, 63, 54, 77, 81, 62, 67, 138, 89, 79, 100, 158, 118, 242, 34, 195, 51, 92, 148, 139, 25, 124, 20, 111, 240, 48, 162, 45, 23, 43, 216, 160, 122, 77, 199, 162, 136, 9, 189, 21, 250, 19, 4, 126, 27, 210, 2, 130, 43, 189, 71, 248, 100, 59, 33, 59, 119, 224, 183, 31, 139, 166, 36, 119, 127, 241, 197, 7, 31, 124, 251, 43, 193, 183, 10, 254, 13, 143, 147, 236, 59, 159, 29, 251, 236, 216, 177, 215, 142, 65, 235, 59, 208, 63, 173, 250, 149, 106, 72, 159, 168, 187, 82, 163, 192, 138, 57, 169, 26, 31, 102, 92, 18, 23, 24, 39, 203, 220, 178, 168, 13, 8, 71, 164, 141, 52, 234, 80, 49, 191, 190, 24, 1, 85, 3, 64, 209, 81, 137, 2, 192, 100, 66, 165, 69, 60, 58, 228, 16, 48, 201, 242, 163, 178, 219, 31, 60, 232, 123, 80, 181, 181, 181, 28, 71, 16, 186, 170, 84, 5, 162, 34, 56, 15, 108, 1, 138, 125, 23, 120, 254, 252, 121, 176, 78, 119, 170, 210, 123, 195, 230, 245, 86, 178, 211, 255, 9, 86, 206, 88, 105, 97, 5, 79, 79, 152, 162, 173, 233, 148, 79, 180, 148, 10, 30, 31, 79, 165, 135, 44, 172, 200, 26, 133, 64, 192, 226, 102, 5, 193, 194, 82, 110, 142, 167, 67, 30, 161, 178, 130, 181, 80, 180, 234, 229, 141, 145, 221, 193, 64, 219, 119, 134, 154, 134, 134, 154, 111, 252, 134, 166, 169, 185, 163, 185, 185, 3, 165, 25, 188, 137, 160, 153, 84, 170, 125, 232, 145, 29, 126, 216, 77, 242, 146, 107, 71, 7, 186, 71, 15, 14, 126, 216, 28, 29, 237, 30, 64, 51, 128, 246, 223, 240, 129, 129, 205, 1, 240, 218, 251, 166, 84, 19, 126, 169, 166, 212, 62, 251, 253, 212, 178, 130, 52, 41, 7, 92, 149, 161, 116, 48, 132, 31, 212, 0, 154, 130, 178, 84, 141, 15, 179, 62, 92, 81, 225, 112, 204, 21, 70, 34, 142, 26, 135, 145, 26, 25, 25, 30, 71, 52, 160, 76, 172, 23, 214, 204, 89, 10, 209, 147, 182, 132, 241, 241, 245, 138, 92, 129, 202, 109, 179, 140, 140, 71, 38, 71, 34, 19, 35, 133, 21, 21, 172, 83, 13, 0, 89, 120, 40, 44, 11, 158, 136, 63, 5, 0, 254, 110, 187, 29, 1, 80, 215, 186, 187, 180, 180, 92, 23, 92, 174, 219, 13, 126, 147, 95, 23, 156, 127, 94, 39, 253, 60, 149, 107, 252, 236, 6, 171, 242, 235, 170, 150, 131, 187, 75, 152, 185, 91, 181, 251, 20, 157, 186, 165, 224, 211, 93, 120, 180, 230, 235, 204, 255, 19, 124, 160, 82, 16, 66, 34, 19, 165, 172, 52, 205, 244, 88, 194, 149, 158, 74, 142, 231, 24, 159, 197, 139, 0, 176, 180, 149, 178, 150, 128, 69, 240, 4, 68, 55, 207, 113, 54, 145, 216, 45, 41, 86, 213, 75, 202, 63, 30, 206, 192, 236, 79, 87, 6, 216, 202, 82, 155, 173, 20, 165, 179, 194, 104, 171, 100, 3, 149, 168, 88, 12, 130, 119, 150, 86, 170, 118, 118, 127, 236, 70, 44, 102, 97, 99, 92, 242, 251, 181, 181, 181, 151, 128, 203, 151, 47, 215, 202, 229, 40, 14, 114, 249, 125, 137, 159, 59, 7, 2, 103, 59, 196, 244, 151, 153, 164, 95, 104, 159, 223, 143, 16, 144, 164, 127, 69, 2, 89, 243, 36, 10, 80, 161, 6, 136, 252, 126, 127, 159, 198, 135, 105, 99, 56, 39, 199, 57, 25, 39, 231, 227, 125, 12, 21, 98, 24, 143, 32, 33, 44, 8, 30, 175, 219, 35, 132, 67, 97, 1, 85, 72, 228, 56, 134, 50, 184, 25, 198, 45, 138, 162, 39, 132, 127, 190, 155, 9, 113, 174, 39, 171, 171, 95, 185, 238, 61, 112, 173, 174, 60, 121, 242, 213, 179, 212, 70, 240, 39, 141, 207, 100, 222, 120, 175, 207, 37, 243, 190, 198, 191, 178, 131, 111, 237, 62, 213, 253, 103, 236, 238, 86, 253, 239, 0, 160, 60, 30, 81, 8, 185, 163, 110, 138, 114, 154, 249, 168, 133, 245, 134, 4, 78, 44, 181, 58, 3, 149, 33, 193, 98, 9, 4, 88, 139, 197, 34, 160, 43, 138, 94, 67, 56, 4, 187, 232, 164, 41, 213, 43, 151, 21, 88, 214, 115, 155, 50, 13, 73, 203, 188, 227, 252, 249, 140, 140, 243, 77, 143, 7, 55, 58, 154, 208, 5, 233, 192, 88, 198, 208, 200, 76, 198, 121, 213, 62, 180, 19, 173, 96, 241, 103, 135, 111, 39, 95, 254, 46, 251, 174, 244, 243, 29, 78, 120, 119, 191, 187, 219, 112, 33, 145, 223, 37, 60, 251, 207, 252, 46, 56, 234, 75, 118, 127, 170, 223, 95, 224, 247, 155, 250, 72, 74, 45, 240, 23, 164, 41, 144, 115, 0, 169, 226, 49, 32, 13, 99, 198, 131, 7, 26, 31, 78, 100, 56, 25, 140, 211, 233, 228, 41, 193, 45, 69, 130, 52, 224, 230, 121, 61, 35, 50, 0, 226, 3, 96, 24, 189, 30, 29, 229, 67, 32, 8, 97, 206, 229, 106, 116, 61, 123, 120, 207, 238, 250, 234, 137, 171, 145, 8, 236, 250, 170, 209, 117, 15, 1, 1, 238, 114, 61, 235, 131, 93, 226, 8, 4, 151, 11, 118, 112, 213, 14, 190, 21, 204, 223, 218, 154, 143, 163, 245, 95, 1, 19, 229, 13, 161, 234, 48, 95, 151, 158, 158, 140, 207, 140, 26, 45, 122, 201, 47, 201, 245, 28, 206, 249, 78, 220, 8, 104, 3, 109, 165, 66, 130, 80, 105, 177, 244, 120, 156, 41, 140, 133, 13, 67, 122, 236, 45, 97, 132, 0, 139, 24, 16, 57, 42, 100, 129, 93, 228, 245, 148, 234, 229, 240, 178, 149, 129, 24, 9, 128, 14, 8, 159, 145, 145, 209, 149, 209, 132, 212, 63, 184, 129, 222, 108, 23, 88, 7, 198, 114, 154, 34, 51, 25, 57, 170, 125, 232, 81, 212, 81, 26, 240, 86, 10, 183, 79, 94, 110, 105, 32, 168, 175, 255, 246, 110, 67, 125, 67, 119, 67, 195, 39, 32, 13, 223, 105, 185, 98, 207, 38, 205, 128, 196, 97, 191, 208, 114, 151, 180, 231, 236, 5, 84, 138, 191, 192, 68, 209, 169, 200, 0, 56, 215, 23, 16, 145, 53, 72, 83, 1, 130, 10, 115, 250, 52, 62, 124, 136, 40, 204, 57, 157, 156, 19, 133, 55, 184, 57, 31, 50, 2, 35, 131, 231, 105, 158, 72, 205, 160, 0, 28, 167, 215, 187, 221, 140, 27, 93, 20, 192, 34, 248, 86, 92, 237, 107, 171, 247, 86, 83, 87, 93, 73, 171, 43, 207, 86, 30, 174, 185, 50, 215, 86, 238, 173, 152, 8, 199, 248, 59, 43, 42, 95, 89, 147, 236, 237, 50, 87, 230, 43, 219, 251, 212, 75, 99, 30, 97, 147, 191, 133, 0, 72, 134, 166, 178, 170, 68, 222, 147, 201, 47, 201, 205, 86, 61, 77, 211, 86, 228, 2, 43, 206, 0, 76, 72, 8, 139, 97, 103, 20, 155, 128, 192, 122, 16, 0, 1, 228, 127, 168, 47, 90, 60, 76, 15, 167, 247, 32, 243, 137, 148, 234, 194, 83, 158, 65, 91, 101, 169, 87, 72, 161, 252, 77, 57, 93, 57, 93, 179, 179, 179, 51, 57, 232, 204, 124, 190, 129, 222, 204, 236, 108, 87, 87, 23, 104, 70, 78, 100, 163, 107, 86, 181, 119, 116, 140, 41, 126, 39, 107, 175, 95, 192, 166, 94, 63, 58, 122, 26, 5, 130, 143, 94, 123, 79, 226, 13, 42, 255, 43, 123, 253, 133, 107, 3, 164, 189, 100, 47, 208, 211, 125, 101, 38, 218, 218, 247, 192, 126, 135, 236, 170, 242, 126, 140, 61, 62, 14, 144, 120, 13, 96, 154, 253, 142, 198, 135, 103, 201, 138, 231, 121, 31, 89, 244, 62, 159, 158, 113, 115, 42, 120, 138, 230, 69, 228, 128, 56, 245, 233, 245, 88, 252, 10, 67, 196, 184, 25, 254, 98, 111, 251, 197, 149, 139, 43, 166, 181, 146, 162, 181, 139, 171, 79, 238, 149, 180, 103, 150, 172, 148, 172, 188, 179, 86, 210, 187, 182, 246, 228, 171, 135, 176, 203, 188, 168, 253, 98, 9, 177, 131, 175, 41, 118, 50, 31, 251, 252, 212, 252, 97, 240, 74, 21, 17, 116, 11, 135, 187, 229, 249, 214, 175, 255, 6, 173, 243, 203, 207, 151, 150, 182, 90, 167, 14, 15, 171, 174, 92, 153, 47, 223, 202, 191, 162, 102, 0, 179, 57, 57, 221, 156, 158, 14, 129, 101, 158, 174, 242, 184, 221, 44, 241, 116, 140, 19, 110, 77, 54, 91, 193, 227, 55, 122, 124, 60, 109, 16, 69, 228, 250, 16, 199, 196, 104, 103, 216, 226, 193, 102, 136, 213, 143, 172, 32, 138, 177, 48, 227, 102, 169, 104, 24, 89, 143, 210, 184, 248, 144, 7, 110, 139, 162, 143, 227, 153, 105, 239, 224, 224, 173, 193, 137, 207, 111, 117, 118, 222, 186, 117, 203, 214, 217, 217, 137, 205, 190, 50, 226, 0, 233, 36, 95, 241, 45, 213, 238, 157, 102, 20, 191, 228, 218, 238, 235, 45, 45, 163, 192, 137, 235, 39, 186, 71, 71, 7, 208, 130, 163, 115, 226, 68, 156, 95, 79, 228, 45, 45, 215, 200, 252, 22, 240, 129, 131, 90, 123, 25, 46, 84, 5, 38, 200, 218, 103, 127, 37, 75, 125, 4, 192, 141, 59, 17, 234, 235, 64, 214, 43, 26, 31, 30, 65, 45, 98, 201, 139, 162, 91, 228, 13, 28, 13, 149, 17, 253, 60, 15, 125, 221, 34, 99, 160, 121, 100, 123, 66, 25, 172, 124, 222, 96, 48, 200, 210, 147, 13, 131, 241, 132, 60, 78, 190, 8, 88, 91, 93, 121, 88, 212, 158, 89, 212, 139, 181, 93, 212, 219, 94, 116, 113, 69, 230, 69, 43, 171, 15, 139, 138, 122, 101, 158, 169, 216, 21, 174, 204, 71, 0, 4, 183, 231, 131, 175, 255, 178, 173, 83, 240, 234, 223, 162, 92, 170, 229, 89, 219, 135, 83, 135, 243, 82, 0, 144, 68, 110, 77, 182, 146, 245, 107, 166, 205, 68, 96, 104, 175, 112, 43, 120, 162, 61, 206, 105, 179, 149, 78, 183, 18, 206, 84, 120, 219, 110, 224, 70, 47, 93, 234, 41, 39, 34, 0, 91, 128, 208, 147, 231, 20, 88, 40, 111, 9, 176, 216, 9, 48, 132, 220, 47, 244, 232, 61, 56, 254, 68, 79, 105, 93, 242, 10, 39, 135, 39, 11, 5, 31, 191, 243, 168, 233, 124, 70, 115, 211, 240, 70, 78, 14, 22, 124, 14, 54, 122, 130, 156, 73, 194, 113, 22, 0, 237, 80, 237, 56, 4, 40, 126, 201, 181, 242, 105, 254, 135, 3, 180, 155, 104, 54, 7, 128, 31, 84, 142, 90, 229, 90, 251, 230, 193, 230, 230, 15, 151, 223, 183, 23, 152, 252, 105, 5, 105, 38, 156, 233, 237, 126, 211, 85, 156, 251, 94, 147, 145, 117, 243, 38, 105, 110, 222, 204, 202, 66, 151, 212, 132, 194, 126, 213, 228, 215, 248, 80, 16, 156, 50, 240, 50, 240, 172, 133, 46, 118, 54, 3, 249, 0, 90, 178, 36, 218, 125, 232, 40, 118, 158, 242, 101, 102, 38, 65, 209, 181, 123, 189, 73, 73, 144, 248, 226, 51, 240, 246, 162, 146, 139, 132, 183, 99, 173, 223, 83, 248, 179, 246, 63, 216, 227, 188, 238, 233, 55, 186, 169, 43, 191, 232, 18, 244, 127, 253, 236, 235, 114, 171, 69, 249, 130, 60, 168, 76, 44, 15, 110, 205, 95, 145, 3, 0, 43, 153, 54, 211, 16, 84, 111, 85, 4, 142, 115, 243, 63, 115, 114, 163, 183, 225, 70, 47, 93, 234, 245, 56, 4, 135, 221, 183, 123, 98, 28, 229, 20, 4, 162, 60, 138, 24, 102, 5, 247, 109, 150, 139, 113, 86, 198, 25, 198, 35, 64, 130, 203, 164, 195, 88, 225, 176, 113, 252, 206, 80, 51, 78, 129, 29, 35, 63, 99, 207, 7, 114, 200, 70, 128, 244, 255, 249, 12, 186, 224, 228, 56, 168, 218, 113, 8, 80, 252, 210, 229, 91, 0, 78, 244, 42, 112, 218, 63, 138, 191, 255, 62, 225, 240, 33, 221, 90, 251, 213, 178, 52, 192, 95, 80, 102, 178, 151, 73, 234, 103, 73, 56, 70, 144, 165, 66, 137, 2, 41, 6, 202, 52, 62, 124, 219, 196, 25, 64, 170, 164, 183, 0, 0, 111, 1, 120, 19, 136, 68, 34, 197, 33, 202, 56, 124, 148, 93, 100, 136, 160, 237, 69, 16, 56, 51, 41, 41, 169, 168, 228, 89, 102, 210, 113, 8, 91, 2, 30, 23, 188, 63, 179, 189, 168, 8, 227, 224, 170, 29, 173, 60, 191, 174, 46, 127, 111, 187, 234, 71, 141, 254, 144, 125, 123, 65, 17, 29, 93, 93, 249, 139, 0, 64, 165, 6, 192, 118, 213, 252, 148, 28, 0, 102, 4, 128, 222, 172, 215, 167, 91, 13, 86, 51, 4, 198, 226, 78, 224, 176, 255, 13, 79, 1, 143, 223, 232, 241, 133, 17, 210, 140, 59, 196, 136, 62, 168, 236, 36, 73, 49, 20, 134, 252, 140, 7, 3, 34, 75, 225, 12, 236, 193, 24, 147, 146, 224, 50, 108, 180, 25, 139, 75, 123, 248, 161, 230, 28, 236, 248, 51, 145, 159, 103, 103, 102, 103, 54, 54, 102, 102, 54, 102, 80, 205, 68, 54, 200, 182, 47, 67, 181, 255, 246, 219, 144, 226, 151, 124, 176, 121, 238, 0, 153, 29, 184, 180, 137, 106, 115, 115, 243, 224, 135, 209, 134, 1, 101, 0, 252, 104, 251, 57, 123, 181, 116, 199, 55, 225, 122, 103, 55, 225, 29, 72, 9, 128, 155, 47, 64, 98, 65, 142, 8, 168, 15, 84, 99, 178, 198, 135, 119, 68, 134, 231, 230, 10, 71, 114, 35, 197, 14, 199, 100, 161, 99, 206, 49, 55, 50, 60, 236, 40, 44, 116, 44, 142, 84, 56, 194, 6, 227, 226, 81, 118, 193, 167, 8, 235, 130, 176, 73, 88, 219, 174, 36, 210, 246, 190, 224, 104, 251, 147, 50, 123, 209, 246, 199, 185, 212, 130, 191, 33, 181, 193, 170, 170, 61, 221, 188, 70, 127, 162, 242, 246, 94, 121, 28, 191, 252, 24, 239, 189, 186, 176, 151, 16, 1, 243, 135, 219, 87, 242, 241, 14, 0, 193, 137, 160, 6, 125, 58, 157, 39, 11, 12, 158, 114, 4, 87, 231, 27, 192, 227, 55, 122, 233, 82, 175, 79, 17, 57, 134, 245, 240, 12, 142, 249, 56, 248, 246, 68, 125, 12, 242, 29, 118, 5, 214, 227, 140, 50, 20, 50, 64, 8, 123, 100, 130, 203, 122, 167, 173, 120, 208, 38, 224, 5, 49, 80, 234, 197, 45, 255, 22, 128, 109, 126, 189, 176, 211, 6, 122, 203, 139, 55, 42, 140, 13, 174, 99, 239, 87, 237, 129, 216, 180, 226, 151, 254, 230, 192, 193, 102, 119, 125, 253, 232, 64, 195, 165, 205, 6, 73, 232, 131, 131, 238, 122, 72, 46, 13, 64, 233, 163, 237, 231, 236, 210, 219, 14, 17, 179, 0, 1, 128, 45, 0, 73, 63, 158, 1, 226, 145, 160, 52, 202, 195, 224, 213, 52, 147, 198, 135, 155, 200, 205, 205, 157, 112, 20, 230, 26, 141, 72, 74, 70, 71, 133, 99, 114, 125, 110, 206, 49, 57, 89, 17, 41, 54, 230, 218, 124, 198, 163, 237, 92, 102, 127, 63, 66, 160, 68, 18, 180, 95, 106, 143, 203, 252, 56, 105, 33, 52, 177, 183, 19, 254, 6, 214, 190, 198, 254, 134, 60, 31, 15, 123, 186, 189, 229, 132, 0, 40, 255, 229, 117, 18, 0, 103, 37, 28, 78, 157, 85, 80, 254, 234, 217, 5, 100, 131, 23, 1, 176, 60, 181, 157, 159, 191, 172, 35, 91, 185, 213, 156, 98, 165, 176, 168, 243, 104, 235, 201, 100, 112, 218, 108, 80, 185, 106, 87, 185, 108, 167, 9, 143, 223, 232, 165, 75, 189, 149, 134, 222, 140, 207, 71, 78, 191, 228, 234, 195, 249, 242, 24, 188, 215, 136, 12, 206, 108, 56, 18, 91, 41, 222, 201, 167, 232, 19, 92, 214, 177, 146, 141, 1, 129, 55, 13, 53, 159, 199, 219, 159, 148, 253, 207, 55, 63, 254, 124, 227, 124, 51, 217, 237, 113, 3, 232, 192, 80, 211, 250, 70, 78, 134, 106, 31, 218, 25, 83, 252, 146, 55, 187, 27, 54, 175, 159, 56, 241, 222, 123, 23, 26, 186, 47, 92, 184, 208, 221, 221, 61, 122, 185, 69, 226, 239, 97, 0, 252, 104, 251, 166, 253, 21, 136, 89, 150, 102, 74, 131, 152, 126, 146, 2, 228, 55, 95, 41, 16, 0, 116, 37, 34, 63, 9, 161, 75, 18, 128, 95, 227, 195, 219, 32, 112, 33, 86, 180, 17, 176, 121, 109, 165, 197, 19, 197, 54, 163, 163, 112, 124, 206, 104, 204, 117, 176, 60, 91, 124, 164, 221, 167, 4, 64, 99, 251, 113, 109, 0, 128, 191, 241, 103, 126, 252, 47, 248, 82, 93, 157, 110, 175, 85, 13, 0, 232, 191, 189, 189, 32, 75, 190, 176, 160, 107, 221, 221, 154, 90, 88, 144, 249, 143, 103, 23, 206, 106, 82, 192, 214, 182, 46, 63, 127, 73, 151, 46, 9, 14, 129, 83, 172, 6, 73, 224, 191, 227, 250, 63, 241, 20, 194, 227, 55, 122, 233, 82, 143, 164, 160, 55, 32, 52, 204, 228, 85, 152, 210, 227, 168, 232, 99, 56, 3, 71, 46, 254, 100, 144, 51, 248, 120, 218, 108, 78, 112, 137, 224, 215, 209, 201, 122, 168, 212, 33, 236, 242, 205, 242, 61, 191, 163, 185, 99, 16, 135, 189, 89, 114, 241, 195, 230, 15, 144, 119, 128, 46, 213, 142, 119, 0, 197, 239, 100, 195, 233, 19, 245, 95, 156, 126, 235, 90, 75, 203, 245, 107, 31, 124, 114, 250, 244, 181, 211, 215, 47, 17, 126, 173, 229, 52, 6, 62, 0, 63, 210, 222, 253, 34, 0, 10, 144, 1, 208, 81, 30, 1, 101, 189, 81, 200, 27, 144, 2, 105, 244, 42, 153, 172, 241, 225, 45, 53, 70, 99, 77, 155, 49, 183, 6, 1, 137, 82, 83, 83, 72, 66, 179, 56, 183, 56, 23, 6, 11, 229, 245, 30, 101, 23, 248, 204, 227, 100, 133, 95, 108, 236, 85, 2, 0, 43, 95, 230, 68, 224, 164, 56, 47, 105, 44, 58, 14, 46, 165, 254, 23, 246, 76, 98, 175, 11, 34, 0, 14, 53, 9, 160, 252, 245, 41, 221, 222, 130, 132, 195, 165, 186, 231, 243, 243, 203, 75, 173, 58, 137, 150, 255, 120, 118, 79, 147, 2, 90, 73, 0, 212, 225, 12, 64, 67, 80, 61, 125, 202, 64, 231, 17, 65, 95, 150, 199, 47, 245, 248, 112, 77, 212, 83, 228, 137, 131, 227, 83, 12, 6, 250, 20, 165, 103, 98, 177, 168, 65, 207, 112, 24, 226, 156, 24, 211, 51, 61, 62, 67, 130, 71, 172, 112, 176, 179, 216, 24, 72, 161, 30, 237, 12, 61, 126, 252, 184, 9, 39, 127, 212, 143, 187, 6, 103, 155, 177, 242, 59, 240, 248, 135, 15, 134, 225, 217, 199, 205, 170, 253, 81, 83, 84, 241, 59, 121, 238, 221, 55, 235, 223, 235, 238, 190, 208, 221, 112, 105, 160, 1, 200, 126, 247, 220, 165, 19, 18, 207, 190, 156, 77, 248, 209, 246, 115, 74, 0, 244, 21, 64, 76, 123, 31, 185, 239, 43, 240, 227, 35, 144, 250, 104, 20, 96, 70, 159, 214, 135, 43, 245, 122, 219, 218, 108, 228, 137, 186, 173, 56, 183, 173, 166, 198, 152, 107, 68, 85, 92, 152, 91, 83, 147, 107, 20, 168, 26, 227, 81, 246, 48, 15, 129, 251, 33, 168, 11, 130, 246, 247, 35, 229, 107, 56, 2, 64, 226, 73, 132, 23, 29, 127, 59, 169, 191, 55, 193, 158, 73, 236, 85, 193, 160, 78, 247, 181, 54, 1, 252, 56, 117, 118, 79, 146, 159, 252, 127, 223, 242, 210, 214, 242, 210, 210, 210, 124, 57, 134, 94, 253, 229, 207, 1, 240, 148, 4, 192, 239, 148, 91, 255, 79, 27, 101, 28, 230, 128, 187, 246, 174, 135, 133, 170, 244, 148, 163, 53, 218, 150, 210, 98, 241, 228, 59, 165, 114, 96, 169, 89, 104, 17, 106, 80, 70, 19, 86, 167, 2, 155, 108, 50, 82, 45, 243, 7, 148, 57, 77, 96, 166, 6, 28, 33, 154, 232, 22, 13, 9, 26, 75, 8, 89, 76, 150, 184, 68, 51, 141, 250, 95, 249, 188, 239, 189, 208, 183, 106, 78, 251, 82, 202, 61, 247, 185, 207, 15, 203, 243, 220, 251, 126, 190, 205, 129, 222, 157, 83, 144, 234, 5, 103, 61, 206, 248, 26, 49, 73, 234, 175, 200, 154, 106, 117, 247, 197, 143, 3, 87, 60, 234, 160, 44, 155, 249, 88, 108, 80, 20, 34, 38, 58, 30, 201, 100, 114, 236, 138, 199, 149, 188, 99, 170, 249, 128, 71, 172, 246, 184, 116, 205, 125, 41, 170, 92, 18, 62, 144, 223, 252, 26, 235, 78, 76, 47, 20, 116, 19, 95, 102, 193, 192, 105, 127, 227, 235, 2, 150, 142, 5, 227, 153, 93, 147, 231, 153, 223, 0, 66, 252, 56, 214, 100, 60, 187, 144, 197, 53, 141, 248, 1, 129, 179, 36, 206, 7, 182, 183, 191, 7, 50, 151, 186, 187, 218, 186, 90, 186, 222, 123, 18, 233, 255, 251, 220, 250, 140, 45, 122, 113, 106, 65, 129, 224, 73, 206, 7, 2, 232, 195, 230, 78, 90, 20, 70, 76, 199, 85, 32, 144, 143, 200, 201, 64, 113, 139, 188, 227, 41, 151, 166, 218, 217, 195, 174, 6, 16, 223, 64, 130, 64, 38, 128, 6, 16, 14, 252, 74, 67, 7, 9, 242, 24, 134, 253, 245, 53, 224, 231, 56, 59, 4, 210, 10, 12, 150, 193, 101, 181, 0, 122, 166, 143, 142, 142, 118, 209, 26, 44, 101, 14, 118, 183, 15, 74, 100, 29, 30, 148, 111, 182, 255, 155, 0, 6, 240, 70, 59, 31, 113, 72, 167, 132, 214, 138, 69, 35, 178, 101, 140, 169, 102, 108, 76, 211, 54, 156, 226, 188, 17, 209, 12, 83, 71, 9, 120, 115, 204, 163, 8, 90, 90, 147, 175, 120, 81, 7, 210, 60, 138, 126, 37, 47, 107, 145, 13, 177, 218, 67, 91, 95, 159, 89, 127, 211, 35, 160, 25, 72, 234, 254, 51, 56, 235, 73, 6, 248, 24, 78, 252, 55, 158, 126, 236, 27, 228, 253, 0, 88, 184, 81, 177, 35, 8, 96, 126, 3, 241, 222, 222, 149, 151, 125, 215, 227, 96, 149, 142, 123, 156, 199, 129, 255, 242, 228, 245, 108, 54, 158, 157, 36, 120, 209, 222, 190, 208, 246, 68, 87, 203, 139, 77, 67, 205, 221, 52, 6, 120, 246, 9, 26, 255, 177, 245, 213, 39, 95, 125, 242, 204, 39, 103, 31, 43, 44, 124, 226, 217, 166, 110, 206, 71, 201, 231, 101, 53, 96, 170, 88, 122, 204, 12, 168, 129, 216, 58, 244, 175, 229, 147, 6, 174, 229, 84, 52, 25, 176, 179, 19, 1, 76, 172, 53, 92, 166, 132, 174, 89, 132, 247, 87, 4, 128, 55, 158, 18, 13, 220, 218, 64, 132, 82, 193, 107, 244, 57, 178, 3, 28, 35, 158, 227, 5, 80, 119, 243, 193, 244, 46, 153, 18, 0, 237, 153, 220, 201, 1, 21, 64, 230, 24, 92, 79, 67, 0, 195, 103, 2, 216, 174, 59, 59, 2, 130, 140, 80, 18, 213, 215, 138, 69, 195, 35, 107, 248, 12, 122, 220, 121, 175, 224, 23, 55, 81, 250, 217, 28, 67, 17, 72, 115, 167, 20, 65, 246, 186, 195, 170, 223, 31, 37, 229, 208, 188, 199, 99, 70, 198, 174, 40, 213, 30, 51, 235, 133, 130, 49, 230, 14, 254, 240, 238, 27, 175, 62, 246, 6, 242, 124, 196, 125, 8, 254, 240, 75, 170, 192, 223, 160, 14, 96, 97, 40, 160, 98, 71, 16, 192, 252, 26, 227, 35, 43, 189, 8, 238, 38, 23, 124, 83, 83, 100, 95, 31, 177, 112, 22, 251, 251, 212, 212, 226, 34, 176, 173, 61, 14, 1, 224, 109, 238, 198, 46, 15, 1, 160, 193, 79, 233, 255, 132, 172, 175, 176, 200, 95, 64, 246, 77, 51, 3, 60, 211, 205, 249, 40, 121, 85, 150, 85, 35, 169, 235, 224, 87, 27, 84, 205, 128, 174, 27, 114, 36, 57, 24, 145, 251, 140, 136, 219, 159, 87, 237, 236, 41, 215, 90, 255, 4, 61, 227, 59, 58, 250, 59, 24, 225, 32, 158, 17, 12, 188, 198, 112, 3, 47, 0, 96, 60, 79, 241, 120, 9, 2, 40, 85, 4, 128, 232, 255, 230, 205, 195, 196, 241, 49, 222, 255, 82, 34, 179, 157, 40, 67, 0, 184, 232, 60, 30, 127, 48, 141, 40, 176, 34, 128, 93, 34, 128, 113, 8, 64, 2, 161, 78, 74, 168, 244, 56, 206, 248, 26, 177, 162, 33, 169, 199, 71, 118, 167, 69, 20, 2, 162, 145, 141, 52, 184, 79, 123, 82, 125, 110, 12, 0, 132, 231, 195, 200, 0, 253, 225, 13, 76, 3, 68, 60, 152, 21, 72, 223, 19, 170, 61, 54, 117, 181, 96, 70, 220, 206, 47, 223, 37, 111, 248, 91, 51, 63, 189, 6, 182, 73, 221, 223, 234, 3, 96, 7, 56, 91, 103, 118, 164, 9, 95, 50, 191, 198, 201, 145, 145, 101, 20, 123, 125, 241, 217, 89, 196, 248, 83, 190, 81, 134, 39, 103, 87, 200, 13, 96, 91, 123, 150, 8, 160, 133, 30, 249, 45, 109, 221, 75, 100, 12, 136, 203, 1, 177, 174, 158, 150, 4, 172, 2, 1, 73, 11, 150, 186, 57, 31, 37, 34, 203, 131, 42, 50, 58, 44, 109, 48, 105, 152, 6, 94, 237, 193, 136, 38, 203, 30, 57, 137, 127, 187, 166, 217, 217, 189, 46, 164, 119, 52, 173, 3, 225, 253, 132, 112, 14, 83, 130, 25, 134, 0, 58, 58, 128, 121, 59, 197, 23, 47, 94, 172, 171, 251, 174, 135, 23, 64, 251, 118, 230, 24, 2, 32, 188, 39, 114, 219, 185, 109, 114, 65, 238, 140, 151, 14, 167, 57, 1, 32, 118, 184, 216, 121, 17, 71, 128, 228, 8, 62, 18, 116, 16, 66, 29, 32, 180, 86, 76, 147, 122, 124, 172, 238, 190, 226, 239, 195, 85, 159, 59, 148, 74, 141, 133, 253, 94, 69, 67, 237, 95, 150, 177, 15, 160, 237, 49, 56, 239, 198, 191, 250, 158, 171, 218, 227, 142, 170, 22, 244, 65, 183, 243, 158, 28, 33, 85, 126, 83, 215, 205, 2, 14, 252, 153, 152, 78, 32, 249, 210, 222, 49, 129, 191, 54, 223, 169, 216, 53, 249, 99, 230, 215, 232, 187, 189, 58, 58, 58, 50, 218, 187, 56, 55, 135, 205, 126, 118, 97, 231, 54, 66, 255, 145, 85, 130, 151, 113, 99, 225, 174, 189, 253, 66, 219, 139, 32, 179, 185, 9, 239, 51, 201, 2, 80, 8, 160, 60, 179, 93, 128, 189, 244, 156, 32, 80, 6, 0, 247, 156, 143, 130, 169, 133, 136, 150, 207, 35, 196, 87, 53, 36, 118, 102, 192, 72, 98, 192, 134, 200, 98, 44, 34, 135, 132, 100, 196, 206, 46, 10, 148, 208, 142, 231, 176, 229, 227, 130, 198, 2, 192, 13, 28, 38, 246, 53, 224, 53, 8, 224, 121, 138, 217, 243, 120, 16, 184, 19, 181, 156, 186, 241, 170, 29, 160, 174, 19, 107, 60, 81, 42, 239, 150, 15, 114, 88, 100, 8, 40, 65, 53, 241, 41, 39, 128, 251, 112, 66, 37, 16, 2, 112, 56, 130, 143, 7, 29, 65, 16, 90, 255, 120, 253, 64, 173, 152, 38, 245, 248, 88, 221, 253, 168, 216, 151, 10, 167, 250, 194, 161, 116, 200, 19, 246, 250, 157, 41, 24, 32, 246, 107, 27, 233, 49, 151, 28, 118, 147, 182, 176, 80, 237, 49, 163, 26, 58, 217, 1, 186, 81, 7, 192, 20, 16, 246, 122, 164, 126, 164, 14, 240, 24, 174, 201, 194, 206, 207, 234, 0, 21, 251, 187, 63, 220, 179, 252, 32, 128, 85, 74, 240, 236, 212, 202, 202, 236, 108, 239, 236, 133, 157, 51, 220, 75, 110, 92, 184, 251, 31, 246, 54, 228, 114, 75, 205, 205, 148, 204, 230, 230, 39, 90, 172, 172, 31, 139, 85, 128, 168, 28, 172, 111, 90, 13, 192, 19, 205, 205, 156, 143, 226, 65, 20, 40, 39, 147, 154, 22, 145, 35, 129, 88, 204, 68, 206, 167, 17, 134, 7, 17, 11, 123, 68, 193, 144, 237, 237, 173, 19, 253, 173, 148, 224, 254, 142, 9, 26, 236, 157, 225, 53, 75, 0, 12, 67, 16, 148, 112, 238, 121, 138, 49, 219, 247, 119, 1, 124, 250, 203, 241, 113, 2, 212, 147, 117, 82, 42, 179, 33, 1, 164, 131, 200, 4, 254, 38, 128, 78, 34, 0, 39, 8, 21, 28, 65, 39, 8, 173, 7, 193, 53, 98, 154, 212, 227, 99, 117, 247, 21, 66, 109, 202, 29, 14, 207, 167, 210, 94, 175, 75, 192, 32, 72, 74, 69, 79, 8, 91, 128, 203, 157, 38, 205, 193, 176, 80, 237, 113, 199, 48, 76, 21, 125, 221, 90, 231, 1, 152, 95, 227, 203, 123, 119, 111, 223, 94, 221, 25, 237, 221, 255, 124, 127, 103, 111, 100, 225, 243, 189, 29, 224, 189, 209, 149, 253, 223, 62, 255, 124, 103, 15, 216, 214, 126, 225, 86, 11, 206, 243, 38, 144, 217, 60, 116, 203, 154, 9, 68, 178, 143, 197, 74, 0, 86, 93, 200, 250, 38, 192, 154, 9, 228, 124, 92, 164, 217, 21, 198, 129, 70, 6, 95, 240, 86, 147, 191, 214, 239, 88, 31, 8, 118, 169, 246, 118, 1, 233, 220, 235, 56, 228, 95, 105, 157, 176, 4, 240, 47, 184, 149, 225, 254, 42, 59, 195, 24, 2, 28, 30, 230, 142, 128, 30, 8, 96, 248, 4, 172, 111, 39, 208, 248, 61, 192, 65, 80, 134, 12, 18, 248, 194, 226, 5, 112, 8, 1, 116, 82, 1, 4, 81, 226, 125, 193, 225, 4, 161, 14, 16, 90, 43, 166, 89, 189, 163, 30, 139, 140, 4, 68, 189, 96, 219, 35, 162, 229, 67, 218, 0, 46, 12, 0, 132, 61, 233, 116, 122, 67, 76, 137, 65, 244, 133, 241, 147, 86, 170, 61, 76, 67, 213, 213, 136, 71, 170, 117, 30, 128, 249, 53, 198, 241, 26, 159, 159, 122, 121, 225, 194, 36, 82, 189, 184, 111, 97, 110, 118, 118, 133, 97, 68, 250, 241, 133, 21, 123, 59, 221, 1, 90, 192, 100, 87, 243, 16, 29, 178, 163, 117, 128, 74, 254, 223, 132, 31, 235, 155, 221, 34, 3, 1, 188, 143, 32, 186, 49, 241, 64, 150, 7, 139, 94, 184, 233, 31, 210, 23, 71, 228, 163, 216, 219, 133, 203, 120, 163, 17, 253, 145, 55, 27, 127, 144, 223, 243, 248, 185, 106, 188, 134, 146, 48, 135, 251, 241, 60, 4, 144, 57, 58, 202, 124, 218, 206, 11, 96, 151, 16, 94, 78, 148, 118, 115, 101, 75, 0, 136, 7, 115, 219, 128, 15, 42, 2, 184, 127, 136, 81, 50, 171, 29, 236, 4, 161, 130, 36, 5, 165, 160, 163, 94, 26, 168, 21, 179, 230, 62, 25, 7, 144, 208, 11, 194, 209, 238, 13, 161, 27, 24, 37, 100, 187, 232, 0, 128, 12, 17, 96, 250, 201, 149, 118, 211, 21, 18, 156, 156, 143, 32, 72, 161, 208, 181, 107, 215, 164, 90, 231, 1, 152, 95, 227, 133, 5, 148, 130, 72, 98, 63, 153, 197, 66, 178, 143, 121, 63, 223, 36, 193, 132, 97, 130, 236, 237, 183, 90, 134, 40, 153, 221, 205, 93, 183, 174, 98, 26, 0, 249, 190, 221, 34, 147, 249, 95, 92, 229, 124, 156, 81, 50, 239, 19, 10, 133, 188, 33, 186, 232, 37, 22, 253, 163, 144, 166, 175, 141, 93, 112, 56, 159, 155, 152, 56, 45, 248, 80, 1, 0, 183, 242, 152, 183, 175, 33, 24, 252, 251, 243, 227, 7, 165, 225, 225, 76, 207, 253, 202, 17, 208, 158, 40, 157, 36, 78, 64, 248, 9, 217, 6, 200, 62, 144, 43, 131, 252, 93, 136, 224, 32, 247, 175, 2, 112, 224, 101, 150, 176, 167, 59, 37, 16, 90, 43, 102, 19, 254, 116, 28, 192, 245, 130, 55, 21, 86, 196, 176, 40, 138, 130, 55, 37, 94, 18, 104, 251, 15, 196, 123, 194, 110, 209, 137, 214, 112, 218, 157, 34, 35, 195, 188, 79, 145, 252, 119, 138, 80, 84, 170, 117, 30, 128, 249, 53, 46, 248, 226, 83, 139, 189, 200, 237, 125, 113, 36, 122, 83, 190, 73, 95, 252, 91, 11, 95, 207, 250, 8, 182, 183, 199, 111, 117, 49, 50, 155, 134, 110, 13, 53, 33, 4, 188, 202, 181, 131, 89, 78, 200, 190, 105, 22, 128, 48, 176, 105, 136, 243, 193, 140, 135, 224, 114, 249, 21, 58, 20, 134, 206, 63, 126, 48, 2, 2, 12, 222, 21, 1, 86, 135, 195, 214, 126, 185, 159, 16, 254, 252, 41, 161, 175, 216, 224, 53, 34, 128, 203, 56, 25, 78, 49, 73, 31, 51, 39, 199, 200, 2, 234, 56, 1, 220, 204, 36, 202, 224, 158, 45, 118, 241, 61, 84, 176, 157, 43, 149, 218, 153, 0, 224, 144, 169, 8, 128, 100, 116, 82, 48, 40, 73, 14, 231, 64, 173, 152, 77, 248, 211, 222, 190, 224, 10, 123, 67, 50, 233, 6, 186, 132, 144, 232, 143, 10, 100, 0, 64, 241, 98, 26, 68, 220, 80, 36, 247, 252, 198, 124, 26, 246, 123, 2, 239, 83, 140, 233, 102, 44, 114, 73, 170, 117, 30, 128, 249, 53, 102, 123, 95, 158, 26, 165, 181, 126, 223, 249, 185, 229, 57, 66, 237, 245, 213, 145, 145, 145, 185, 185, 185, 201, 243, 203, 203, 192, 182, 246, 169, 182, 46, 244, 246, 201, 14, 143, 237, 188, 11, 13, 95, 139, 122, 16, 78, 170, 0, 214, 21, 126, 153, 28, 240, 75, 30, 233, 226, 124, 132, 36, 198, 192, 139, 31, 190, 132, 86, 191, 22, 9, 123, 157, 169, 245, 34, 254, 219, 207, 157, 153, 98, 113, 125, 253, 70, 44, 86, 196, 178, 177, 135, 157, 151, 241, 198, 247, 91, 77, 158, 14, 66, 40, 193, 29, 167, 248, 178, 133, 215, 56, 194, 33, 8, 8, 128, 217, 241, 92, 230, 164, 68, 130, 192, 67, 46, 8, 60, 57, 41, 151, 183, 31, 244, 28, 254, 241, 227, 207, 116, 253, 248, 115, 251, 195, 115, 231, 126, 111, 207, 229, 118, 15, 206, 118, 128, 195, 158, 204, 240, 176, 37, 0, 201, 65, 42, 251, 82, 80, 144, 160, 215, 129, 90, 49, 107, 238, 211, 222, 190, 43, 26, 158, 247, 134, 201, 128, 180, 232, 199, 169, 175, 208, 29, 64, 196, 64, 152, 172, 136, 116, 7, 240, 166, 96, 247, 59, 120, 159, 27, 5, 67, 143, 245, 133, 164, 90, 231, 1, 152, 95, 227, 228, 114, 239, 212, 14, 77, 245, 102, 151, 193, 235, 50, 146, 187, 184, 133, 71, 113, 3, 216, 222, 190, 216, 134, 168, 110, 105, 168, 137, 205, 3, 44, 209, 58, 0, 227, 219, 90, 213, 91, 192, 85, 218, 14, 230, 124, 4, 217, 64, 108, 31, 211, 241, 83, 220, 52, 20, 101, 230, 134, 169, 227, 198, 86, 12, 121, 191, 138, 101, 154, 118, 246, 148, 179, 181, 18, 245, 119, 240, 81, 63, 240, 68, 53, 238, 176, 48, 13, 10, 25, 38, 89, 192, 201, 248, 33, 25, 8, 225, 4, 240, 75, 121, 247, 1, 24, 7, 231, 191, 62, 69, 214, 175, 211, 191, 159, 35, 235, 225, 247, 185, 7, 149, 32, 112, 26, 177, 3, 19, 128, 228, 168, 23, 112, 162, 35, 178, 7, 161, 181, 98, 214, 220, 167, 57, 61, 98, 0, 28, 0, 243, 34, 102, 1, 200, 176, 31, 219, 1, 208, 8, 130, 40, 4, 178, 3, 136, 196, 46, 10, 188, 79, 209, 40, 80, 1, 212, 58, 15, 192, 252, 26, 227, 203, 179, 127, 39, 56, 187, 179, 202, 9, 192, 222, 190, 72, 231, 1, 134, 134, 104, 111, 159, 9, 128, 74, 128, 105, 128, 95, 168, 5, 49, 1, 112, 62, 46, 36, 247, 235, 49, 29, 33, 105, 192, 48, 84, 57, 90, 4, 175, 1, 253, 198, 250, 86, 192, 140, 21, 205, 88, 44, 96, 216, 217, 35, 2, 31, 213, 243, 81, 63, 143, 237, 178, 132, 76, 57, 113, 120, 120, 204, 9, 0, 169, 126, 174, 231, 232, 156, 181, 168, 2, 134, 31, 50, 244, 48, 87, 87, 17, 0, 191, 3, 212, 215, 35, 22, 11, 58, 41, 161, 181, 98, 214, 220, 167, 57, 189, 243, 5, 17, 9, 158, 31, 140, 147, 227, 205, 47, 90, 3, 0, 136, 146, 188, 225, 168, 224, 220, 240, 207, 207, 83, 187, 139, 247, 153, 49, 140, 194, 86, 228, 154, 84, 235, 60, 0, 243, 107, 156, 4, 193, 86, 174, 127, 126, 142, 240, 11, 194, 23, 24, 193, 184, 1, 108, 111, 159, 162, 157, 61, 196, 243, 132, 204, 238, 102, 212, 248, 185, 113, 128, 39, 173, 185, 64, 130, 172, 44, 144, 124, 183, 116, 53, 119, 115, 62, 81, 85, 207, 231, 245, 226, 140, 142, 247, 89, 203, 143, 41, 49, 195, 76, 198, 102, 2, 250, 86, 236, 195, 117, 20, 127, 243, 73, 221, 206, 30, 240, 219, 100, 1, 253, 255, 7, 163, 29, 124, 19, 51, 129, 188, 0, 208, 248, 61, 199, 214, 239, 132, 255, 105, 6, 160, 0, 46, 13, 108, 175, 8, 64, 66, 123, 231, 5, 73, 64, 125, 223, 41, 53, 214, 138, 89, 115, 159, 230, 244, 24, 29, 165, 211, 0, 104, 245, 10, 103, 179, 0, 78, 88, 8, 66, 214, 40, 56, 168, 253, 81, 222, 167, 88, 48, 212, 66, 223, 37, 169, 214, 121, 0, 230, 247, 145, 111, 111, 100, 150, 230, 246, 183, 71, 239, 238, 239, 239, 237, 220, 221, 27, 93, 32, 120, 21, 185, 254, 237, 253, 125, 96, 91, 123, 175, 37, 0, 164, 246, 221, 40, 4, 225, 162, 133, 155, 7, 160, 223, 75, 184, 176, 190, 233, 93, 250, 48, 231, 163, 12, 170, 121, 82, 225, 75, 170, 168, 240, 229, 35, 174, 205, 0, 42, 84, 51, 91, 201, 27, 91, 96, 220, 32, 245, 63, 59, 123, 94, 249, 71, 22, 192, 227, 215, 255, 27, 103, 50, 56, 2, 58, 239, 87, 9, 96, 216, 218, 0, 142, 238, 99, 11, 32, 27, 192, 209, 193, 233, 142, 192, 247, 2, 144, 5, 92, 60, 219, 1, 164, 23, 28, 65, 66, 168, 163, 177, 86, 204, 186, 251, 180, 185, 31, 85, 68, 54, 12, 224, 16, 156, 252, 44, 64, 148, 220, 20, 211, 40, 18, 17, 187, 131, 247, 209, 99, 42, 136, 84, 164, 90, 231, 1, 152, 223, 64, 214, 151, 93, 156, 59, 143, 92, 127, 113, 225, 58, 162, 124, 52, 252, 227, 217, 57, 150, 251, 95, 39, 216, 222, 30, 103, 59, 192, 219, 93, 67, 116, 30, 128, 27, 8, 224, 230, 1, 184, 113, 0, 172, 183, 121, 31, 37, 111, 208, 254, 190, 25, 72, 230, 141, 124, 196, 191, 137, 126, 159, 161, 175, 231, 147, 120, 241, 49, 9, 148, 12, 152, 118, 118, 67, 225, 162, 124, 58, 232, 81, 43, 78, 36, 42, 71, 64, 59, 21, 192, 167, 71, 108, 207, 239, 76, 252, 114, 238, 207, 167, 126, 29, 126, 56, 126, 156, 251, 23, 1, 176, 74, 160, 69, 168, 227, 47, 82, 174, 173, 181, 137, 32, 10, 179, 105, 179, 217, 75, 109, 116, 75, 204, 66, 215, 198, 135, 90, 98, 42, 45, 193, 174, 198, 214, 224, 214, 75, 65, 98, 212, 70, 124, 240, 66, 141, 82, 170, 130, 138, 55, 52, 130, 136, 86, 95, 42, 34, 168, 136, 111, 234, 139, 160, 62, 84, 17, 41, 24, 16, 188, 65, 189, 252, 41, 191, 179, 59, 201, 206, 54, 237, 212, 169, 75, 141, 158, 156, 158, 7, 57, 103, 103, 230, 204, 247, 157, 15, 111, 180, 138, 159, 88, 187, 172, 205, 233, 3, 244, 107, 31, 71, 207, 19, 230, 233, 85, 173, 206, 120, 206, 2, 241, 205, 57, 239, 98, 66, 40, 121, 222, 241, 60, 175, 215, 234, 215, 109, 242, 231, 245, 120, 24, 5, 92, 63, 123, 201, 52, 84, 89, 62, 0, 139, 219, 10, 72, 159, 250, 124, 252, 169, 156, 240, 31, 80, 0, 96, 7, 120, 63, 61, 98, 63, 227, 3, 12, 118, 35, 153, 215, 232, 30, 96, 89, 62, 0, 221, 3, 112, 49, 113, 199, 241, 241, 253, 113, 218, 237, 171, 122, 124, 58, 111, 23, 192, 247, 177, 193, 5, 204, 15, 76, 15, 228, 167, 175, 139, 252, 158, 193, 117, 1, 62, 232, 35, 107, 99, 196, 23, 185, 228, 47, 2, 81, 0, 243, 65, 1, 196, 222, 238, 253, 78, 43, 192, 135, 216, 99, 86, 0, 60, 24, 228, 223, 4, 126, 160, 2, 160, 53, 157, 18, 74, 247, 251, 237, 178, 118, 220, 195, 173, 54, 211, 7, 208, 244, 164, 219, 235, 129, 240, 52, 122, 43, 145, 236, 197, 14, 55, 128, 69, 112, 252, 250, 64, 213, 234, 173, 213, 236, 209, 91, 253, 5, 139, 252, 187, 117, 35, 140, 114, 157, 90, 173, 54, 153, 84, 101, 249, 0, 44, 142, 248, 0, 67, 77, 188, 191, 4, 188, 159, 217, 19, 165, 146, 255, 197, 148, 200, 207, 248, 0, 96, 250, 119, 251, 96, 16, 248, 0, 56, 4, 178, 222, 111, 81, 62, 192, 5, 252, 198, 170, 85, 92, 76, 198, 9, 240, 125, 192, 125, 96, 125, 88, 153, 129, 14, 61, 192, 255, 123, 209, 165, 56, 14, 72, 0, 34, 127, 135, 113, 242, 230, 158, 182, 61, 109, 199, 155, 9, 149, 181, 139, 15, 139, 11, 11, 224, 1, 91, 1, 230, 135, 247, 98, 5, 88, 191, 217, 183, 130, 111, 120, 48, 232, 61, 182, 142, 141, 69, 20, 64, 34, 161, 166, 98, 236, 141, 78, 181, 203, 218, 241, 14, 96, 187, 129, 62, 128, 166, 38, 211, 73, 111, 58, 155, 213, 251, 226, 74, 102, 119, 213, 205, 213, 70, 45, 140, 67, 219, 192, 61, 157, 248, 45, 0, 130, 86, 150, 252, 24, 152, 9, 163, 6, 28, 59, 63, 57, 185, 79, 149, 229, 3, 176, 184, 173, 1, 31, 96, 106, 228, 68, 15, 224, 125, 194, 251, 153, 61, 17, 242, 1, 68, 254, 138, 95, 0, 24, 243, 68, 50, 193, 7, 96, 132, 16, 142, 14, 16, 229, 3, 192, 139, 147, 224, 32, 23, 99, 17, 190, 159, 3, 210, 87, 208, 93, 61, 87, 48, 243, 85, 93, 207, 3, 241, 155, 25, 96, 248, 191, 208, 175, 163, 0, 110, 182, 221, 68, 66, 219, 46, 19, 10, 136, 4, 75, 218, 197, 55, 101, 190, 0, 134, 185, 45, 128, 181, 1, 63, 30, 132, 135, 64, 174, 0, 94, 111, 188, 119, 47, 224, 3, 36, 84, 36, 20, 61, 29, 238, 118, 144, 80, 89, 219, 176, 155, 250, 0, 177, 78, 220, 248, 230, 60, 64, 28, 25, 179, 211, 114, 11, 73, 219, 209, 171, 118, 110, 95, 78, 7, 100, 106, 166, 51, 164, 15, 64, 254, 164, 18, 11, 163, 176, 72, 92, 237, 200, 101, 85, 89, 62, 0, 139, 219, 218, 224, 3, 84, 90, 240, 254, 33, 198, 7, 16, 250, 25, 31, 128, 144, 29, 172, 0, 212, 3, 240, 55, 129, 212, 251, 81, 247, 199, 62, 169, 41, 160, 62, 96, 21, 23, 163, 19, 190, 111, 131, 225, 131, 55, 59, 169, 187, 74, 85, 183, 114, 227, 104, 249, 166, 241, 193, 240, 127, 129, 127, 52, 67, 9, 221, 179, 150, 222, 232, 224, 112, 39, 107, 151, 223, 20, 35, 5, 240, 231, 246, 237, 217, 159, 7, 155, 25, 95, 143, 103, 243, 60, 179, 126, 225, 8, 208, 44, 128, 207, 167, 190, 126, 13, 10, 32, 22, 38, 52, 129, 4, 75, 218, 156, 62, 64, 66, 209, 251, 120, 125, 128, 172, 158, 183, 170, 118, 171, 62, 128, 158, 72, 133, 81, 51, 147, 29, 184, 245, 207, 170, 178, 124, 0, 22, 71, 124, 128, 139, 28, 222, 95, 225, 240, 127, 250, 2, 182, 208, 207, 248, 0, 116, 206, 227, 249, 0, 148, 255, 86, 62, 0, 10, 128, 241, 1, 194, 24, 61, 192, 247, 171, 142, 99, 143, 2, 239, 81, 242, 86, 210, 173, 226, 233, 245, 176, 253, 17, 254, 47, 244, 219, 153, 181, 91, 252, 132, 130, 10, 118, 179, 141, 246, 118, 89, 187, 88, 198, 105, 30, 63, 236, 12, 248, 187, 254, 173, 254, 109, 238, 211, 247, 70, 23, 56, 183, 179, 200, 42, 128, 182, 3, 20, 0, 183, 5, 220, 187, 199, 40, 97, 154, 138, 220, 169, 90, 130, 208, 157, 118, 89, 155, 211, 7, 136, 167, 250, 178, 255, 160, 15, 144, 61, 202, 233, 3, 52, 11, 64, 150, 15, 192, 226, 120, 62, 192, 16, 94, 234, 9, 14, 255, 39, 32, 16, 182, 208, 31, 160, 129, 171, 41, 155, 196, 7, 216, 181, 166, 161, 16, 180, 24, 31, 128, 92, 248, 141, 213, 171, 185, 152, 49, 123, 55, 210, 89, 160, 126, 175, 160, 143, 101, 180, 188, 110, 141, 234, 132, 255, 87, 109, 175, 134, 131, 191, 215, 33, 242, 187, 6, 37, 114, 15, 18, 185, 178, 191, 89, 1, 20, 155, 5, 48, 254, 174, 94, 175, 127, 157, 251, 50, 27, 100, 124, 125, 57, 118, 234, 39, 118, 129, 95, 243, 243, 191, 176, 27, 132, 132, 16, 60, 101, 182, 5, 16, 151, 63, 69, 239, 51, 75, 176, 164, 205, 235, 3, 196, 50, 201, 127, 208, 7, 24, 51, 34, 250, 0, 147, 118, 193, 117, 211, 210, 124, 0, 22, 119, 247, 134, 223, 211, 63, 218, 62, 244, 244, 201, 179, 151, 143, 206, 140, 80, 143, 207, 240, 127, 34, 0, 140, 60, 19, 251, 129, 6, 130, 219, 75, 171, 249, 96, 192, 7, 160, 134, 127, 73, 62, 0, 125, 71, 124, 0, 46, 198, 212, 221, 28, 246, 119, 59, 231, 230, 64, 118, 48, 18, 186, 78, 76, 151, 36, 150, 123, 11, 158, 42, 216, 192, 17, 191, 22, 245, 91, 166, 252, 155, 31, 181, 33, 248, 4, 165, 143, 176, 11, 124, 241, 238, 29, 10, 96, 110, 238, 203, 15, 186, 5, 244, 234, 40, 8, 84, 0, 123, 34, 5, 240, 16, 135, 64, 182, 5, 36, 192, 244, 6, 180, 151, 160, 132, 202, 218, 188, 62, 64, 103, 38, 189, 188, 62, 64, 214, 92, 160, 15, 80, 152, 68, 34, 165, 249, 0, 44, 174, 139, 227, 3, 140, 148, 64, 250, 15, 241, 254, 160, 217, 59, 39, 246, 79, 208, 10, 0, 181, 151, 109, 144, 125, 235, 190, 230, 203, 3, 8, 249, 0, 190, 64, 192, 53, 46, 70, 209, 173, 130, 149, 14, 30, 160, 31, 169, 180, 197, 224, 222, 116, 54, 75, 63, 174, 205, 249, 113, 12, 234, 75, 242, 126, 67, 249, 199, 189, 254, 208, 66, 123, 45, 179, 125, 78, 224, 43, 254, 16, 248, 251, 221, 28, 21, 128, 159, 113, 239, 247, 159, 111, 191, 215, 135, 5, 208, 66, 10, 61, 229, 23, 0, 46, 118, 124, 112, 159, 18, 42, 107, 71, 244, 1, 250, 247, 45, 175, 15, 144, 140, 234, 3, 116, 93, 50, 20, 165, 75, 149, 230, 3, 176, 184, 46, 226, 3, 148, 24, 222, 207, 16, 255, 10, 108, 116, 124, 61, 48, 48, 13, 32, 246, 19, 31, 128, 146, 73, 178, 127, 18, 124, 128, 48, 70, 27, 195, 28, 124, 156, 233, 3, 96, 90, 82, 33, 185, 160, 52, 73, 128, 208, 252, 127, 31, 254, 191, 161, 95, 73, 65, 47, 64, 49, 56, 191, 162, 201, 159, 254, 163, 54, 72, 61, 195, 195, 199, 162, 109, 224, 23, 20, 0, 75, 249, 44, 195, 3, 2, 84, 40, 40, 0, 86, 1, 247, 217, 77, 32, 45, 233, 248, 81, 240, 129, 127, 180, 203, 218, 192, 246, 29, 167, 161, 15, 96, 30, 93, 94, 31, 32, 109, 242, 33, 27, 156, 235, 211, 215, 199, 167, 115, 210, 124, 0, 22, 215, 21, 240, 1, 14, 55, 240, 126, 232, 255, 140, 4, 189, 126, 233, 6, 190, 128, 45, 246, 87, 192, 7, 128, 238, 15, 65, 252, 18, 124, 0, 46, 70, 91, 160, 15, 144, 208, 120, 125, 0, 56, 20, 206, 15, 22, 173, 166, 68, 252, 154, 124, 255, 31, 181, 139, 229, 226, 236, 236, 91, 86, 0, 236, 42, 152, 43, 128, 7, 84, 0, 209, 21, 160, 177, 4, 28, 99, 55, 129, 116, 168, 195, 15, 186, 56, 192, 59, 137, 118, 89, 59, 62, 147, 212, 11, 141, 97, 127, 205, 92, 94, 31, 160, 79, 139, 132, 140, 15, 92, 157, 169, 117, 200, 243, 1, 88, 92, 215, 200, 84, 105, 106, 251, 14, 244, 246, 135, 43, 83, 212, 215, 29, 153, 42, 53, 122, 253, 202, 254, 195, 176, 197, 254, 82, 247, 233, 0, 219, 39, 104, 119, 19, 154, 252, 86, 62, 0, 30, 158, 15, 128, 171, 130, 77, 92, 140, 38, 154, 255, 47, 100, 148, 177, 170, 192, 63, 166, 253, 239, 10, 0, 70, 208, 227, 225, 97, 20, 0, 191, 4, 252, 164, 29, 96, 145, 231, 117, 253, 15, 87, 0, 111, 1, 6, 81, 1, 116, 166, 40, 161, 154, 66, 24, 47, 18, 42, 107, 27, 182, 43, 167, 15, 208, 167, 69, 66, 94, 228, 39, 49, 230, 125, 86, 154, 15, 16, 196, 161, 0, 24, 225, 227, 92, 72, 248, 184, 2, 194, 7, 108, 255, 11, 216, 66, 127, 143, 175, 15, 0, 233, 55, 188, 205, 62, 28, 124, 97, 161, 54, 4, 221, 10, 242, 75, 0, 193, 193, 92, 140, 34, 156, 255, 31, 19, 235, 3, 120, 255, 191, 5, 20, 31, 71, 102, 3, 215, 137, 10, 224, 115, 189, 254, 237, 78, 164, 0, 78, 81, 1, 80, 91, 183, 97, 3, 37, 148, 222, 104, 89, 219, 116, 221, 112, 216, 95, 85, 143, 46, 171, 15, 96, 24, 8, 105, 209, 7, 80, 87, 172, 15, 48, 116, 99, 191, 127, 185, 3, 130, 15, 38, 128, 49, 247, 125, 224, 138, 111, 251, 95, 32, 225, 98, 127, 15, 227, 3, 12, 250, 112, 48, 154, 64, 26, 4, 65, 158, 151, 224, 3, 224, 3, 141, 224, 32, 23, 163, 8, 231, 255, 119, 199, 59, 132, 126, 67, 249, 223, 45, 160, 204, 10, 32, 50, 25, 18, 110, 1, 145, 231, 121, 29, 21, 112, 187, 89, 0, 179, 179, 97, 1, 104, 44, 161, 177, 118, 89, 27, 204, 111, 57, 125, 0, 83, 67, 72, 139, 62, 128, 186, 82, 125, 128, 30, 194, 251, 67, 194, 7, 141, 123, 76, 132, 132, 15, 216, 98, 255, 1, 134, 236, 53, 244, 1, 78, 55, 228, 1, 168, 10, 22, 227, 3, 208, 2, 48, 200, 197, 152, 121, 79, 48, 255, 159, 203, 136, 245, 1, 210, 166, 52, 8, 20, 181, 81, 0, 101, 42, 128, 232, 18, 176, 68, 1, 140, 35, 255, 88, 2, 22, 91, 1, 212, 32, 161, 24, 247, 150, 181, 241, 170, 135, 195, 254, 157, 137, 168, 62, 64, 87, 103, 127, 188, 63, 209, 111, 210, 29, 1, 193, 197, 208, 7, 128, 196, 55, 66, 90, 244, 1, 212, 149, 234, 3, 236, 111, 224, 253, 23, 183, 55, 240, 254, 74, 131, 251, 143, 47, 96, 11, 253, 71, 120, 129, 8, 94, 31, 128, 191, 12, 96, 159, 173, 250, 0, 84, 0, 85, 79, 164, 15, 144, 17, 235, 3, 196, 77, 121, 24, 56, 106, 67, 3, 150, 77, 7, 115, 140, 0, 236, 1, 97, 243, 31, 105, 2, 238, 32, 255, 45, 5, 160, 242, 9, 149, 181, 163, 250, 0, 154, 73, 162, 137, 88, 0, 144, 103, 208, 63, 250, 97, 65, 62, 50, 56, 246, 166, 240, 123, 153, 179, 40, 136, 72, 8, 155, 243, 87, 87, 172, 15, 208, 83, 154, 66, 175, 79, 120, 63, 208, 29, 28, 243, 9, 239, 63, 18, 244, 254, 7, 200, 22, 251, 71, 186, 79, 179, 100, 134, 250, 0, 225, 211, 194, 7, 96, 98, 129, 156, 64, 132, 49, 227, 216, 75, 207, 255, 91, 134, 88, 31, 32, 110, 182, 16, 61, 36, 237, 226, 99, 240, 1, 238, 243, 5, 64, 21, 176, 232, 33, 224, 151, 127, 15, 196, 183, 129, 141, 21, 32, 1, 130, 199, 223, 214, 206, 199, 167, 173, 42, 138, 227, 150, 12, 74, 95, 169, 212, 86, 4, 3, 182, 153, 210, 6, 91, 173, 147, 64, 134, 115, 45, 63, 92, 212, 48, 166, 2, 50, 55, 134, 48, 149, 76, 42, 152, 208, 68, 11, 196, 200, 6, 101, 26, 135, 46, 97, 252, 170, 3, 29, 137, 10, 235, 252, 181, 200, 18, 93, 226, 22, 53, 97, 49, 250, 79, 249, 57, 247, 93, 250, 218, 78, 48, 168, 95, 224, 221, 251, 189, 231, 222, 232, 114, 206, 189, 239, 254, 122, 231, 200, 151, 62, 244, 211, 131, 123, 229, 106, 81, 207, 175, 190, 19, 224, 112, 76, 22, 123, 110, 20, 247, 58, 58, 90, 91, 111, 216, 138, 42, 29, 108, 128, 79, 78, 78, 114, 29, 208, 62, 249, 182, 163, 124, 50, 212, 116, 214, 106, 241, 191, 220, 7, 24, 232, 247, 1, 181, 188, 27, 232, 234, 87, 128, 194, 125, 79, 203, 129, 191, 162, 64, 203, 133, 247, 227, 39, 86, 85, 160, 250, 64, 242, 49, 83, 153, 204, 233, 146, 15, 178, 15, 160, 207, 255, 183, 111, 3, 228, 248, 12, 84, 229, 106, 31, 128, 53, 192, 118, 155, 106, 163, 38, 210, 180, 243, 247, 255, 229, 254, 221, 253, 3, 20, 59, 191, 87, 208, 129, 1, 190, 84, 164, 0, 59, 4, 16, 208, 210, 54, 185, 19, 200, 158, 206, 93, 67, 192, 221, 3, 64, 174, 254, 193, 165, 66, 3, 168, 136, 149, 160, 208, 189, 114, 78, 246, 57, 212, 58, 125, 164, 86, 109, 4, 16, 16, 162, 247, 236, 121, 7, 55, 62, 122, 223, 238, 245, 151, 22, 213, 133, 111, 20, 223, 96, 94, 96, 220, 240, 151, 58, 216, 12, 58, 91, 121, 190, 216, 106, 241, 127, 220, 7, 224, 44, 96, 161, 7, 7, 192, 93, 120, 3, 222, 124, 97, 98, 115, 116, 110, 110, 173, 1, 46, 254, 130, 55, 209, 243, 208, 194, 232, 194, 201, 137, 23, 124, 152, 193, 166, 240, 230, 185, 197, 197, 145, 77, 228, 88, 193, 201, 205, 77, 95, 242, 9, 137, 254, 240, 152, 56, 4, 73, 178, 15, 32, 103, 0, 214, 252, 159, 5, 64, 14, 244, 105, 208, 131, 46, 215, 83, 217, 54, 213, 70, 100, 172, 113, 231, 239, 255, 203, 253, 187, 251, 7, 240, 59, 93, 196, 8, 0, 237, 60, 223, 127, 127, 137, 199, 82, 251, 251, 187, 97, 41, 235, 82, 222, 173, 16, 141, 170, 207, 195, 11, 135, 128, 95, 238, 30, 2, 10, 244, 79, 35, 181, 17, 228, 197, 215, 19, 203, 186, 88, 32, 32, 10, 181, 31, 220, 43, 47, 174, 149, 69, 61, 255, 34, 181, 170, 247, 251, 27, 249, 224, 161, 170, 88, 34, 67, 132, 217, 23, 241, 219, 194, 206, 27, 108, 24, 201, 75, 224, 217, 98, 191, 39, 88, 28, 50, 242, 91, 232, 115, 253, 192, 191, 189, 15, 224, 91, 94, 144, 85, 157, 239, 232, 230, 200, 80, 115, 115, 195, 230, 214, 252, 218, 130, 240, 254, 205, 134, 6, 150, 125, 19, 139, 139, 163, 205, 204, 254, 125, 79, 79, 52, 112, 7, 180, 103, 115, 126, 126, 109, 237, 112, 243, 144, 175, 107, 243, 196, 208, 225, 209, 209, 137, 164, 75, 182, 117, 159, 16, 79, 176, 201, 167, 120, 201, 179, 10, 176, 124, 68, 160, 127, 8, 80, 41, 127, 72, 31, 20, 15, 17, 217, 54, 213, 206, 200, 149, 157, 191, 255, 63, 226, 40, 62, 93, 91, 230, 168, 250, 104, 7, 121, 77, 240, 31, 12, 224, 235, 127, 52, 128, 182, 99, 242, 117, 240, 213, 252, 33, 64, 89, 64, 225, 11, 160, 64, 255, 87, 219, 244, 78, 96, 5, 218, 177, 7, 194, 1, 38, 238, 156, 239, 28, 244, 86, 176, 190, 207, 229, 200, 53, 15, 51, 195, 203, 231, 37, 129, 131, 70, 72, 127, 237, 111, 110, 4, 216, 88, 247, 133, 43, 195, 78, 63, 139, 129, 146, 34, 175, 147, 121, 63, 219, 93, 78, 153, 17, 114, 55, 176, 55, 204, 10, 49, 191, 133, 62, 215, 183, 97, 0, 247, 51, 241, 159, 254, 153, 132, 3, 63, 209, 255, 195, 96, 156, 141, 32, 245, 3, 178, 114, 78, 133, 178, 247, 1, 38, 150, 23, 23, 215, 214, 214, 54, 125, 205, 163, 178, 220, 223, 156, 87, 167, 63, 11, 155, 190, 195, 176, 19, 35, 19, 8, 49, 136, 230, 9, 223, 144, 56, 134, 106, 120, 17, 49, 7, 130, 163, 47, 250, 154, 23, 164, 254, 209, 164, 139, 251, 221, 166, 247, 255, 228, 19, 247, 153, 171, 0, 150, 0, 86, 180, 8, 126, 53, 20, 87, 199, 193, 86, 27, 12, 96, 186, 241, 52, 19, 253, 22, 32, 135, 127, 53, 100, 107, 78, 55, 29, 225, 18, 68, 232, 73, 92, 128, 28, 233, 12, 53, 181, 68, 180, 156, 185, 160, 200, 107, 77, 121, 211, 147, 211, 117, 225, 190, 190, 3, 111, 213, 167, 211, 251, 87, 113, 1, 91, 95, 159, 238, 174, 175, 63, 240, 218, 129, 213, 213, 238, 190, 183, 20, 223, 168, 63, 32, 60, 221, 221, 71, 162, 228, 84, 232, 75, 175, 236, 95, 173, 175, 95, 93, 93, 93, 193, 79, 160, 220, 7, 80, 203, 128, 252, 237, 192, 223, 127, 217, 101, 252, 7, 215, 182, 239, 4, 210, 155, 99, 37, 40, 52, 198, 61, 63, 165, 224, 18, 188, 59, 9, 47, 178, 120, 73, 192, 176, 197, 236, 225, 146, 146, 109, 142, 28, 94, 90, 26, 56, 232, 116, 228, 249, 7, 112, 26, 49, 89, 8, 218, 120, 216, 88, 15, 84, 218, 176, 5, 25, 15, 152, 239, 247, 218, 156, 204, 15, 13, 103, 126, 11, 125, 172, 107, 235, 157, 116, 52, 225, 55, 161, 12, 12, 50, 73, 142, 180, 176, 102, 226, 181, 41, 177, 44, 40, 124, 39, 242, 78, 89, 149, 37, 111, 61, 159, 61, 14, 158, 152, 95, 94, 198, 6, 134, 122, 112, 0, 198, 178, 174, 127, 75, 52, 188, 176, 54, 114, 102, 232, 132, 172, 245, 251, 151, 231, 100, 198, 191, 48, 114, 82, 45, 1, 79, 244, 207, 207, 97, 17, 205, 163, 13, 47, 8, 27, 57, 49, 144, 148, 175, 188, 221, 195, 68, 110, 25, 78, 50, 180, 99, 1, 10, 218, 93, 32, 107, 127, 160, 85, 15, 208, 63, 51, 63, 87, 78, 27, 231, 88, 231, 248, 71, 31, 161, 244, 105, 217, 220, 225, 123, 31, 118, 122, 106, 142, 112, 220, 171, 38, 252, 14, 127, 217, 149, 178, 233, 200, 71, 45, 17, 185, 42, 201, 37, 48, 242, 53, 45, 79, 34, 231, 170, 240, 88, 237, 116, 185, 13, 253, 62, 128, 130, 191, 150, 228, 64, 125, 122, 255, 91, 111, 9, 39, 125, 64, 248, 106, 150, 31, 208, 60, 167, 126, 125, 125, 95, 122, 63, 209, 63, 152, 207, 181, 93, 187, 90, 248, 18, 248, 248, 251, 207, 175, 154, 75, 129, 223, 175, 142, 255, 38, 250, 63, 148, 219, 255, 175, 181, 93, 208, 119, 2, 75, 208, 81, 169, 205, 176, 133, 75, 194, 165, 40, 152, 17, 62, 28, 211, 28, 5, 239, 187, 215, 110, 241, 0, 10, 183, 120, 169, 17, 8, 4, 14, 218, 202, 243, 252, 3, 208, 225, 217, 14, 116, 146, 97, 59, 192, 86, 97, 160, 254, 73, 217, 254, 247, 215, 217, 123, 157, 108, 2, 97, 10, 249, 45, 90, 154, 228, 147, 233, 160, 211, 125, 89, 66, 129, 16, 43, 132, 136, 0, 236, 244, 150, 125, 120, 252, 101, 178, 138, 28, 127, 227, 149, 119, 107, 238, 176, 34, 200, 202, 47, 127, 83, 172, 219, 165, 206, 220, 222, 162, 211, 111, 205, 13, 221, 190, 125, 123, 126, 126, 161, 235, 246, 60, 42, 159, 155, 95, 27, 218, 82, 5, 112, 177, 136, 229, 53, 37, 223, 90, 235, 186, 189, 44, 99, 2, 159, 135, 207, 223, 222, 218, 90, 94, 30, 72, 13, 187, 92, 195, 195, 213, 242, 147, 114, 15, 51, 181, 83, 241, 65, 248, 85, 142, 225, 242, 128, 196, 188, 14, 238, 202, 105, 19, 30, 251, 228, 92, 103, 231, 116, 231, 149, 113, 44, 97, 140, 192, 81, 132, 12, 153, 142, 140, 211, 207, 177, 225, 113, 135, 179, 108, 170, 165, 83, 226, 74, 77, 215, 140, 71, 58, 63, 145, 56, 34, 227, 17, 172, 36, 50, 70, 155, 72, 167, 199, 142, 171, 88, 92, 193, 138, 107, 88, 128, 79, 224, 189, 242, 76, 244, 209, 11, 98, 1, 153, 31, 102, 110, 230, 91, 192, 31, 172, 250, 191, 19, 112, 34, 252, 103, 158, 254, 111, 222, 250, 33, 131, 163, 216, 123, 46, 168, 227, 224, 64, 44, 78, 119, 46, 142, 25, 129, 176, 45, 16, 216, 87, 17, 176, 25, 89, 110, 179, 221, 205, 205, 250, 137, 152, 97, 51, 224, 5, 254, 1, 194, 226, 42, 25, 18, 150, 206, 206, 43, 67, 190, 18, 57, 239, 247, 179, 15, 228, 180, 171, 235, 225, 200, 243, 91, 12, 134, 56, 21, 247, 36, 156, 179, 24, 0, 26, 38, 36, 196, 203, 151, 137, 10, 20, 186, 67, 238, 21, 54, 4, 148, 214, 143, 95, 174, 189, 35, 235, 65, 45, 39, 102, 144, 161, 219, 121, 125, 244, 123, 206, 119, 241, 249, 115, 84, 157, 240, 53, 143, 208, 243, 225, 253, 62, 117, 6, 216, 213, 124, 98, 8, 46, 114, 133, 174, 195, 120, 19, 239, 57, 99, 126, 30, 142, 188, 63, 89, 173, 206, 120, 221, 213, 238, 234, 100, 94, 188, 0, 243, 9, 149, 130, 252, 120, 1, 46, 171, 77, 181, 209, 121, 14, 240, 233, 159, 108, 244, 127, 50, 21, 97, 159, 183, 179, 147, 79, 255, 166, 106, 216, 255, 189, 210, 234, 12, 49, 40, 76, 213, 16, 84, 108, 106, 44, 50, 134, 250, 207, 145, 155, 138, 68, 58, 167, 169, 55, 117, 206, 83, 132, 119, 112, 229, 44, 90, 58, 182, 164, 123, 229, 209, 76, 70, 44, 224, 158, 153, 11, 109, 199, 248, 34, 48, 251, 18, 56, 164, 12, 224, 99, 129, 24, 128, 165, 255, 91, 23, 190, 61, 214, 246, 235, 12, 57, 244, 47, 199, 193, 182, 88, 34, 204, 159, 51, 78, 175, 229, 128, 181, 72, 243, 112, 156, 241, 220, 22, 219, 39, 220, 208, 60, 102, 139, 33, 119, 90, 60, 22, 43, 240, 15, 128, 246, 65, 41, 126, 209, 73, 216, 24, 130, 43, 103, 250, 54, 182, 1, 253, 12, 9, 130, 252, 22, 94, 62, 25, 8, 120, 139, 156, 195, 151, 229, 30, 200, 27, 116, 112, 146, 151, 95, 9, 221, 161, 167, 179, 16, 160, 72, 248, 241, 178, 59, 199, 95, 177, 228, 239, 190, 219, 166, 219, 121, 7, 186, 228, 114, 175, 90, 230, 9, 186, 228, 71, 21, 240, 16, 222, 133, 28, 136, 92, 113, 41, 33, 111, 54, 128, 36, 171, 121, 147, 51, 152, 211, 155, 147, 73, 229, 33, 128, 128, 60, 59, 1, 217, 44, 117, 134, 115, 218, 216, 90, 85, 212, 168, 115, 18, 40, 72, 146, 41, 117, 224, 67, 22, 141, 79, 227, 249, 164, 238, 180, 216, 198, 115, 231, 68, 38, 143, 200, 21, 6, 12, 228, 87, 198, 58, 35, 145, 42, 163, 168, 15, 53, 226, 247, 127, 169, 175, 15, 117, 174, 172, 44, 173, 226, 10, 188, 47, 13, 175, 135, 167, 225, 34, 39, 173, 87, 60, 189, 68, 125, 166, 2, 22, 143, 102, 80, 99, 20, 19, 48, 141, 32, 122, 233, 214, 182, 5, 76, 177, 237, 247, 146, 224, 143, 223, 126, 59, 119, 200, 60, 4, 186, 117, 41, 154, 57, 133, 242, 193, 133, 40, 13, 219, 184, 18, 22, 11, 87, 26, 177, 112, 208, 136, 199, 226, 232, 202, 91, 18, 11, 215, 197, 133, 39, 98, 113, 186, 233, 189, 37, 78, 75, 254, 55, 92, 237, 3, 16, 186, 65, 223, 242, 119, 168, 148, 178, 93, 185, 78, 201, 113, 35, 4, 255, 17, 229, 29, 193, 112, 172, 248, 172, 7, 240, 29, 1, 193, 128, 60, 231, 111, 176, 121, 80, 87, 215, 81, 201, 237, 63, 207, 249, 114, 66, 207, 157, 71, 152, 149, 159, 45, 182, 5, 1, 71, 75, 206, 167, 39, 124, 61, 35, 236, 241, 158, 97, 217, 215, 211, 211, 35, 1, 100, 94, 20, 126, 114, 98, 243, 105, 10, 100, 193, 223, 160, 56, 242, 134, 172, 124, 136, 250, 34, 63, 233, 75, 37, 103, 209, 43, 202, 172, 78, 166, 158, 225, 74, 184, 21, 34, 112, 7, 112, 53, 252, 205, 156, 54, 88, 248, 179, 193, 58, 78, 185, 234, 120, 4, 129, 250, 223, 226, 40, 148, 253, 111, 58, 20, 93, 66, 197, 10, 0, 42, 68, 4, 96, 0, 212, 160, 87, 84, 164, 87, 81, 251, 70, 183, 107, 101, 37, 189, 178, 190, 177, 225, 90, 217, 230, 105, 139, 147, 105, 79, 167, 87, 211, 235, 27, 235, 237, 105, 204, 33, 13, 95, 5, 43, 235, 235, 237, 153, 99, 232, 81, 155, 0, 152, 57, 165, 108, 64, 92, 70, 31, 250, 195, 50, 128, 67, 240, 123, 110, 125, 21, 109, 179, 180, 47, 184, 206, 12, 114, 159, 97, 224, 113, 49, 94, 25, 79, 132, 19, 113, 195, 184, 55, 96, 196, 225, 137, 186, 120, 194, 8, 194, 43, 108, 166, 188, 78, 228, 134, 200, 225, 134, 211, 172, 79, 132, 12, 229, 31, 160, 234, 237, 166, 150, 113, 181, 170, 55, 163, 1, 82, 70, 186, 51, 215, 169, 202, 77, 141, 117, 142, 77, 143, 51, 108, 146, 180, 212, 114, 76, 82, 195, 65, 9, 140, 192, 90, 128, 155, 245, 60, 248, 150, 86, 194, 3, 80, 137, 193, 85, 106, 146, 163, 100, 154, 242, 218, 163, 205, 39, 27, 150, 101, 230, 191, 54, 49, 196, 22, 0, 75, 129, 147, 47, 64, 101, 37, 48, 49, 180, 54, 215, 220, 44, 242, 69, 37, 63, 156, 39, 167, 0, 121, 79, 42, 69, 63, 158, 173, 118, 39, 83, 73, 2, 136, 233, 176, 129, 130, 199, 115, 38, 255, 230, 19, 145, 10, 28, 232, 118, 229, 180, 225, 92, 84, 190, 255, 7, 232, 88, 158, 26, 100, 252, 6, 195, 157, 157, 10, 40, 94, 75, 178, 82, 49, 2, 26, 218, 239, 93, 39, 248, 19, 209, 127, 220, 235, 233, 213, 149, 141, 245, 13, 247, 6, 234, 134, 187, 254, 150, 175, 108, 184, 165, 62, 81, 166, 218, 87, 86, 251, 200, 172, 187, 174, 71, 51, 68, 11, 187, 110, 25, 129, 178, 129, 175, 110, 137, 9, 252, 121, 72, 129, 155, 194, 56, 16, 255, 234, 98, 94, 223, 7, 109, 143, 70, 197, 0, 226, 9, 15, 186, 174, 75, 4, 227, 28, 228, 199, 43, 98, 22, 175, 180, 120, 161, 60, 1, 55, 132, 43, 255, 0, 158, 211, 102, 0, 128, 74, 155, 25, 13, 144, 50, 210, 157, 185, 78, 85, 174, 134, 143, 73, 6, 67, 161, 198, 166, 42, 153, 240, 135, 66, 85, 131, 85, 4, 7, 227, 89, 213, 216, 88, 214, 212, 52, 88, 91, 251, 228, 160, 200, 28, 34, 104, 172, 226, 89, 214, 216, 216, 244, 228, 96, 107, 35, 194, 170, 198, 142, 9, 174, 247, 49, 217, 195, 6, 154, 23, 231, 73, 89, 247, 251, 76, 190, 220, 60, 39, 150, 177, 60, 138, 92, 48, 186, 108, 202, 23, 124, 204, 27, 201, 205, 139, 252, 4, 6, 144, 100, 236, 159, 77, 1, 215, 51, 89, 3, 208, 139, 192, 66, 40, 3, 120, 198, 149, 211, 6, 253, 2, 195, 95, 160, 99, 58, 58, 160, 139, 23, 97, 30, 90, 106, 150, 233, 90, 70, 24, 56, 237, 222, 238, 247, 87, 187, 187, 219, 247, 187, 187, 151, 164, 223, 119, 15, 239, 39, 42, 208, 221, 188, 27, 158, 222, 232, 110, 135, 83, 127, 99, 169, 219, 189, 177, 36, 227, 192, 134, 27, 3, 200, 92, 63, 118, 12, 11, 200, 179, 129, 76, 244, 210, 181, 155, 24, 129, 224, 161, 155, 215, 10, 181, 15, 218, 136, 38, 119, 61, 202, 28, 32, 17, 244, 4, 19, 193, 58, 84, 92, 135, 207, 149, 34, 99, 111, 92, 249, 7, 120, 182, 213, 163, 111, 249, 183, 170, 148, 178, 93, 185, 78, 85, 110, 176, 163, 188, 53, 36, 31, 77, 52, 54, 242, 96, 143, 76, 230, 118, 141, 33, 168, 199, 81, 54, 216, 138, 242, 165, 120, 48, 228, 160, 140, 121, 95, 83, 43, 41, 92, 174, 84, 242, 233, 65, 71, 220, 183, 118, 120, 219, 0, 150, 183, 182, 72, 151, 23, 142, 106, 3, 192, 34, 48, 128, 197, 209, 17, 20, 14, 22, 76, 3, 16, 57, 21, 193, 40, 22, 115, 194, 155, 74, 86, 164, 120, 249, 23, 121, 83, 100, 201, 88, 51, 0, 94, 247, 20, 152, 247, 4, 249, 81, 37, 112, 144, 211, 198, 110, 23, 253, 131, 60, 19, 32, 67, 36, 33, 80, 97, 119, 50, 181, 114, 234, 10, 74, 68, 86, 191, 8, 196, 0, 232, 225, 221, 221, 42, 26, 216, 6, 209, 227, 190, 150, 48, 113, 58, 122, 88, 46, 199, 32, 186, 225, 95, 19, 62, 110, 157, 250, 22, 119, 243, 10, 16, 175, 144, 215, 51, 25, 173, 217, 215, 163, 167, 212, 68, 112, 230, 90, 230, 226, 165, 153, 155, 55, 103, 46, 93, 204, 92, 187, 165, 38, 128, 167, 46, 178, 240, 87, 136, 210, 249, 249, 185, 30, 109, 195, 0, 18, 229, 137, 120, 34, 40, 61, 60, 24, 79, 20, 133, 247, 198, 149, 127, 128, 160, 199, 163, 87, 245, 173, 42, 165, 108, 87, 174, 83, 149, 11, 213, 149, 103, 253, 102, 121, 120, 86, 181, 134, 176, 4, 178, 173, 194, 26, 171, 228, 225, 41, 231, 143, 255, 6, 74, 39, 21, 211, 8, 57, 160, 100, 59, 140, 129, 247, 190, 248, 241, 140, 138, 0, 33, 49, 33, 37, 40, 208, 23, 207, 159, 81, 113, 1, 94, 164, 0, 254, 247, 114, 168, 146, 255, 36, 6, 112, 47, 58, 173, 16, 3, 160, 87, 231, 30, 250, 128, 44, 211, 5, 230, 181, 208, 156, 54, 69, 118, 153, 228, 26, 162, 214, 48, 31, 199, 102, 161, 95, 244, 94, 59, 115, 107, 13, 170, 25, 218, 8, 4, 42, 150, 140, 247, 145, 207, 4, 159, 202, 151, 71, 159, 126, 250, 1, 89, 13, 38, 27, 121, 96, 246, 33, 197, 212, 51, 119, 37, 117, 181, 225, 215, 193, 163, 252, 170, 132, 31, 197, 47, 98, 3, 168, 156, 129, 0, 156, 154, 121, 8, 156, 186, 136, 222, 145, 169, 39, 80, 79, 181, 19, 152, 143, 131, 123, 229, 114, 195, 191, 152, 57, 80, 135, 90, 213, 155, 183, 253, 41, 35, 221, 153, 235, 84, 114, 24, 128, 196, 89, 228, 12, 9, 59, 0, 114, 95, 26, 2, 115, 192, 5, 50, 54, 76, 78, 162, 110, 126, 41, 147, 186, 173, 173, 114, 165, 100, 146, 186, 65, 227, 85, 162, 63, 154, 32, 229, 23, 110, 21, 0, 75, 78, 185, 78, 7, 44, 249, 171, 104, 157, 142, 47, 125, 61, 229, 37, 157, 117, 23, 90, 0, 5, 36, 195, 10, 228, 213, 192, 144, 211, 70, 69, 9, 66, 177, 168, 214, 82, 46, 48, 204, 46, 142, 1, 144, 132, 195, 166, 148, 98, 176, 253, 122, 0, 118, 239, 146, 107, 22, 84, 43, 204, 110, 155, 28, 15, 65, 123, 33, 68, 164, 99, 204, 82, 71, 118, 143, 83, 143, 222, 141, 215, 183, 141, 224, 33, 13, 180, 159, 85, 122, 65, 173, 255, 110, 0, 118, 249, 167, 48, 220, 105, 63, 129, 42, 165, 108, 87, 174, 83, 149, 243, 116, 4, 131, 76, 160, 179, 110, 180, 128, 204, 168, 9, 183, 201, 165, 217, 14, 36, 20, 231, 1, 161, 44, 28, 38, 69, 22, 244, 59, 95, 5, 207, 235, 63, 129, 230, 104, 55, 159, 91, 117, 178, 6, 0, 207, 49, 128, 20, 35, 0, 169, 219, 236, 239, 59, 2, 83, 192, 0, 172, 54, 56, 62, 48, 167, 1, 244, 231, 44, 32, 138, 150, 86, 176, 115, 14, 41, 0, 18, 17, 130, 10, 47, 74, 5, 238, 66, 48, 204, 40, 96, 116, 40, 155, 74, 121, 34, 140, 69, 100, 8, 83, 50, 146, 171, 104, 208, 64, 50, 109, 185, 250, 197, 8, 24, 247, 181, 174, 53, 164, 110, 84, 106, 103, 218, 50, 60, 239, 57, 88, 160, 208, 189, 114, 89, 213, 19, 202, 93, 95, 244, 183, 171, 148, 178, 93, 185, 78, 85, 174, 4, 87, 99, 252, 89, 80, 212, 42, 83, 137, 37, 207, 103, 84, 123, 117, 239, 216, 151, 75, 188, 251, 80, 188, 130, 87, 1, 205, 90, 16, 69, 23, 112, 133, 156, 54, 242, 175, 43, 178, 155, 40, 42, 64, 133, 132, 90, 99, 136, 64, 215, 26, 118, 160, 40, 41, 98, 26, 75, 247, 255, 247, 24, 78, 69, 239, 134, 132, 7, 199, 2, 10, 129, 234, 69, 247, 5, 248, 11, 191, 251, 223, 84, 153, 36, 87, 100, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130}
diff --git a/examples/community/raycaster/screenshot.png b/examples/community/raycaster/screenshot.png
new file mode 100644
index 0000000..fd18e89
Binary files /dev/null and b/examples/community/raycaster/screenshot.png differ
diff --git a/examples/community/scrolling-background/README.md b/examples/community/scrolling-background/README.md
new file mode 100644
index 0000000..db3ae06
--- /dev/null
+++ b/examples/community/scrolling-background/README.md
@@ -0,0 +1,7 @@
+# Infinite scrolling background demo
+
+Created by [Sergio Vera](https://github.com/svera)
+
+This example shows how to implement an infinite side scrolling background.
+
+Credits to [Peter Hellberg](https://github.com/peterhellberg) for the improved background image.
diff --git a/examples/community/scrolling-background/gamebackground.png b/examples/community/scrolling-background/gamebackground.png
new file mode 100644
index 0000000..9338733
Binary files /dev/null and b/examples/community/scrolling-background/gamebackground.png differ
diff --git a/examples/community/scrolling-background/main.go b/examples/community/scrolling-background/main.go
new file mode 100644
index 0000000..7c3c301
--- /dev/null
+++ b/examples/community/scrolling-background/main.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "image"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+const (
+ windowWidth = 600
+ windowHeight = 450
+ // This is the scrolling speed
+ linesPerSecond = 60
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Scrolling background demo",
+ Bounds: pixel.R(0, 0, windowWidth, windowHeight),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ // Pic must have double the width of the window, as it will scroll to the left
+ pic, err := loadPicture("gamebackground.png")
+ if err != nil {
+ panic(err)
+ }
+
+ // Backgrounds are made taking the left and right halves of the image
+ background1 := pixel.NewSprite(pic, pixel.R(0, 0, windowWidth, windowHeight))
+ background2 := pixel.NewSprite(pic, pixel.R(windowWidth, 0, windowWidth*2, windowHeight))
+
+ // In the beginning, vector1 will put background1 filling the whole window, while vector2 will
+ // put background2 just at the right side of the window, out of view
+ vector1 := pixel.V(windowWidth/2, windowHeight/2)
+ vector2 := pixel.V(windowWidth+(windowWidth/2), windowHeight/2)
+
+ i := float64(0)
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+ // When one of the backgrounds has completely scrolled, we swap displacement vectors,
+ // so the backgrounds will swap positions too regarding the previous iteration,
+ // thus making the background endless.
+ if i <= -windowWidth {
+ i = 0
+ vector1, vector2 = vector2, vector1
+ }
+ // This delta vector will move the backgrounds to the left
+ d := pixel.V(-i, 0)
+ background1.Draw(win, pixel.IM.Moved(vector1.Sub(d)))
+ background2.Draw(win, pixel.IM.Moved(vector2.Sub(d)))
+ i -= linesPerSecond * dt
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/seascape-shader/Makefile b/examples/community/seascape-shader/Makefile
new file mode 100644
index 0000000..0800ec0
--- /dev/null
+++ b/examples/community/seascape-shader/Makefile
@@ -0,0 +1,18 @@
+# go get -u github.com/faiface/pixel-examples
+# cd ~/go/src/github.com/faiface/pixel-examples/community/seascape-shader
+
+all:
+ go build
+ ./seascape-shader
+
+push:
+ git pull
+ git add --all
+ -git commit -a -s
+ git push
+
+update:
+ git pull
+
+diff:
+ git diff
diff --git a/examples/community/seascape-shader/README.md b/examples/community/seascape-shader/README.md
new file mode 100644
index 0000000..7d1dea7
--- /dev/null
+++ b/examples/community/seascape-shader/README.md
@@ -0,0 +1,133 @@
+# Shadertoy to Pixel shader conversion
+
+This will show you an example how you can take an example from [shadertoy.com](shadertoy.com) and use it with the Pixel Shader support.
+
+["Seascape" by Alexander Alekseev aka TDM - 2014](https://www.shadertoy.com/view/Ms2SD1) is a nice one. It's very impressive and doesn't use textures so that simplifies things for us. Let's try that one.
+
+Looking at the seascape.glsl example you can see **iResolution**, **iTime** and **iMouse** in there.
+
+These are commonly needed to be exposed because these things are coming from outside and needs to be updated. Any other variable you need to have changed/updated from code can be exposed like those.
+
+## Command line arguments
+
+```
+./seascape-shader -h # will show you the command line options
+
+./seascape-shader -filename ./shaders/seascape.glsl # Seascape
+./seascape-shader -filename ./shaders/planetfall.glsl # Planet Fall demo
+```
+
+## Exposing variables
+
+How to expose variables like this?
+
+Well, first we need to figure out what type of variables they are. Looking at the shader, you can see **iResolution.x** in there. This tells you that it's not a simple type. In this case it's a **vec2***, a **Vector containing 2 values, x and y**. This makes sense since resolution is described by x and y. That is, width and height.
+
+And thus, we create our variable in Go with like so:
+```
+uResolution := mgl32.Vec2{float32(win.Bounds().W()), float32(win.Bounds().H())}
+```
+That is, to be of the type **mgl32.Vec2**. Here we create it by taking the window's width and height.
+
+**iTime** is just a float, and thus that is just created like so:
+```
+var uTime float32
+```
+
+For the **iMouse**, it's a **mgl32.Vec4**, a **Vector containing 4 variables**. We only use x and y for the mouse position here though.
+```
+var uMouse mgl32.Vec4
+```
+
+And finally, to make our variables available in the shader itself we use:
+```
+canvas.SetUniform(name string, value interface{})
+```
+
+We create a handy function to do this:
+
+``` go
+func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) {
+ if len(unifs)%2 != 0 {
+ panic("needs to be divisable by 2")
+ }
+ for i := 0; i < len(unifs); i += 2 {
+
+ c.SetUniform(unifs[i+0].(string), unifs[i+1])
+ }
+}
+```
+and we call that function like so:
+
+``` go
+ EasyBindUniforms(canvas,
+ "uResolution", &uResolution,
+ "uTime", &uTime,
+ "uMouse", &uMouse,
+ )
+```
+
+## Updating shader source file
+
+We also need to do some updates to the shader file itself to match these variables. First thing would be to add the variables we exposed in Go.
+
+```
+uniform vec2 uResolution;
+uniform float uTime;
+uniform vec4 uMouse;
+```
+Then we just rename the variables to match.
+
+We also need to rename the main function itself, as the one used here is specific for use with shadertoy. For our shader, the entrypoint is main(). So we rename:
+```
+void mainImage( out vec4 fragColor, in vec2 fragCoord )
+```
+to
+```
+void main() {
+```
+
+
+Also, rename:
+```
+fragCoord
+```
+to
+```
+gl_FragCoord
+```
+because this is available globaly in the OpenGL space for the shader.
+
+We also need to add:
+```
+out vec4 fragColor;
+```
+
+to expose that.
+
+Lastly, we need to add:
+```
+#version 330 core
+```
+at the top to tell what version we require.
+
+
+## Using shader
+To use the shader in our canvas we do:
+```
+canvas.SetFragmentShader(fragSource string)
+```
+where fragSource is the fragment shader, not a path fo a file.
+
+#
+
+## Result converting shadertoy shader to use with Pixel
+
+Here is a diff of the changes:
+
+![code changes](shader_diffs.png "Code changes")
+
+
+And that is it. Running the program we should see this:
+
+![seascape animation](seascape.gif "Seascape animation")
diff --git a/examples/community/seascape-shader/commandline.go b/examples/community/seascape-shader/commandline.go
new file mode 100644
index 0000000..8ceb8cc
--- /dev/null
+++ b/examples/community/seascape-shader/commandline.go
@@ -0,0 +1,67 @@
+package main
+
+/*
+ This simply parses the command line arguments using the default golang
+ package called 'flag'. This can be used as a simple template to parse
+ command line arguments in other programs.
+*/
+
+import "log"
+import "os"
+import "flag"
+import "fmt"
+
+var (
+ version string
+ race bool
+ debug = os.Getenv("BUILDDEBUG") != ""
+ filename string
+ width int
+ height int
+ timeout = "120s"
+ uDrift float32
+)
+
+var customUsage = func() {
+ fmt.Fprintf(flag.CommandLine.Output(), "Usage of %s:\n", os.Args[0])
+ flag.PrintDefaults()
+
+ fmt.Println("")
+ fmt.Println("EXAMPLE:")
+ fmt.Println("")
+ fmt.Println("seascape-shader --width 640 --height 480 --filename shaders/planetfall.glsl")
+ fmt.Println("")
+}
+
+func parseFlags() {
+ flag.StringVar (&version, "version", "v0.1", "Set compiled in version string")
+ flag.StringVar (&filename, "filename", "shaders/seascape.glsl", "path to GLSL file")
+ flag.IntVar (&width, "width", 1024, "Width of the OpenGL Window")
+ flag.IntVar (&height, "height", 768, "Height of the OpenGL Window")
+ var tmp float64
+ flag.Float64Var (&tmp, "drift", 0.01, "Speed of the gradual camera drift")
+ flag.BoolVar (&race, "race", race, "Use race detector")
+
+ // Set the output if something fails to stdout rather than stderr
+ flag.CommandLine.SetOutput(os.Stdout)
+ // flag.SetOutput(os.Stdout)
+
+ flag.Usage = customUsage
+ flag.Parse()
+
+ if flag.Parsed() {
+ log.Println("Parsed() worked. width=",width)
+ } else {
+ log.Println("Parsed() failed. width=",width)
+ }
+
+// if err := flag.Parse(); err != nil {
+// log.Println("Example:",width)
+// }
+
+
+ uDrift = float32(tmp)
+ log.Println("width=",width)
+ log.Println("height=",height)
+ log.Println("uDrift=",uDrift)
+}
diff --git a/examples/community/seascape-shader/psutil.go b/examples/community/seascape-shader/psutil.go
new file mode 100644
index 0000000..2fb370e
--- /dev/null
+++ b/examples/community/seascape-shader/psutil.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "io/ioutil"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+// Pixel Shader utility functions
+
+// EasyBindUniforms does all the work for you, just pass in a
+// valid array adhering to format: String, Variable, ...
+//
+// example:
+//
+// var uTimeVar float32
+// var uMouseVar mgl32.Vec4
+//
+// EasyBindUniforms(win.GetCanvas(),
+// "u_time", &uTimeVar,
+// "u_mouse", &uMouseVar,
+// )
+//
+func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) {
+ if len(unifs)%2 != 0 {
+ panic("needs to be divisable by 2")
+ }
+ for i := 0; i < len(unifs); i += 2 {
+
+ c.SetUniform(unifs[i+0].(string), unifs[i+1])
+ }
+}
+
+// CenterWindow will... center the window
+func CenterWindow(win *pixelgl.Window) {
+ x, y := pixelgl.PrimaryMonitor().Size()
+ width, height := win.Bounds().Size().XY()
+ win.SetPos(
+ pixel.V(
+ x/2-width/2,
+ y/2-height/2,
+ ),
+ )
+}
+
+// LoadFileToString loads the contents of a file into a string
+func LoadFileToString(filename string) (string, error) {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
diff --git a/examples/community/seascape-shader/seascape.gif b/examples/community/seascape-shader/seascape.gif
new file mode 100644
index 0000000..d8f7c2a
Binary files /dev/null and b/examples/community/seascape-shader/seascape.gif differ
diff --git a/examples/community/seascape-shader/seascape.go b/examples/community/seascape-shader/seascape.go
new file mode 100644
index 0000000..2e2d4a9
--- /dev/null
+++ b/examples/community/seascape-shader/seascape.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "time"
+
+ "github.com/go-gl/mathgl/mgl32"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func run() {
+ // Set up window configs
+ cfg := pixelgl.WindowConfig{ // Default: 1024 x 768
+ Title: "Golang GLSL",
+ Bounds: pixel.R(0, 0, float64(width), float64(height)),
+ VSync: true,
+ }
+
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ camVector := win.Bounds().Center()
+
+ bounds := win.Bounds()
+ bounds.Max = bounds.Max.ScaledXY(pixel.V(1.0, 1.0))
+
+ // I am putting all shader example initializing stuff here for
+ // easier reference to those learning to use this functionality
+
+ fragSource, err := LoadFileToString(filename)
+
+ if err != nil {
+ panic(err)
+ }
+
+ var uMouse mgl32.Vec4
+ var uTime float32
+
+ canvas := win.Canvas()
+ uResolution := mgl32.Vec2{float32(win.Bounds().W()), float32(win.Bounds().H())}
+
+ EasyBindUniforms(canvas,
+ "uResolution", &uResolution,
+ "uTime", &uTime,
+ "uMouse", &uMouse,
+ "uDrift", &uDrift,
+ )
+
+ canvas.SetFragmentShader(fragSource)
+
+ start := time.Now()
+
+ // Game Loop
+ for !win.Closed() {
+ uTime = float32(time.Since(start).Seconds())
+ mpos := win.MousePosition()
+ uMouse[0] = float32(mpos.X)
+ uMouse[1] = float32(mpos.Y)
+
+ win.Clear(colornames.Black)
+
+ // Drawing to the screen
+ canvas.Draw(win, pixel.IM.Moved(camVector))
+
+ win.Update()
+ }
+
+}
+
+func main() {
+ parseFlags()
+
+ pixelgl.Run(run)
+}
diff --git a/examples/community/seascape-shader/shader_diffs.png b/examples/community/seascape-shader/shader_diffs.png
new file mode 100644
index 0000000..efdd553
Binary files /dev/null and b/examples/community/seascape-shader/shader_diffs.png differ
diff --git a/examples/community/seascape-shader/shaders/planetfall.glsl b/examples/community/seascape-shader/shaders/planetfall.glsl
new file mode 100644
index 0000000..d1a74e6
--- /dev/null
+++ b/examples/community/seascape-shader/shaders/planetfall.glsl
@@ -0,0 +1,333 @@
+// Created by inigo quilez - iq/2018
+// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+
+// Pretty much a modification to Klems' shader (https://www.shadertoy.com/view/XlcfRs)
+// Youtube version: https://www.youtube.com/watch?v=q1OBrqtl7Yo
+
+#version 330 core
+
+// Change AA to 1 if it renders too slow for you
+#define AA 1
+
+uniform vec2 uResolution;
+uniform float uTime; // shader playback time (in seconds)
+uniform vec4 uMouse;
+
+// there is tearing on my box. is this because this isn't working? -- jcarr
+uniform int iFrame; // shader playback frame
+
+out vec4 fragColor;
+// in vec2 fragCoord;
+
+mat3 makeBase( in vec3 w )
+{
+ float k = inversesqrt(1.0-w.y*w.y);
+ return mat3( vec3(-w.z,0.0,w.x)*k,
+ vec3(-w.x*w.y,1.0-w.y*w.y,-w.y*w.z)*k,
+ w);
+}
+
+#define ZERO (min(iFrame,0))
+
+// http://iquilezles.org/www/articles/intersectors/intersectors.htm
+vec2 sphIntersect( in vec3 ro, in vec3 rd, in float rad )
+{
+ float b = dot( ro, rd );
+ float c = dot( ro, ro ) - rad*rad;
+ float h = b*b - c;
+ if( h<0.0 ) return vec2(-1.0);
+ h = sqrt(h);
+ return vec2(-b-h,-b+h);
+}
+
+// http://iquilezles.org/www/articles/distfunctions/distfunctions.htm
+float sdCapsule( in vec3 p, in float b, in float r )
+{
+ float h = clamp( p.z/b, 0.0, 1.0 );
+ return length( p - vec3(0.0,0.0,b)*h ) - r;//*(0.2+1.6*h);
+}
+
+// modified Keinert et al's inverse Spherical Fibonacci Mapping
+vec4 inverseSF( in vec3 p, const in float n )
+{
+ const float PI = 3.14159265359;
+ const float PHI = 1.61803398875;
+
+ float phi = min(atan(p.y,p.x),PI);
+ float k = max(floor(log(n*PI*sqrt(5.0)*(1.-p.z*p.z))/log(PHI+1.)),2.0);
+ float Fk = pow(PHI,k)/sqrt(5.0);
+ vec2 F = vec2(round(Fk),round(Fk*PHI));
+ vec2 G = PI*(fract((F+1.0)*PHI)-(PHI-1.0));
+
+ mat2 iB = mat2(F.y,-F.x,G.y,-G.x)/(F.y*G.x-F.x*G.y);
+ vec2 c = floor(iB*0.5*vec2(phi,n*p.z-n+1.0));
+
+ float ma = 0.0;
+ vec4 res = vec4(0);
+ for( int s=0; s<4; s++ )
+ {
+ vec2 uv = vec2(s&1,s>>1);
+ float i = dot(F,uv+c);
+ float phi = 2.0*PI*fract(i*PHI);
+ float cT = 1.0 - (2.0*i+1.0)/n;
+ float sT = sqrt(1.0-cT*cT);
+ vec3 q = vec3(cos(phi)*sT, sin(phi)*sT,cT);
+ float a = dot(p,q);
+ if (a > ma)
+ {
+ ma = a;
+ res.xyz = q;
+ res.w = i;
+ }
+ }
+ return res;
+}
+
+float map( in vec3 p, out vec4 color, const in bool doColor )
+{
+ float lp = length(p);
+ float dmin = lp-1.0;
+ {
+ vec3 w = p/lp;
+ vec4 fibo = inverseSF(w, 700.0);
+ float hh = 1.0 - smoothstep(0.05,0.1,length(fibo.xyz-w));
+ dmin -= 0.07*hh;
+ color = vec4(0.05,0.1,0.1,1.0)*hh * (1.0+0.5*sin(fibo.w*111.1));
+ }
+
+
+ float s = 1.0;
+
+ for( int i=0; i<3; i++ )
+ {
+ float h = float(i)/float(3-1);
+
+ vec4 f = inverseSF(normalize(p), 65.0 + h*75.0);
+
+ // snap
+ p -= f.xyz;
+
+ // orient to surface
+ p = p*makeBase(f.xyz);
+
+ // scale
+ float scale = 6.6 + 2.0*sin(111.0*f.w);
+ p *= scale;
+ p.xy *= 1.2;
+
+ //translate
+ p.z -= 3.0 - length(p.xy)*0.6*sin(f.w*212.1);
+
+ // measure distance
+ s *= scale;
+ float d = sdCapsule( p, -6.0, 0.42 );
+ d /= s;
+
+ if( d>1)&1),((i>>1)&1),(i&1))-1.0);
+ n += e*map(pos+e*ep, kk, false);
+ }
+ return normalize(n);
+#endif
+
+}
+
+// http://iquilezles.org/www/articles/rmshadows/rmshadows.htm
+float calcSoftshadow( in vec3 ro, in vec3 rd, float tmin, float tmax, const float k )
+{
+ vec2 bound = sphIntersect( ro, rd, 2.1 );
+ tmin = max(tmin,bound.x);
+ tmax = min(tmax,bound.y);
+
+ float res = 1.0;
+ float t = tmin;
+ for( int i=0; i<50; i++ )
+ {
+ vec4 kk;
+ float h = map( ro + rd*t, kk, false );
+ res = min( res, k*h/t );
+ t += clamp( h, 0.02, 0.20 );
+ if( res<0.005 || t>tmax ) break;
+ }
+ return clamp( res, 0.0, 1.0 );
+}
+
+float raycast(in vec3 ro, in vec3 rd, in float tmin, in float tmax )
+{
+ vec4 kk;
+ float t = tmin;
+ for( int i=0; i<512; i++ )
+ {
+ vec3 p = ro + t*rd;
+ float h = map(p,kk,false);
+ if( abs(h)<(0.15*t/uResolution.x) ) break;
+ t += h*0.5;
+ if( t>tmax ) return -1.0;;
+ }
+ //if( t>tmax ) t=-1.0;
+
+ return t;
+}
+
+// void mainImage( out vec4 fragColor, in vec2 fragCoord )
+// gl_FragCoord.xy
+void main()
+{
+ float an = (uTime-10.0)*0.05;
+
+ // camera
+ vec3 ro = vec3( 4.5*sin(an), 0.0, 4.5*cos(an) );
+ vec3 ta = vec3( 0.0, 0.0, 0.0 );
+ // camera-to-world rotation
+ mat3 ca = makeBase( normalize(ta-ro) );
+
+ // render
+ vec3 tot = vec3(0.0);
+
+ #if AA>1
+ for( int m=ZERO; m0.0 )
+ {
+ // raycast
+ float t = raycast(ro, rd, bound.x, bound.y );
+ if( t>0.0 )
+ {
+ // local geometry
+ vec3 pos = ro + t*rd;
+ vec3 nor = calcNormal(pos, 0.01);
+ vec3 upp = normalize(pos);
+
+ // color and occlusion
+ vec4 mate; map(pos, mate, true);
+
+ // lighting
+ col = vec3(0.0);
+
+ // key ligh
+ {
+ // dif
+ vec3 lig = normalize(vec3(1.0,0.0,0.7));
+ float dif = clamp(0.5+0.5*dot(nor,lig),0.0,1.0);
+ float sha = calcSoftshadow( pos+0.0001*nor, lig, 0.0001, 2.0, 6.0 );
+ col += mate.xyz*dif*vec3(1.8,0.6,0.5)*1.1*vec3(sha,sha*0.3+0.7*sha*sha,sha*sha);
+ // spec
+ vec3 hal = normalize(lig-rd);
+ float spe = clamp( dot(nor,hal), 0.0, 1.0 );
+ float fre = clamp( dot(-rd,lig), 0.0, 1.0 );
+ fre = 0.2 + 0.8*pow(fre,5.0);
+ spe *= spe;
+ spe *= spe;
+ spe *= spe;
+ col += 1.0*(0.25+0.75*mate.x)*spe*dif*sha*fre;
+ }
+
+ // back light
+ {
+ vec3 lig = normalize(vec3(-1.0,0.0,0.0));
+ float dif = clamp(0.5+0.5*dot(nor,lig),0.0,1.0);
+ col += mate.rgb*dif*vec3(1.2,0.9,0.6)*0.2*mate.w;
+ }
+
+ // dome light
+ {
+ float dif = clamp(0.3+0.7*dot(nor,upp),0.0,1.0);
+ #if 0
+ dif *= 0.05 + 0.95*calcSoftshadow( pos+0.0001*nor, upp, 0.0001, 1.0, 1.0 );
+ col += mate.xyz*dif*5.0*vec3(0.1,0.1,0.3)*mate.w;
+ #else
+ col += mate.xyz*dif*3.0*vec3(0.1,0.1,0.3)*mate.w*(0.2+0.8*mate.w);
+ #endif
+ }
+
+ // fake sss
+ {
+ float fre = clamp(1.0+dot(rd,nor),0.0,1.0);
+ col += 0.3*vec3(1.0,0.3,0.2)*mate.xyz*mate.xyz*fre*fre*mate.w;
+ }
+
+ // grade/sss
+ {
+ col = 2.0*pow( col, vec3(0.7,0.85,1.0) );
+ }
+
+ // exposure control
+ col *= 0.7 + 0.3*smoothstep(0.0,25.0,abs(uTime-31.0));
+
+ // display fake occlusion
+ //col = mate.www;
+ }
+ }
+
+
+ // gamma
+ col = pow( col, vec3(0.4545) );
+
+ tot += col;
+ #if AA>1
+ }
+ tot /= float(AA*AA);
+ #endif
+
+ // vignetting
+ vec2 q = gl_FragCoord.xy/uResolution.xy;
+ tot *= pow( 16.0*q.x*q.y*(1.0-q.x)*(1.0-q.y), 0.2 );
+
+ fragColor = vec4( tot, 1.0 );
+}
diff --git a/examples/community/seascape-shader/shaders/seascape.glsl b/examples/community/seascape-shader/shaders/seascape.glsl
new file mode 100644
index 0000000..7a69f40
--- /dev/null
+++ b/examples/community/seascape-shader/shaders/seascape.glsl
@@ -0,0 +1,196 @@
+/*
+ * "Seascape" by Alexander Alekseev aka TDM - 2014
+ * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+ * Contact: tdmaav@gmail.com
+ */
+
+#version 330 core
+
+uniform vec2 uResolution;
+uniform float uTime;
+uniform vec4 uMouse;
+
+// This is how much you are drifting around. Zero means it only moves when the mouse moves
+uniform float uDrift;
+
+out vec4 fragColor;
+
+const int NUM_STEPS = 8;
+const float PI = 3.141592;
+const float EPSILON = 1e-3;
+#define EPSILON_NRM (0.1 / uResolution.x)
+
+// sea
+const int ITER_GEOMETRY = 3;
+const int ITER_FRAGMENT = 5;
+const float SEA_HEIGHT = 0.6;
+const float SEA_CHOPPY = 4.0;
+const float SEA_SPEED = 0.8;
+const float SEA_FREQ = 0.16;
+const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
+const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
+#define SEA_TIME (1.0 + uTime * SEA_SPEED)
+const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
+
+// math
+mat3 fromEuler(vec3 ang) {
+ vec2 a1 = vec2(sin(ang.x),cos(ang.x));
+ vec2 a2 = vec2(sin(ang.y),cos(ang.y));
+ vec2 a3 = vec2(sin(ang.z),cos(ang.z));
+ mat3 m;
+ m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
+ m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
+ m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
+ return m;
+}
+float hash( vec2 p ) {
+ float h = dot(p,vec2(127.1,311.7));
+ return fract(sin(h)*43758.5453123);
+}
+float noise( in vec2 p ) {
+ vec2 i = floor( p );
+ vec2 f = fract( p );
+ vec2 u = f*f*(3.0-2.0*f);
+ return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
+ hash( i + vec2(1.0,0.0) ), u.x),
+ mix( hash( i + vec2(0.0,1.0) ),
+ hash( i + vec2(1.0,1.0) ), u.x), u.y);
+}
+
+// lighting
+float diffuse(vec3 n,vec3 l,float p) {
+ return pow(dot(n,l) * 0.4 + 0.6,p);
+}
+float specular(vec3 n,vec3 l,vec3 e,float s) {
+ float nrm = (s + 8.0) / (PI * 8.0);
+ return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
+}
+
+// sky
+vec3 getSkyColor(vec3 e) {
+ e.y = max(e.y,0.0);
+ return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4);
+}
+
+// sea
+float sea_octave(vec2 uv, float choppy) {
+ uv += noise(uv);
+ vec2 wv = 1.0-abs(sin(uv));
+ vec2 swv = abs(cos(uv));
+ wv = mix(wv,swv,wv);
+ return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
+}
+
+float map(vec3 p) {
+ float freq = SEA_FREQ;
+ float amp = SEA_HEIGHT;
+ float choppy = SEA_CHOPPY;
+ vec2 uv = p.xz; uv.x *= 0.75;
+
+ float d, h = 0.0;
+ for(int i = 0; i < ITER_GEOMETRY; i++) {
+ d = sea_octave((uv+SEA_TIME)*freq,choppy);
+ d += sea_octave((uv-SEA_TIME)*freq,choppy);
+ h += d * amp;
+ uv *= octave_m; freq *= 1.9; amp *= 0.22;
+ choppy = mix(choppy,1.0,0.2);
+ }
+ return p.y - h;
+}
+
+float map_detailed(vec3 p) {
+ float freq = SEA_FREQ;
+ float amp = SEA_HEIGHT;
+ float choppy = SEA_CHOPPY;
+ vec2 uv = p.xz; uv.x *= 0.75;
+
+ float d, h = 0.0;
+ for(int i = 0; i < ITER_FRAGMENT; i++) {
+ d = sea_octave((uv+SEA_TIME)*freq,choppy);
+ d += sea_octave((uv-SEA_TIME)*freq,choppy);
+ h += d * amp;
+ uv *= octave_m; freq *= 1.9; amp *= 0.22;
+ choppy = mix(choppy,1.0,0.2);
+ }
+ return p.y - h;
+}
+
+vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {
+ float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0);
+ fresnel = pow(fresnel,3.0) * 0.65;
+
+ vec3 reflected = getSkyColor(reflect(eye,n));
+ vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;
+
+ vec3 color = mix(refracted,reflected,fresnel);
+
+ float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
+ color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
+
+ color += vec3(specular(n,l,eye,60.0));
+
+ return color;
+}
+
+// tracing
+vec3 getNormal(vec3 p, float eps) {
+ vec3 n;
+ n.y = map_detailed(p);
+ n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
+ n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
+ n.y = eps;
+ return normalize(n);
+}
+
+float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {
+ float tm = 0.0;
+ float tx = 1000.0;
+ float hx = map(ori + dir * tx);
+ if(hx > 0.0) return tx;
+ float hm = map(ori + dir * tm);
+ float tmid = 0.0;
+ for(int i = 0; i < NUM_STEPS; i++) {
+ tmid = mix(tm,tx, hm/(hm-hx));
+ p = ori + dir * tmid;
+ float hmid = map(p);
+ if(hmid < 0.0) {
+ tx = tmid;
+ hx = hmid;
+ } else {
+ tm = tmid;
+ hm = hmid;
+ }
+ }
+ return tmid;
+}
+
+// main
+void main()
+{
+ vec2 uv = gl_FragCoord.xy / uResolution.xy;
+ uv = uv * 2.0 - 1.0;
+ uv.x *= uResolution.x / uResolution.y;
+ float time = uTime * uDrift * 5 + uMouse.x*0.01;
+
+ // ray
+ vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);
+ vec3 ori = vec3(0.0,3.5,time*5.0);
+ vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;
+ dir = normalize(dir) * fromEuler(ang);
+
+ // tracing
+ vec3 p;
+ heightMapTracing(ori,dir,p);
+ vec3 dist = p - ori;
+ vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
+ vec3 light = normalize(vec3(0.0,1.0,0.8));
+
+ // color
+ vec3 color = mix(
+ getSkyColor(dir),
+ getSeaColor(p,n,light,dir,dist),
+ pow(smoothstep(0.0,-0.05,dir.y),0.3));
+
+ // post
+ fragColor = vec4(pow(color,vec3(0.75)), 1.0);
+}
diff --git a/examples/community/seascape-shader/shaders/seascape_original.glsl b/examples/community/seascape-shader/shaders/seascape_original.glsl
new file mode 100644
index 0000000..b70f887
--- /dev/null
+++ b/examples/community/seascape-shader/shaders/seascape_original.glsl
@@ -0,0 +1,184 @@
+/*
+ * "Seascape" by Alexander Alekseev aka TDM - 2014
+ * License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
+ * Contact: tdmaav@gmail.com
+ */
+
+const int NUM_STEPS = 8;
+const float PI = 3.141592;
+const float EPSILON = 1e-3;
+#define EPSILON_NRM (0.1 / iResolution.x)
+
+// sea
+const int ITER_GEOMETRY = 3;
+const int ITER_FRAGMENT = 5;
+const float SEA_HEIGHT = 0.6;
+const float SEA_CHOPPY = 4.0;
+const float SEA_SPEED = 0.8;
+const float SEA_FREQ = 0.16;
+const vec3 SEA_BASE = vec3(0.1,0.19,0.22);
+const vec3 SEA_WATER_COLOR = vec3(0.8,0.9,0.6);
+#define SEA_TIME (1.0 + iTime * SEA_SPEED)
+const mat2 octave_m = mat2(1.6,1.2,-1.2,1.6);
+
+// math
+mat3 fromEuler(vec3 ang) {
+ vec2 a1 = vec2(sin(ang.x),cos(ang.x));
+ vec2 a2 = vec2(sin(ang.y),cos(ang.y));
+ vec2 a3 = vec2(sin(ang.z),cos(ang.z));
+ mat3 m;
+ m[0] = vec3(a1.y*a3.y+a1.x*a2.x*a3.x,a1.y*a2.x*a3.x+a3.y*a1.x,-a2.y*a3.x);
+ m[1] = vec3(-a2.y*a1.x,a1.y*a2.y,a2.x);
+ m[2] = vec3(a3.y*a1.x*a2.x+a1.y*a3.x,a1.x*a3.x-a1.y*a3.y*a2.x,a2.y*a3.y);
+ return m;
+}
+float hash( vec2 p ) {
+ float h = dot(p,vec2(127.1,311.7));
+ return fract(sin(h)*43758.5453123);
+}
+float noise( in vec2 p ) {
+ vec2 i = floor( p );
+ vec2 f = fract( p );
+ vec2 u = f*f*(3.0-2.0*f);
+ return -1.0+2.0*mix( mix( hash( i + vec2(0.0,0.0) ),
+ hash( i + vec2(1.0,0.0) ), u.x),
+ mix( hash( i + vec2(0.0,1.0) ),
+ hash( i + vec2(1.0,1.0) ), u.x), u.y);
+}
+
+// lighting
+float diffuse(vec3 n,vec3 l,float p) {
+ return pow(dot(n,l) * 0.4 + 0.6,p);
+}
+float specular(vec3 n,vec3 l,vec3 e,float s) {
+ float nrm = (s + 8.0) / (PI * 8.0);
+ return pow(max(dot(reflect(e,n),l),0.0),s) * nrm;
+}
+
+// sky
+vec3 getSkyColor(vec3 e) {
+ e.y = max(e.y,0.0);
+ return vec3(pow(1.0-e.y,2.0), 1.0-e.y, 0.6+(1.0-e.y)*0.4);
+}
+
+// sea
+float sea_octave(vec2 uv, float choppy) {
+ uv += noise(uv);
+ vec2 wv = 1.0-abs(sin(uv));
+ vec2 swv = abs(cos(uv));
+ wv = mix(wv,swv,wv);
+ return pow(1.0-pow(wv.x * wv.y,0.65),choppy);
+}
+
+float map(vec3 p) {
+ float freq = SEA_FREQ;
+ float amp = SEA_HEIGHT;
+ float choppy = SEA_CHOPPY;
+ vec2 uv = p.xz; uv.x *= 0.75;
+
+ float d, h = 0.0;
+ for(int i = 0; i < ITER_GEOMETRY; i++) {
+ d = sea_octave((uv+SEA_TIME)*freq,choppy);
+ d += sea_octave((uv-SEA_TIME)*freq,choppy);
+ h += d * amp;
+ uv *= octave_m; freq *= 1.9; amp *= 0.22;
+ choppy = mix(choppy,1.0,0.2);
+ }
+ return p.y - h;
+}
+
+float map_detailed(vec3 p) {
+ float freq = SEA_FREQ;
+ float amp = SEA_HEIGHT;
+ float choppy = SEA_CHOPPY;
+ vec2 uv = p.xz; uv.x *= 0.75;
+
+ float d, h = 0.0;
+ for(int i = 0; i < ITER_FRAGMENT; i++) {
+ d = sea_octave((uv+SEA_TIME)*freq,choppy);
+ d += sea_octave((uv-SEA_TIME)*freq,choppy);
+ h += d * amp;
+ uv *= octave_m; freq *= 1.9; amp *= 0.22;
+ choppy = mix(choppy,1.0,0.2);
+ }
+ return p.y - h;
+}
+
+vec3 getSeaColor(vec3 p, vec3 n, vec3 l, vec3 eye, vec3 dist) {
+ float fresnel = clamp(1.0 - dot(n,-eye), 0.0, 1.0);
+ fresnel = pow(fresnel,3.0) * 0.65;
+
+ vec3 reflected = getSkyColor(reflect(eye,n));
+ vec3 refracted = SEA_BASE + diffuse(n,l,80.0) * SEA_WATER_COLOR * 0.12;
+
+ vec3 color = mix(refracted,reflected,fresnel);
+
+ float atten = max(1.0 - dot(dist,dist) * 0.001, 0.0);
+ color += SEA_WATER_COLOR * (p.y - SEA_HEIGHT) * 0.18 * atten;
+
+ color += vec3(specular(n,l,eye,60.0));
+
+ return color;
+}
+
+// tracing
+vec3 getNormal(vec3 p, float eps) {
+ vec3 n;
+ n.y = map_detailed(p);
+ n.x = map_detailed(vec3(p.x+eps,p.y,p.z)) - n.y;
+ n.z = map_detailed(vec3(p.x,p.y,p.z+eps)) - n.y;
+ n.y = eps;
+ return normalize(n);
+}
+
+float heightMapTracing(vec3 ori, vec3 dir, out vec3 p) {
+ float tm = 0.0;
+ float tx = 1000.0;
+ float hx = map(ori + dir * tx);
+ if(hx > 0.0) return tx;
+ float hm = map(ori + dir * tm);
+ float tmid = 0.0;
+ for(int i = 0; i < NUM_STEPS; i++) {
+ tmid = mix(tm,tx, hm/(hm-hx));
+ p = ori + dir * tmid;
+ float hmid = map(p);
+ if(hmid < 0.0) {
+ tx = tmid;
+ hx = hmid;
+ } else {
+ tm = tmid;
+ hm = hmid;
+ }
+ }
+ return tmid;
+}
+
+// main
+void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
+ vec2 uv = fragCoord.xy / iResolution.xy;
+ uv = uv * 2.0 - 1.0;
+ uv.x *= iResolution.x / iResolution.y;
+ float time = iTime * 0.3 + iMouse.x*0.01;
+
+ // ray
+ vec3 ang = vec3(sin(time*3.0)*0.1,sin(time)*0.2+0.3,time);
+ vec3 ori = vec3(0.0,3.5,time*5.0);
+ vec3 dir = normalize(vec3(uv.xy,-2.0)); dir.z += length(uv) * 0.15;
+ dir = normalize(dir) * fromEuler(ang);
+
+ // tracing
+ vec3 p;
+ heightMapTracing(ori,dir,p);
+ vec3 dist = p - ori;
+ vec3 n = getNormal(p, dot(dist,dist) * EPSILON_NRM);
+ vec3 light = normalize(vec3(0.0,1.0,0.8));
+
+ // color
+ vec3 color = mix(
+ getSkyColor(dir),
+ getSeaColor(p,n,light,dir,dist),
+ pow(smoothstep(0.0,-0.05,dir.y),0.3));
+
+ // post
+ fragColor = vec4(pow(color,vec3(0.75)), 1.0);
+}
\ No newline at end of file
diff --git a/examples/community/starfield/README.md b/examples/community/starfield/README.md
new file mode 100644
index 0000000..9afba9f
--- /dev/null
+++ b/examples/community/starfield/README.md
@@ -0,0 +1,20 @@
+# starfield
+
+Classic starfield… with [supposedly accurate stellar colors](http://www.vendian.org/mncharity/dir3/starcolor/)
+
+Made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments)
+
+## Controls
+
+Arrow up and down to change speed. Space bar to almost stop.
+
+## Screenshots
+
+![starfield animation](https://user-images.githubusercontent.com/565124/32411599-a5fcba72-c1df-11e7-8730-a570470a4eee.gif)
+
+![starfield screenshot](screenshot.png)
+
+## Links
+
+ - https://github.com/peterhellberg/pixel-experiments/tree/master/starfield
+ - https://gist.github.com/peterhellberg/4018e228cced61a0bb26991e49299c96
diff --git a/examples/community/starfield/screenshot.png b/examples/community/starfield/screenshot.png
new file mode 100644
index 0000000..b3d5298
Binary files /dev/null and b/examples/community/starfield/screenshot.png differ
diff --git a/examples/community/starfield/starfield.go b/examples/community/starfield/starfield.go
new file mode 100644
index 0000000..658ecdb
--- /dev/null
+++ b/examples/community/starfield/starfield.go
@@ -0,0 +1,165 @@
+package main
+
+import (
+ "image/color"
+ "math/rand"
+ "time"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+)
+
+const w, h = float64(1024), float64(512)
+
+var speed = float64(200)
+
+var stars [1024]*star
+
+func init() {
+ rand.Seed(4)
+
+ for i := 0; i < len(stars); i++ {
+ stars[i] = newStar()
+ }
+}
+
+type star struct {
+ pixel.Vec
+ Z float64
+ P float64
+ C color.RGBA
+}
+
+func newStar() *star {
+ return &star{
+ pixel.V(random(-w, w), random(-h, h)),
+ random(0, w), 0, Colors[rand.Intn(len(Colors))],
+ }
+}
+
+func (s *star) update(d float64) {
+ s.P = s.Z
+ s.Z -= d * speed
+
+ if s.Z < 0 {
+ s.X = random(-w, w)
+ s.Y = random(-h, h)
+ s.Z = w
+ s.P = s.Z
+ }
+}
+
+func (s *star) draw(imd *imdraw.IMDraw) {
+ p := pixel.V(
+ scale(s.X/s.Z, 0, 1, 0, w),
+ scale(s.Y/s.Z, 0, 1, 0, h),
+ )
+
+ o := pixel.V(
+ scale(s.X/s.P, 0, 1, 0, w),
+ scale(s.Y/s.P, 0, 1, 0, h),
+ )
+
+ r := scale(s.Z, 0, w, 11, 0)
+
+ imd.Color = s.C
+
+ if p.Sub(o).Len() > 6 {
+ imd.Push(p, o)
+ imd.Line(r)
+ }
+
+ imd.Push(p)
+ imd.Circle(r, 0)
+}
+
+func run() {
+ win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Bounds: pixel.R(0, 0, w, h),
+ VSync: true,
+ Undecorated: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+
+ imd := imdraw.New(nil)
+
+ imd.Precision = 7
+
+ imd.SetMatrix(pixel.IM.Moved(win.Bounds().Center()))
+
+ last := time.Now()
+
+ for !win.Closed() {
+ win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
+
+ if win.Pressed(pixelgl.KeyUp) {
+ speed += 10
+ }
+
+ if win.Pressed(pixelgl.KeyDown) {
+ if speed > 10 {
+ speed -= 10
+ }
+ }
+
+ if win.Pressed(pixelgl.KeySpace) {
+ speed = 100
+ }
+
+ d := time.Since(last).Seconds()
+
+ last = time.Now()
+
+ imd.Clear()
+
+ for _, s := range stars {
+ s.update(d)
+ s.draw(imd)
+ }
+
+ win.Clear(color.Black)
+ imd.Draw(win)
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func random(min, max float64) float64 {
+ return rand.Float64()*(max-min) + min
+}
+
+func scale(unscaledNum, min, max, minAllowed, maxAllowed float64) float64 {
+ return (maxAllowed-minAllowed)*(unscaledNum-min)/(max-min) + minAllowed
+}
+
+// Colors based on stellar types listed at
+// http://www.vendian.org/mncharity/dir3/starcolor/
+var Colors = []color.RGBA{
+ color.RGBA{157, 180, 255, 255},
+ color.RGBA{162, 185, 255, 255},
+ color.RGBA{167, 188, 255, 255},
+ color.RGBA{170, 191, 255, 255},
+ color.RGBA{175, 195, 255, 255},
+ color.RGBA{186, 204, 255, 255},
+ color.RGBA{192, 209, 255, 255},
+ color.RGBA{202, 216, 255, 255},
+ color.RGBA{228, 232, 255, 255},
+ color.RGBA{237, 238, 255, 255},
+ color.RGBA{251, 248, 255, 255},
+ color.RGBA{255, 249, 249, 255},
+ color.RGBA{255, 245, 236, 255},
+ color.RGBA{255, 244, 232, 255},
+ color.RGBA{255, 241, 223, 255},
+ color.RGBA{255, 235, 209, 255},
+ color.RGBA{255, 215, 174, 255},
+ color.RGBA{255, 198, 144, 255},
+ color.RGBA{255, 190, 127, 255},
+ color.RGBA{255, 187, 123, 255},
+ color.RGBA{255, 187, 123, 255},
+}
diff --git a/examples/community/sudoku/LICENSE b/examples/community/sudoku/LICENSE
new file mode 100644
index 0000000..ae925dc
--- /dev/null
+++ b/examples/community/sudoku/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 Jason Wangsadinata
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/community/sudoku/README.md b/examples/community/sudoku/README.md
new file mode 100644
index 0000000..73b7a1c
--- /dev/null
+++ b/examples/community/sudoku/README.md
@@ -0,0 +1,45 @@
+# Sudoku
+
+A game of Sudoku written in [Go][go].
+
+From [Wikipedia][sudoku-wiki]:
+> Sudoku (数独 sūdoku, digit-single) (/suːˈdoʊkuː/, /-ˈdɒk-/, /sə-/, originally
+> called Number Place) is a logic-based, combinatorial number-placement puzzle.
+> The objective is to fill a 9×9 grid with digits so that each column, each row,
+> and each of the nine 3×3 subgrids that compose the grid (also called "boxes",
+> "blocks", or "regions") contains all of the digits from 1 to 9. The puzzle
+> setter provides a partially completed grid, which for a well-posed puzzle has
+> a single solution.
+
+Created by [Jason Wangsadinata][jwangsadinata] using
+[Pixel][pixel].
+
+This example is kept to the bare minimum for demonstrating just the main
+concepts of using imdraw, and batch with texts. For a game with slightly more
+features, please check out [Go-Sudoku][go-sudoku]
+
+## Usage
+
+Run it the usual way:
+
+ go run sudoku.go
+
+## How to Play
+
+- *Left mouse* click to select a box.
+- *Number keys* to input a number.
+- *Space* or *Backspace* to delete a number.
+
+## Screenshots
+
+![Sudoku](sudoku.png)
+
+## Links
+
+- [Go-Sudoku][go-sudoku]
+
+[go]: https://golang.org
+[go-sudoku]: https://github.com/jwangsadinata/go-sudoku
+[jwangsadinata]: https://github.com/jwangsadinata
+[pixel]: https://github.com/faiface/pixel
+[sudoku-wiki]: https://en.wikipedia.org/wiki/Sudoku
diff --git a/examples/community/sudoku/sudoku.go b/examples/community/sudoku/sudoku.go
new file mode 100644
index 0000000..c1b3888
--- /dev/null
+++ b/examples/community/sudoku/sudoku.go
@@ -0,0 +1,217 @@
+package main
+
+import (
+ "image"
+ "os"
+ "strconv"
+
+ _ "image/png"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/imdraw"
+ "github.com/faiface/pixel/pixelgl"
+ "github.com/faiface/pixel/text"
+ "github.com/golang/freetype/truetype"
+ "golang.org/x/image/colornames"
+ "golang.org/x/image/font/gofont/goregular"
+)
+
+const (
+ width = 900
+)
+
+var input bool
+var x, y int
+
+var board = [9][9]int{}
+var puzzle = [9][9]int{
+ {6, 7, 2, 3, 4, 1, 5, 8, 9},
+ {5, 3, 4, 9, 6, 8, 1, 2, 7},
+ {8, 9, 1, 7, 5, 2, 6, 3, 4},
+ {3, 5, 6, 8, 2, 9, 4, 7, 1},
+ {7, 2, 8, 4, 1, 5, 3, 9, 6},
+ {4, 1, 9, 6, 7, 3, 8, 5, 2},
+ {1, 8, 3, 2, 9, 6, 7, 4, 5},
+ {9, 6, 7, 5, 3, 4, 2, 1, 8},
+ {2, 4, 5, 1, 8, 7, 9, 6, 3},
+}
+
+var mask = [9][9]bool{
+ {false, false, true, false, false, true, true, true, false},
+ {false, false, true, true, true, false, false, false, true},
+ {true, false, false, false, false, false, true, true, false},
+ {false, true, true, false, true, false, true, true, false},
+ {false, true, false, false, false, true, false, true, false},
+ {false, true, true, false, true, false, true, true, false},
+ {false, true, true, false, false, false, false, false, true},
+ {true, false, false, false, true, true, true, false, false},
+ {false, true, true, true, false, false, true, false, false},
+}
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func updateBoard(value int, imd *imdraw.IMDraw) {
+ board[x][y] = value
+ imd.Clear()
+ input = false
+}
+
+func run() {
+ // initialize window
+ cfg := pixelgl.WindowConfig{
+ Title: "Sudoku",
+ Bounds: pixel.R(0, 0, width, width),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ // initialize imdraw
+ imd := imdraw.New(nil)
+
+ // initialize font
+ ttf, err := truetype.Parse(goregular.TTF)
+ if err != nil {
+ panic(err)
+ }
+ face := truetype.NewFace(ttf, &truetype.Options{
+ Size: 64 * width / 900,
+ })
+ atlas := text.NewAtlas(face, text.ASCII)
+
+ // initialize batch
+ batch := pixel.NewBatch(&pixel.TrianglesData{}, atlas.Picture())
+
+ // initialize text placeholder
+ num := text.New(pixel.ZV, atlas)
+
+ // setup game
+ for i := 0; i < 9; i++ {
+ for j := 0; j < 9; j++ {
+ if mask[i][j] {
+ board[i][j] = puzzle[i][j]
+ }
+ }
+ }
+
+ for !win.Closed() {
+ // win condition
+ if board == puzzle {
+ win.SetClosed(true)
+ }
+
+ // select a box
+ if win.JustPressed(pixelgl.MouseButtonLeft) {
+ if input {
+ imd.Clear()
+ }
+ pos := win.MousePosition()
+ x = int(pos.X) / (width / 9)
+ y = int(pos.Y) / (width / 9)
+
+ if !mask[x][y] {
+ imd.Color = colornames.Paleturquoise
+ imd.Push(pixel.V(float64(x*width/9)+1, float64(y*width/9)+1),
+ pixel.V(float64((x+1)*width/9)-1, float64((y+1)*width/9)-1))
+ imd.Rectangle(0)
+ input = true
+ }
+ }
+ // act on user input
+ if input && !mask[x][y] {
+ if win.JustPressed(pixelgl.Key1) || win.JustPressed(pixelgl.KeyKP1) {
+ updateBoard(1, imd)
+ }
+ if win.JustPressed(pixelgl.Key2) || win.JustPressed(pixelgl.KeyKP2) {
+ updateBoard(2, imd)
+ }
+ if win.JustPressed(pixelgl.Key3) || win.JustPressed(pixelgl.KeyKP3) {
+ updateBoard(3, imd)
+ }
+ if win.JustPressed(pixelgl.Key4) || win.JustPressed(pixelgl.KeyKP4) {
+ updateBoard(4, imd)
+ }
+ if win.JustPressed(pixelgl.Key5) || win.JustPressed(pixelgl.KeyKP5) {
+ updateBoard(5, imd)
+ }
+ if win.JustPressed(pixelgl.Key6) || win.JustPressed(pixelgl.KeyKP6) {
+ updateBoard(6, imd)
+ }
+ if win.JustPressed(pixelgl.Key7) || win.JustPressed(pixelgl.KeyKP7) {
+ updateBoard(7, imd)
+ }
+ if win.JustPressed(pixelgl.Key8) || win.JustPressed(pixelgl.KeyKP8) {
+ updateBoard(8, imd)
+ }
+ if win.JustPressed(pixelgl.Key9) || win.JustPressed(pixelgl.KeyKP9) {
+ updateBoard(9, imd)
+ }
+ if win.JustPressed(pixelgl.Key0) || win.JustPressed(pixelgl.KeyKP0) ||
+ win.JustPressed(pixelgl.KeyBackspace) || win.JustPressed(pixelgl.KeySpace) {
+ updateBoard(0, imd)
+ }
+ }
+
+ // set up lines for drawing
+ for i := 1; i < 9; i++ {
+ imd.Color = colornames.Black
+ if i%3 == 0 {
+ imd.Push(pixel.V(float64(i*width/9), 0), pixel.V(float64(i*width/9), width))
+ imd.Line(6)
+
+ imd.Push(pixel.V(0, float64(i*width/9)), pixel.V(width, float64(i*width/9)))
+ imd.Line(6)
+ } else {
+ imd.Push(pixel.V(float64(i*width/9), 0), pixel.V(float64(i*width/9), width))
+ imd.Line(3)
+
+ imd.Push(pixel.V(0, float64(i*width/9)), pixel.V(width, float64(i*width/9)))
+ imd.Line(3)
+ }
+ }
+
+ // set up numbers for drawing
+ batch.Clear()
+ for a, sa := range board {
+ for b, sb := range sa {
+ if sb != 0 {
+ num.Clear()
+ num.WriteString(strconv.Itoa(sb))
+ num.DrawColorMask(batch,
+ pixel.IM.
+ Scaled(
+ pixel.ZV, float64(width)/900).
+ Moved(
+ pixel.V((float64(a)+0.3)*width/9,
+ (float64(b)+0.25)*width/9)),
+ colornames.Black)
+ }
+ }
+ }
+
+ // draw the scene to the window
+ win.Clear(colornames.Snow)
+ batch.Draw(win)
+ imd.Draw(win)
+
+ // update the window
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/community/sudoku/sudoku.png b/examples/community/sudoku/sudoku.png
new file mode 100644
index 0000000..6e317fd
Binary files /dev/null and b/examples/community/sudoku/sudoku.png differ
diff --git a/examples/community/tilemap/.gitignore b/examples/community/tilemap/.gitignore
new file mode 100644
index 0000000..912305e
--- /dev/null
+++ b/examples/community/tilemap/.gitignore
@@ -0,0 +1 @@
+tilemap
diff --git a/examples/community/tilemap/README.md b/examples/community/tilemap/README.md
new file mode 100644
index 0000000..766dd7b
--- /dev/null
+++ b/examples/community/tilemap/README.md
@@ -0,0 +1,11 @@
+# Pixel Tiles Example
+This is a very simple example of how you might load a tilemap generated with [Tiled](https://www.mapeditor.org/) (i.e. a .tmx file),
+in a game with [Pixel](https://github.com/faiface/pixel) for Go.
+
+The tilemap used in this example is humbly borrowed from James Bowman's repo here: [https://github.com/jamesbowman/tiled-maps](https://github.com/jamesbowman/tiled-maps).
+
+## Preview
+![Screenshot of Tiles Window](./screenshot.png)
+
+## Tilemap
+![Tilemap](./gameart2d-desert.png)
diff --git a/examples/community/tilemap/gameart2d-desert.png b/examples/community/tilemap/gameart2d-desert.png
new file mode 100644
index 0000000..89170fd
Binary files /dev/null and b/examples/community/tilemap/gameart2d-desert.png differ
diff --git a/examples/community/tilemap/gameart2d-desert.tmx b/examples/community/tilemap/gameart2d-desert.tmx
new file mode 100644
index 0000000..3102285
--- /dev/null
+++ b/examples/community/tilemap/gameart2d-desert.tmx
@@ -0,0 +1,60 @@
+
+
diff --git a/examples/community/tilemap/main.go b/examples/community/tilemap/main.go
new file mode 100644
index 0000000..a10d6d4
--- /dev/null
+++ b/examples/community/tilemap/main.go
@@ -0,0 +1,158 @@
+package main
+
+import (
+ "fmt"
+ "image/png"
+ "math"
+ "os"
+ "time"
+
+ "golang.org/x/image/colornames"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "github.com/salviati/go-tmx/tmx"
+)
+
+var clearColor = colornames.Skyblue
+
+func gameloop(win *pixelgl.Window, tilemap *tmx.Map) {
+ batches := make([]*pixel.Batch, 0)
+ batchIndices := make(map[string]int)
+ batchCounter := 0
+
+ // Load the sprites
+ sprites := make(map[string]*pixel.Sprite)
+ for _, tileset := range tilemap.Tilesets {
+ if _, alreadyLoaded := sprites[tileset.Image.Source]; !alreadyLoaded {
+ sprite, pictureData := loadSprite(tileset.Image.Source)
+ sprites[tileset.Image.Source] = sprite
+ batches = append(batches, pixel.NewBatch(&pixel.TrianglesData{}, pictureData))
+ batchIndices[tileset.Image.Source] = batchCounter
+ batchCounter++
+ }
+ }
+
+ var (
+ camPos = pixel.ZV
+ camSpeed = 1000.0
+ camZoom = 0.2
+ camZoomSpeed = 1.2
+ )
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ // Camera movement
+ cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
+ win.SetMatrix(cam)
+ if win.Pressed(pixelgl.KeyLeft) {
+ camPos.X -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyRight) {
+ camPos.X += camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyDown) {
+ camPos.Y -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyUp) {
+ camPos.Y += camSpeed * dt
+ }
+ camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
+
+ win.Clear(clearColor)
+
+ // Draw tiles
+ for _, batch := range batches {
+ batch.Clear()
+ }
+
+ for _, layer := range tilemap.Layers {
+ for tileIndex, tile := range layer.DecodedTiles {
+ ts := layer.Tileset
+ tID := int(tile.ID)
+
+ if tID == 0 {
+ // Tile ID 0 means blank, skip it.
+ continue
+ }
+
+ // Calculate the framing for the tile within its tileset's source image
+ numRows := ts.Tilecount / ts.Columns
+ x, y := tileIDToCoord(tID, ts.Columns, numRows)
+ gamePos := indexToGamePos(tileIndex, tilemap.Width, tilemap.Height)
+
+ iX := float64(x) * float64(ts.TileWidth)
+ fX := iX + float64(ts.TileWidth)
+ iY := float64(y) * float64(ts.TileHeight)
+ fY := iY + float64(ts.TileHeight)
+
+ sprite := sprites[ts.Image.Source]
+ sprite.Set(sprite.Picture(), pixel.R(iX, iY, fX, fY))
+ pos := gamePos.ScaledXY(pixel.V(float64(ts.TileWidth), float64(ts.TileHeight)))
+ sprite.Draw(batches[batchIndices[ts.Image.Source]], pixel.IM.Moved(pos))
+ }
+ }
+
+ for _, batch := range batches {
+ batch.Draw(win)
+ }
+ win.Update()
+ }
+}
+
+func tileIDToCoord(tID int, numColumns int, numRows int) (x int, y int) {
+ x = tID % numColumns
+ y = numRows - (tID / numColumns) - 1
+ return
+}
+
+func indexToGamePos(idx int, width int, height int) pixel.Vec {
+ gamePos := pixel.V(
+ float64(idx%width)-1,
+ float64(height)-float64(idx/width),
+ )
+ return gamePos
+}
+
+func run() {
+ // Create the window with OpenGL
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Tilemaps",
+ Bounds: pixel.R(0, 0, 800, 600),
+ VSync: true,
+ }
+
+ win, err := pixelgl.NewWindow(cfg)
+ panicIfErr(err)
+
+ // Initialize art assets (i.e. the tilemap)
+ tilemap, err := tmx.ReadFile("gameart2d-desert.tmx")
+ panicIfErr(err)
+
+ fmt.Println("use WASD to move camera around")
+ gameloop(win, tilemap)
+}
+
+func loadSprite(path string) (*pixel.Sprite, *pixel.PictureData) {
+ f, err := os.Open(path)
+ panicIfErr(err)
+
+ img, err := png.Decode(f)
+ panicIfErr(err)
+
+ pd := pixel.PictureDataFromImage(img)
+ return pixel.NewSprite(pd, pd.Bounds()), pd
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func panicIfErr(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/examples/community/tilemap/screenshot.png b/examples/community/tilemap/screenshot.png
new file mode 100644
index 0000000..afbd7f3
Binary files /dev/null and b/examples/community/tilemap/screenshot.png differ
diff --git a/examples/community/video-modes/README.md b/examples/community/video-modes/README.md
new file mode 100644
index 0000000..d5fbc0e
--- /dev/null
+++ b/examples/community/video-modes/README.md
@@ -0,0 +1,12 @@
+# Video Modes
+
+Just a demo on how to retrieve and set video modes with Pixel.
+
+Made by [David Linus Briemann](https://github.com/dbriemann/).
+
+## Controls
+
+ESC - Quit
+W - Toggle between fullscreen and windowed.
+
+Switch video modes with the shown characters.
diff --git a/examples/community/video-modes/main.go b/examples/community/video-modes/main.go
new file mode 100644
index 0000000..a561c89
--- /dev/null
+++ b/examples/community/video-modes/main.go
@@ -0,0 +1,116 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/faiface/pixel"
+ "github.com/faiface/pixel/pixelgl"
+ "github.com/faiface/pixel/text"
+ "golang.org/x/image/colornames"
+ "golang.org/x/image/font/basicfont"
+)
+
+type setting struct {
+ mode *pixelgl.VideoMode
+ monitor *pixelgl.Monitor
+}
+
+var (
+ texts []*text.Text
+ staticText *text.Text
+ settings []setting
+ activeSetting *setting
+ isFullScreen = false
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Video Modes",
+ Bounds: pixel.R(0, 0, 800, 600),
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ atlas := text.NewAtlas(basicfont.Face7x13, text.ASCII)
+
+ // Retrieve all monitors.
+ monitors := pixelgl.Monitors()
+
+ texts = make([]*text.Text, len(monitors))
+ key := byte('0')
+ for i := 0; i < len(monitors); i++ {
+ // Retrieve all video modes for a specific monitor.
+ modes := monitors[i].VideoModes()
+ for j := 0; j < len(modes); j++ {
+ settings = append(settings, setting{
+ monitor: monitors[i],
+ mode: &modes[j],
+ })
+ }
+
+ texts[i] = text.New(pixel.V(10+250*float64(i), -20), atlas)
+ texts[i].Color = colornames.Black
+ texts[i].WriteString(fmt.Sprintf("MONITOR %s\n\n", monitors[i].Name()))
+
+ for _, v := range modes {
+ texts[i].WriteString(fmt.Sprintf("(%c) %dx%d @ %d hz\n", key, v.Width, v.Height, v.RefreshRate))
+ key++
+ }
+ }
+
+ staticText = text.New(pixel.V(10, 30), atlas)
+ staticText.Color = colornames.Black
+ staticText.WriteString("ESC to exit\nW toggles windowed/fullscreen")
+
+ activeSetting = &settings[0]
+
+ for !win.Closed() {
+ win.Clear(colornames.Antiquewhite)
+
+ for _, txt := range texts {
+ txt.Draw(win, pixel.IM.Moved(pixel.V(0, win.Bounds().H())))
+ }
+ staticText.Draw(win, pixel.IM)
+
+ if win.JustPressed(pixelgl.KeyEscape) {
+ win.SetClosed(true)
+ }
+
+ if win.JustPressed(pixelgl.KeyW) {
+ if isFullScreen {
+ // Switch to windowed and backup the correct monitor.
+ win.SetMonitor(nil)
+ isFullScreen = false
+ } else {
+ // Switch to fullscreen.
+ win.SetMonitor(activeSetting.monitor)
+ isFullScreen = true
+ }
+ win.SetBounds(pixel.R(0, 0, float64(activeSetting.mode.Width), float64(activeSetting.mode.Height)))
+ }
+
+ input := win.Typed()
+ if len(input) > 0 {
+ key := int(input[0]) - 48
+ fmt.Println(key)
+ if key >= 0 && key < len(settings) {
+ activeSetting = &settings[key]
+
+ if isFullScreen {
+ win.SetMonitor(activeSetting.monitor)
+ } else {
+ win.SetMonitor(nil)
+ }
+ win.SetBounds(pixel.R(0, 0, float64(activeSetting.mode.Width), float64(activeSetting.mode.Height)))
+ }
+ }
+
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/go.mod b/examples/go.mod
new file mode 100644
index 0000000..5dae761
--- /dev/null
+++ b/examples/go.mod
@@ -0,0 +1,17 @@
+module github.com/faiface/pixel-examples
+
+go 1.16
+
+require (
+ github.com/aquilax/go-perlin v1.0.0
+ github.com/faiface/beep v1.0.2
+ github.com/faiface/pixel v0.10.0
+ github.com/go-gl/glfw v0.0.0-20210410170116-ea3d685f79fb
+ github.com/go-gl/mathgl v1.0.0
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
+ github.com/pkg/errors v0.9.1
+ github.com/pkg/profile v1.5.0
+ github.com/salviati/go-tmx v0.0.0-20180901011116-8dae25beffeb
+ github.com/sqweek/dialog v0.0.0-20200911184034-8a3d98e8211d
+ golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
+)
diff --git a/examples/go.sum b/examples/go.sum
new file mode 100644
index 0000000..2bf3d78
--- /dev/null
+++ b/examples/go.sum
@@ -0,0 +1,71 @@
+github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf h1:FPsprx82rdrX2jiKyS17BH6IrTmUBYqZa/CXT4uvb+I=
+github.com/TheTitanrain/w32 v0.0.0-20180517000239-4f5cfb03fabf/go.mod h1:peYoMncQljjNS6tZwI9WVyQB3qZS6u79/N3mBOcnd3I=
+github.com/aquilax/go-perlin v1.0.0 h1:7KBttX3KwqipwhmIVE/B2cEZVYiOZpoE/q8HsS6HBoQ=
+github.com/aquilax/go-perlin v1.0.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE=
+github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/faiface/beep v1.0.2 h1:UB5DiRNmA4erfUYnHbgU4UB6DlBOrsdEFRtcc8sCkdQ=
+github.com/faiface/beep v1.0.2/go.mod h1:1yLb5yRdHMsovYYWVqYLioXkVuziCSITW1oarTeduQM=
+github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0=
+github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g=
+github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
+github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
+github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0=
+github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A=
+github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
+github.com/gdamore/tcell v1.1.1/go.mod h1:K1udHkiR3cOtlpKG5tZPD5XxrF7v2y7lDq7Whcj+xkQ=
+github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
+github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
+github.com/go-gl/glfw v0.0.0-20210410170116-ea3d685f79fb h1:yMlfD+jmoG8wwBVLaFwtcgNBY207iWsYwtNC9u+T2yk=
+github.com/go-gl/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
+github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
+github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
+github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gopherjs/gopherwasm v0.1.1/go.mod h1:kx4n9a+MzHH0BJJhvlsQ65hqLFXDO/m256AsaDPQ+/4=
+github.com/gopherjs/gopherwasm v1.0.0 h1:32nge/RlujS1Im4HNCJPp0NbBOAeBXFuT1KonUuLl+Y=
+github.com/gopherjs/gopherwasm v1.0.0/go.mod h1:SkZ8z7CWBz5VXbhJel8TxCmAcsQqzgWGR/8nMhyhZSI=
+github.com/hajimehoshi/go-mp3 v0.1.1/go.mod h1:4i+c5pDNKDrxl1iu9iG90/+fhP37lio6gNhjCx9WBJw=
+github.com/hajimehoshi/oto v0.1.1/go.mod h1:hUiLWeBQnbDu4pZsAhOnGqMI1ZGibS6e2qhQdfpwz04=
+github.com/hajimehoshi/oto v0.3.1 h1:cpf/uIv4Q0oc5uf9loQn7PIehv+mZerh+0KKma6gzMk=
+github.com/hajimehoshi/oto v0.3.1/go.mod h1:e9eTLBB9iZto045HLbzfHJIc+jP3xaKrjZTghvb6fdM=
+github.com/jfreymuth/oggvorbis v1.0.0 h1:aOpiihGrFLXpsh2osOlEvTcg5/aluzGQeC7m3uYWOZ0=
+github.com/jfreymuth/oggvorbis v1.0.0/go.mod h1:abe6F9QRjuU9l+2jek3gj46lu40N4qlYxh2grqkLEDM=
+github.com/jfreymuth/vorbis v1.0.0 h1:SmDf783s82lIjGZi8EGUUaS7YxPHgRj4ZXW/h7rUi7U=
+github.com/jfreymuth/vorbis v1.0.0/go.mod h1:8zy3lUAm9K/rJJk223RKy6vjCZTWC61NA2QD06bfOE0=
+github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
+github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mewkiz/flac v1.0.5/go.mod h1:EHZNU32dMF6alpurYyKHDLYpW1lYpBZ5WrXi/VuNIGs=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug=
+github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/salviati/go-tmx v0.0.0-20180901011116-8dae25beffeb h1:xnT9hS4WTIDoD5dKlbDpBANISLHbW4JCEp4C5Mb4RLY=
+github.com/salviati/go-tmx v0.0.0-20180901011116-8dae25beffeb/go.mod h1:MHMSHqNIP7nhf3dSczdtzy/1mKyYaQP7sHbXwmXvvaA=
+github.com/sqweek/dialog v0.0.0-20200911184034-8a3d98e8211d h1:Chay1rwJnXxI27H+pzu7P81BKf647un9GOoRPTdXN18=
+github.com/sqweek/dialog v0.0.0-20200911184034-8a3d98e8211d/go.mod h1:/qNPSY91qTz/8TgHEMioAUc6q7+3SOybeKczHMXFcXw=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd h1:nLIcFw7GiqKXUS7HiChg6OAYWgASB2H97dZKd1GhDSs=
+golang.org/x/exp v0.0.0-20180710024300-14dda7b62fcd/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
+golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
+golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/mobile v0.0.0-20180806140643-507816974b79 h1:t2JRgCWkY7Qaa1J2jal+wqC9OjbyHCHwIA9rVlRUSMo=
+golang.org/x/mobile v0.0.0-20180806140643-507816974b79/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb h1:pf3XwC90UUdNPYWZdFjhGBE7DUFuK3Ct1zWmZ65QN30=
+golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
diff --git a/examples/guide/01_creating_a_window/main.go b/examples/guide/01_creating_a_window/main.go
new file mode 100644
index 0000000..be73b2c
--- /dev/null
+++ b/examples/guide/01_creating_a_window/main.go
@@ -0,0 +1,29 @@
+package main
+
+import (
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ win.Clear(colornames.Skyblue)
+
+ for !win.Closed() {
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/02_drawing_a_sprite/hiking.png b/examples/guide/02_drawing_a_sprite/hiking.png
new file mode 100644
index 0000000..83496bc
Binary files /dev/null and b/examples/guide/02_drawing_a_sprite/hiking.png differ
diff --git a/examples/guide/02_drawing_a_sprite/main.go b/examples/guide/02_drawing_a_sprite/main.go
new file mode 100644
index 0000000..f1ce974
--- /dev/null
+++ b/examples/guide/02_drawing_a_sprite/main.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "image"
+ "os"
+
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ pic, err := loadPicture("hiking.png")
+ if err != nil {
+ panic(err)
+ }
+
+ sprite := pixel.NewSprite(pic, pic.Bounds())
+
+ win.Clear(colornames.Greenyellow)
+
+ sprite.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+
+ for !win.Closed() {
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/03_moving_scaling_and_rotating_with_matrix/hiking.png b/examples/guide/03_moving_scaling_and_rotating_with_matrix/hiking.png
new file mode 100644
index 0000000..83496bc
Binary files /dev/null and b/examples/guide/03_moving_scaling_and_rotating_with_matrix/hiking.png differ
diff --git a/examples/guide/03_moving_scaling_and_rotating_with_matrix/main.go b/examples/guide/03_moving_scaling_and_rotating_with_matrix/main.go
new file mode 100644
index 0000000..f767857
--- /dev/null
+++ b/examples/guide/03_moving_scaling_and_rotating_with_matrix/main.go
@@ -0,0 +1,70 @@
+package main
+
+import (
+ "image"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ win.SetSmooth(true)
+
+ pic, err := loadPicture("hiking.png")
+ if err != nil {
+ panic(err)
+ }
+
+ sprite := pixel.NewSprite(pic, pic.Bounds())
+
+ angle := 0.0
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ angle += 3 * dt
+
+ win.Clear(colornames.Firebrick)
+
+ mat := pixel.IM
+ mat = mat.Rotated(pixel.ZV, angle)
+ mat = mat.Moved(win.Bounds().Center())
+ sprite.Draw(win, mat)
+
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/04_pressing_keys_and_clicking_mouse/main.go b/examples/guide/04_pressing_keys_and_clicking_mouse/main.go
new file mode 100644
index 0000000..8a327f1
--- /dev/null
+++ b/examples/guide/04_pressing_keys_and_clicking_mouse/main.go
@@ -0,0 +1,102 @@
+package main
+
+import (
+ "image"
+ "math"
+ "math/rand"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ spritesheet, err := loadPicture("trees.png")
+ if err != nil {
+ panic(err)
+ }
+
+ var treesFrames []pixel.Rect
+ for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 {
+ for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 {
+ treesFrames = append(treesFrames, pixel.R(x, y, x+32, y+32))
+ }
+ }
+
+ var (
+ camPos = pixel.ZV
+ camSpeed = 500.0
+ camZoom = 1.0
+ camZoomSpeed = 1.2
+ trees []*pixel.Sprite
+ matrices []pixel.Matrix
+ )
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
+ win.SetMatrix(cam)
+
+ if win.JustPressed(pixelgl.MouseButtonLeft) {
+ tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
+ trees = append(trees, tree)
+ mouse := cam.Unproject(win.MousePosition())
+ matrices = append(matrices, pixel.IM.Scaled(pixel.ZV, 4).Moved(mouse))
+ }
+ if win.Pressed(pixelgl.KeyLeft) {
+ camPos.X -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyRight) {
+ camPos.X += camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyDown) {
+ camPos.Y -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyUp) {
+ camPos.Y += camSpeed * dt
+ }
+ camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
+
+ win.Clear(colornames.Forestgreen)
+
+ for i, tree := range trees {
+ tree.Draw(win, matrices[i])
+ }
+
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/04_pressing_keys_and_clicking_mouse/trees.png b/examples/guide/04_pressing_keys_and_clicking_mouse/trees.png
new file mode 100644
index 0000000..73a6bec
Binary files /dev/null and b/examples/guide/04_pressing_keys_and_clicking_mouse/trees.png differ
diff --git a/examples/guide/05_drawing_efficiently_with_batch/main.go b/examples/guide/05_drawing_efficiently_with_batch/main.go
new file mode 100644
index 0000000..ab2d2a4
--- /dev/null
+++ b/examples/guide/05_drawing_efficiently_with_batch/main.go
@@ -0,0 +1,110 @@
+package main
+
+import (
+ "fmt"
+ "image"
+ "math"
+ "math/rand"
+ "os"
+ "time"
+
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ spritesheet, err := loadPicture("trees.png")
+ if err != nil {
+ panic(err)
+ }
+
+ batch := pixel.NewBatch(&pixel.TrianglesData{}, spritesheet)
+
+ var treesFrames []pixel.Rect
+ for x := spritesheet.Bounds().Min.X; x < spritesheet.Bounds().Max.X; x += 32 {
+ for y := spritesheet.Bounds().Min.Y; y < spritesheet.Bounds().Max.Y; y += 32 {
+ treesFrames = append(treesFrames, pixel.R(x, y, x+32, y+32))
+ }
+ }
+
+ var (
+ camPos = pixel.ZV
+ camSpeed = 500.0
+ camZoom = 1.0
+ camZoomSpeed = 1.2
+ )
+
+ var (
+ frames = 0
+ second = time.Tick(time.Second)
+ )
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center().Sub(camPos))
+ win.SetMatrix(cam)
+
+ if win.Pressed(pixelgl.MouseButtonLeft) {
+ tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
+ mouse := cam.Unproject(win.MousePosition())
+ tree.Draw(batch, pixel.IM.Scaled(pixel.ZV, 4).Moved(mouse))
+ }
+ if win.Pressed(pixelgl.KeyLeft) {
+ camPos.X -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyRight) {
+ camPos.X += camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyDown) {
+ camPos.Y -= camSpeed * dt
+ }
+ if win.Pressed(pixelgl.KeyUp) {
+ camPos.Y += camSpeed * dt
+ }
+ camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y)
+
+ win.Clear(colornames.Forestgreen)
+ batch.Draw(win)
+ win.Update()
+
+ frames++
+ select {
+ case <-second:
+ win.SetTitle(fmt.Sprintf("%s | FPS: %d", cfg.Title, frames))
+ frames = 0
+ default:
+ }
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/05_drawing_efficiently_with_batch/trees.png b/examples/guide/05_drawing_efficiently_with_batch/trees.png
new file mode 100644
index 0000000..73a6bec
Binary files /dev/null and b/examples/guide/05_drawing_efficiently_with_batch/trees.png differ
diff --git a/examples/guide/06_drawing_shapes_with_imdraw/main.go b/examples/guide/06_drawing_shapes_with_imdraw/main.go
new file mode 100644
index 0000000..69ad7a1
--- /dev/null
+++ b/examples/guide/06_drawing_shapes_with_imdraw/main.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+ "math"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ imd := imdraw.New(nil)
+
+ imd.Color = colornames.Blueviolet
+ imd.EndShape = imdraw.RoundEndShape
+ imd.Push(pixel.V(100, 100), pixel.V(700, 100))
+ imd.EndShape = imdraw.SharpEndShape
+ imd.Push(pixel.V(100, 500), pixel.V(700, 500))
+ imd.Line(30)
+
+ imd.Color = colornames.Limegreen
+ imd.Push(pixel.V(500, 500))
+ imd.Circle(300, 50)
+ imd.Color = colornames.Navy
+ imd.Push(pixel.V(200, 500), pixel.V(800, 500))
+ imd.Ellipse(pixel.V(120, 80), 0)
+
+ imd.Color = colornames.Red
+ imd.EndShape = imdraw.RoundEndShape
+ imd.Push(pixel.V(500, 350))
+ imd.CircleArc(150, -math.Pi, 0, 30)
+
+ for !win.Closed() {
+ win.Clear(colornames.Aliceblue)
+ imd.Draw(win)
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/guide/07_typing_text_on_the_screen/intuitive.ttf b/examples/guide/07_typing_text_on_the_screen/intuitive.ttf
new file mode 100644
index 0000000..9039d7b
Binary files /dev/null and b/examples/guide/07_typing_text_on_the_screen/intuitive.ttf differ
diff --git a/examples/guide/07_typing_text_on_the_screen/main.go b/examples/guide/07_typing_text_on_the_screen/main.go
new file mode 100644
index 0000000..51df8ce
--- /dev/null
+++ b/examples/guide/07_typing_text_on_the_screen/main.go
@@ -0,0 +1,78 @@
+package main
+
+import (
+ "io/ioutil"
+ "os"
+ "time"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "github.com/gopxl/pixel/text"
+ "github.com/golang/freetype/truetype"
+ "golang.org/x/image/colornames"
+ "golang.org/x/image/font"
+)
+
+func loadTTF(path string, size float64) (font.Face, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ bytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ return nil, err
+ }
+
+ font, err := truetype.Parse(bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ return truetype.NewFace(font, &truetype.Options{
+ Size: size,
+ GlyphCacheEntries: 1,
+ }), nil
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ win.SetSmooth(true)
+
+ face, err := loadTTF("intuitive.ttf", 80)
+ if err != nil {
+ panic(err)
+ }
+
+ atlas := text.NewAtlas(face, text.ASCII)
+ txt := text.New(pixel.V(50, 500), atlas)
+
+ txt.Color = colornames.Lightgrey
+
+ fps := time.Tick(time.Second / 120)
+
+ for !win.Closed() {
+ txt.WriteString(win.Typed())
+ if win.JustPressed(pixelgl.KeyEnter) || win.Repeated(pixelgl.KeyEnter) {
+ txt.WriteRune('\n')
+ }
+
+ win.Clear(colornames.Darkcyan)
+ txt.Draw(win, pixel.IM.Moved(win.Bounds().Center().Sub(txt.Bounds().Center())))
+ win.Update()
+
+ <-fps
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/lights/README.md b/examples/lights/README.md
new file mode 100644
index 0000000..75c3909
--- /dev/null
+++ b/examples/lights/README.md
@@ -0,0 +1,13 @@
+# Lights
+
+This example demonstrates powerful Porter-Duff composition used to create a nice noisy light effect.
+
+**Use W and S keys** to adjust the level of "dust".
+
+The FPS is limited to 30, because the effect is a little expensive and my computer couldn't handle
+60 FPS. If you have a more powerful computer (which is quite likely), peek into the code and disable
+the limit.
+
+Credit for the panda art goes to [Ján Štrba](https://www.artstation.com/artist/janstrba).
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/lights/main.go b/examples/lights/main.go
new file mode 100644
index 0000000..61cedbd
--- /dev/null
+++ b/examples/lights/main.go
@@ -0,0 +1,195 @@
+package main
+
+import (
+ "image"
+ "math"
+ "os"
+ "time"
+
+ _ "image/jpeg"
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+func loadPicture(path string) (pixel.Picture, error) {
+ file, err := os.Open(path)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+ img, _, err := image.Decode(file)
+ if err != nil {
+ return nil, err
+ }
+ return pixel.PictureDataFromImage(img), nil
+}
+
+type colorlight struct {
+ color pixel.RGBA
+ point pixel.Vec
+ angle float64
+ radius float64
+ dust float64
+
+ spread float64
+
+ imd *imdraw.IMDraw
+}
+
+func (cl *colorlight) apply(dst pixel.ComposeTarget, center pixel.Vec, src, noise *pixel.Sprite) {
+ // create the light arc if not created already
+ if cl.imd == nil {
+ imd := imdraw.New(nil)
+ imd.Color = pixel.Alpha(1)
+ imd.Push(pixel.ZV)
+ imd.Color = pixel.Alpha(0)
+ for angle := -cl.spread / 2; angle <= cl.spread/2; angle += cl.spread / 64 {
+ imd.Push(pixel.V(1, 0).Rotated(angle))
+ }
+ imd.Polygon(0)
+ cl.imd = imd
+ }
+
+ // draw the light arc
+ dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
+ dst.SetColorMask(pixel.Alpha(1))
+ dst.SetComposeMethod(pixel.ComposePlus)
+ cl.imd.Draw(dst)
+
+ // draw the noise inside the light
+ dst.SetMatrix(pixel.IM)
+ dst.SetComposeMethod(pixel.ComposeIn)
+ noise.Draw(dst, pixel.IM.Moved(center))
+
+ // draw an image inside the noisy light
+ dst.SetColorMask(cl.color)
+ dst.SetComposeMethod(pixel.ComposeIn)
+ src.Draw(dst, pixel.IM.Moved(center))
+
+ // draw the light reflected from the dust
+ dst.SetMatrix(pixel.IM.Scaled(pixel.ZV, cl.radius).Rotated(pixel.ZV, cl.angle).Moved(cl.point))
+ dst.SetColorMask(cl.color.Mul(pixel.Alpha(cl.dust)))
+ dst.SetComposeMethod(pixel.ComposePlus)
+ cl.imd.Draw(dst)
+}
+
+func run() {
+ pandaPic, err := loadPicture("panda.png")
+ if err != nil {
+ panic(err)
+ }
+ noisePic, err := loadPicture("noise.png")
+ if err != nil {
+ panic(err)
+ }
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Lights",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ panda := pixel.NewSprite(pandaPic, pandaPic.Bounds())
+ noise := pixel.NewSprite(noisePic, noisePic.Bounds())
+
+ colors := []pixel.RGBA{
+ pixel.RGB(1, 0, 0),
+ pixel.RGB(0, 1, 0),
+ pixel.RGB(0, 0, 1),
+ pixel.RGB(1/math.Sqrt2, 1/math.Sqrt2, 0),
+ }
+
+ points := []pixel.Vec{
+ {X: win.Bounds().Min.X, Y: win.Bounds().Min.Y},
+ {X: win.Bounds().Max.X, Y: win.Bounds().Min.Y},
+ {X: win.Bounds().Max.X, Y: win.Bounds().Max.Y},
+ {X: win.Bounds().Min.X, Y: win.Bounds().Max.Y},
+ }
+
+ angles := []float64{
+ math.Pi / 4,
+ math.Pi/4 + math.Pi/2,
+ math.Pi/4 + 2*math.Pi/2,
+ math.Pi/4 + 3*math.Pi/2,
+ }
+
+ lights := make([]colorlight, 4)
+ for i := range lights {
+ lights[i] = colorlight{
+ color: colors[i],
+ point: points[i],
+ angle: angles[i],
+ radius: 800,
+ dust: 0.3,
+ spread: math.Pi / math.E,
+ }
+ }
+
+ speed := []float64{11.0 / 23, 13.0 / 23, 17.0 / 23, 19.0 / 23}
+
+ oneLight := pixelgl.NewCanvas(win.Bounds())
+ allLight := pixelgl.NewCanvas(win.Bounds())
+
+ fps30 := time.Tick(time.Second / 30)
+
+ start := time.Now()
+ for !win.Closed() {
+ if win.Pressed(pixelgl.KeyW) {
+ for i := range lights {
+ lights[i].dust += 0.05
+ if lights[i].dust > 1 {
+ lights[i].dust = 1
+ }
+ }
+ }
+ if win.Pressed(pixelgl.KeyS) {
+ for i := range lights {
+ lights[i].dust -= 0.05
+ if lights[i].dust < 0 {
+ lights[i].dust = 0
+ }
+ }
+ }
+
+ since := time.Since(start).Seconds()
+ for i := range lights {
+ lights[i].angle = angles[i] + math.Sin(since*speed[i])*math.Pi/8
+ }
+
+ win.Clear(pixel.RGB(0, 0, 0))
+
+ // draw the panda visible outside the light
+ win.SetColorMask(pixel.Alpha(0.4))
+ win.SetComposeMethod(pixel.ComposePlus)
+ panda.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+
+ allLight.Clear(pixel.Alpha(0))
+ allLight.SetComposeMethod(pixel.ComposePlus)
+
+ // accumulate all the lights
+ for i := range lights {
+ oneLight.Clear(pixel.Alpha(0))
+ lights[i].apply(oneLight, oneLight.Bounds().Center(), panda, noise)
+ oneLight.Draw(allLight, pixel.IM.Moved(allLight.Bounds().Center()))
+ }
+
+ // compose the final result
+ win.SetColorMask(pixel.Alpha(1))
+ allLight.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+
+ win.Update()
+
+ <-fps30 // maintain 30 fps, because my computer couldn't handle 60 here
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/lights/noise.png b/examples/lights/noise.png
new file mode 100644
index 0000000..4e52c8c
Binary files /dev/null and b/examples/lights/noise.png differ
diff --git a/examples/lights/panda.png b/examples/lights/panda.png
new file mode 100644
index 0000000..9a987d3
Binary files /dev/null and b/examples/lights/panda.png differ
diff --git a/examples/lights/screenshot.png b/examples/lights/screenshot.png
new file mode 100644
index 0000000..4ffe84c
Binary files /dev/null and b/examples/lights/screenshot.png differ
diff --git a/examples/multiwindows/README.md b/examples/multiwindows/README.md
new file mode 100644
index 0000000..610a151
--- /dev/null
+++ b/examples/multiwindows/README.md
@@ -0,0 +1,3 @@
+# Multiple Windows
+
+This example demostrates how to use the convinient Window Manager to create multiple windows.
diff --git a/examples/multiwindows/main.go b/examples/multiwindows/main.go
new file mode 100644
index 0000000..cb2ec82
--- /dev/null
+++ b/examples/multiwindows/main.go
@@ -0,0 +1,113 @@
+package main
+
+import (
+ "fmt"
+
+ pixel "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "github.com/gopxl/pixel/text"
+)
+
+type EasyWindow1 struct {
+ win *pixelgl.Window
+ txt *text.Text
+ counter int
+}
+
+func (w *EasyWindow1) Setup() error {
+ w.txt = text.New(pixel.V(0, 0), text.Atlas7x13)
+
+ return nil
+}
+
+func (w *EasyWindow1) Win() *pixelgl.Window {
+ return w.win
+}
+
+func (w *EasyWindow1) Update() error {
+ w.counter++
+ return nil
+}
+
+func (w *EasyWindow1) Draw() error {
+ w.win.Clear(pixel.RGB(0, 0, 0))
+ w.txt.Clear()
+
+ fmt.Fprintf(w.txt, "Window 1\n")
+ fmt.Fprintf(w.txt, "Counter: %d\n", w.counter)
+ fmt.Fprintf(w.txt, "FPS: %.01f\n", wm.FPS())
+
+ w.txt.Draw(w.win, pixel.IM.Scaled(w.txt.Orig, 2))
+ return nil
+}
+
+type EasyWindow2 struct {
+ win *pixelgl.Window
+ txt *text.Text
+ counter uint64
+}
+
+func (w *EasyWindow2) Setup() error {
+ w.txt = text.New(pixel.V(0, 0), text.Atlas7x13)
+ w.counter = 0
+ return nil
+}
+
+func (w *EasyWindow2) Win() *pixelgl.Window {
+ return w.win
+}
+
+func (w *EasyWindow2) Update() error {
+ w.counter--
+ return nil
+}
+
+func (w *EasyWindow2) Draw() error {
+ w.win.Clear(pixel.RGB(0, 0, 0))
+ w.txt.Clear()
+
+ fmt.Fprintf(w.txt, "Window 2\n")
+ fmt.Fprintf(w.txt, "Counter: %d\n", w.counter)
+ fmt.Fprintf(w.txt, "FPS: %.01f\n", wm.FPS())
+
+ w.txt.Draw(w.win, pixel.IM.Scaled(w.txt.Orig, 2))
+ return nil
+}
+
+var wm *pixelgl.WindowManager = pixelgl.NewWindowManager()
+
+func run() {
+ w1, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Title: "Main Window",
+ Bounds: pixel.R(0, 0, 200, 200),
+ })
+
+ if err != nil {
+ panic(err)
+ }
+
+ w2, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Title: "Window 2",
+ Bounds: pixel.R(0, 0, 500, 200),
+ })
+
+ if err != nil {
+ panic(err)
+ }
+
+ wm.InsertWindows([]pixelgl.EasyWindow{
+ &EasyWindow1{win: w1},
+ &EasyWindow2{win: w2},
+ })
+
+ wm.SetFPS(60)
+
+ if err := wm.Loop(); err != nil {
+ panic(err)
+ }
+
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/platformer/README.md b/examples/platformer/README.md
new file mode 100644
index 0000000..48d4247
--- /dev/null
+++ b/examples/platformer/README.md
@@ -0,0 +1,14 @@
+# Platformer
+
+This example demostrates a way to put things together and create a simple platformer game with a
+Gopher!
+
+Use **arrow keys** to run and jump around. Press **ENTER** to restart. (And hush, hush, secret.
+Press TAB for slo-mo!)
+
+The retro feel is, other than from the pixel art spritesheet, achieved by using a 160x120px large
+off-screen canvas, drawing everything to it and then stretching it to fit the window.
+
+The Gopher spritesheet comes from excellent [Egon Elbre](https://github.com/egonelbre/gophers).
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/platformer/main.go b/examples/platformer/main.go
new file mode 100644
index 0000000..4bf2177
--- /dev/null
+++ b/examples/platformer/main.go
@@ -0,0 +1,394 @@
+package main
+
+import (
+ "encoding/csv"
+ "image"
+ "image/color"
+ "io"
+ "math"
+ "math/rand"
+ "os"
+ "strconv"
+ "time"
+
+ _ "image/png"
+
+ pixel "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+ "github.com/pkg/errors"
+ "golang.org/x/image/colornames"
+)
+
+func loadAnimationSheet(sheetPath, descPath string, frameWidth float64) (sheet pixel.Picture, anims map[string][]pixel.Rect, err error) {
+ // total hack, nicely format the error at the end, so I don't have to type it every time
+ defer func() {
+ if err != nil {
+ err = errors.Wrap(err, "error loading animation sheet")
+ }
+ }()
+
+ // open and load the spritesheet
+ sheetFile, err := os.Open(sheetPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer sheetFile.Close()
+ sheetImg, _, err := image.Decode(sheetFile)
+ if err != nil {
+ return nil, nil, err
+ }
+ sheet = pixel.PictureDataFromImage(sheetImg)
+
+ // create a slice of frames inside the spritesheet
+ var frames []pixel.Rect
+ for x := 0.0; x+frameWidth <= sheet.Bounds().Max.X; x += frameWidth {
+ frames = append(frames, pixel.R(
+ x,
+ 0,
+ x+frameWidth,
+ sheet.Bounds().H(),
+ ))
+ }
+
+ descFile, err := os.Open(descPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer descFile.Close()
+
+ anims = make(map[string][]pixel.Rect)
+
+ // load the animation information, name and interval inside the spritesheet
+ desc := csv.NewReader(descFile)
+ for {
+ anim, err := desc.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ name := anim[0]
+ start, _ := strconv.Atoi(anim[1])
+ end, _ := strconv.Atoi(anim[2])
+
+ anims[name] = frames[start : end+1]
+ }
+
+ return sheet, anims, nil
+}
+
+type platform struct {
+ rect pixel.Rect
+ color color.Color
+}
+
+func (p *platform) draw(imd *imdraw.IMDraw) {
+ imd.Color = p.color
+ imd.Push(p.rect.Min, p.rect.Max)
+ imd.Rectangle(0)
+}
+
+type gopherPhys struct {
+ gravity float64
+ runSpeed float64
+ jumpSpeed float64
+
+ rect pixel.Rect
+ vel pixel.Vec
+ ground bool
+}
+
+func (gp *gopherPhys) update(dt float64, ctrl pixel.Vec, platforms []platform) {
+ // apply controls
+ switch {
+ case ctrl.X < 0:
+ gp.vel.X = -gp.runSpeed
+ case ctrl.X > 0:
+ gp.vel.X = +gp.runSpeed
+ default:
+ gp.vel.X = 0
+ }
+
+ // apply gravity and velocity
+ gp.vel.Y += gp.gravity * dt
+ gp.rect = gp.rect.Moved(gp.vel.Scaled(dt))
+
+ // check collisions against each platform
+ gp.ground = false
+ if gp.vel.Y <= 0 {
+ for _, p := range platforms {
+ if gp.rect.Max.X <= p.rect.Min.X || gp.rect.Min.X >= p.rect.Max.X {
+ continue
+ }
+ if gp.rect.Min.Y > p.rect.Max.Y || gp.rect.Min.Y < p.rect.Max.Y+gp.vel.Y*dt {
+ continue
+ }
+ gp.vel.Y = 0
+ gp.rect = gp.rect.Moved(pixel.V(0, p.rect.Max.Y-gp.rect.Min.Y))
+ gp.ground = true
+ }
+ }
+
+ // jump if on the ground and the player wants to jump
+ if gp.ground && ctrl.Y > 0 {
+ gp.vel.Y = gp.jumpSpeed
+ }
+}
+
+type animState int
+
+const (
+ idle animState = iota
+ running
+ jumping
+)
+
+type gopherAnim struct {
+ sheet pixel.Picture
+ anims map[string][]pixel.Rect
+ rate float64
+
+ state animState
+ counter float64
+ dir float64
+
+ frame pixel.Rect
+
+ sprite *pixel.Sprite
+}
+
+func (ga *gopherAnim) update(dt float64, phys *gopherPhys) {
+ ga.counter += dt
+
+ // determine the new animation state
+ var newState animState
+ switch {
+ case !phys.ground:
+ newState = jumping
+ case phys.vel.Len() == 0:
+ newState = idle
+ case phys.vel.Len() > 0:
+ newState = running
+ }
+
+ // reset the time counter if the state changed
+ if ga.state != newState {
+ ga.state = newState
+ ga.counter = 0
+ }
+
+ // determine the correct animation frame
+ switch ga.state {
+ case idle:
+ ga.frame = ga.anims["Front"][0]
+ case running:
+ i := int(math.Floor(ga.counter / ga.rate))
+ ga.frame = ga.anims["Run"][i%len(ga.anims["Run"])]
+ case jumping:
+ speed := phys.vel.Y
+ i := int((-speed/phys.jumpSpeed + 1) / 2 * float64(len(ga.anims["Jump"])))
+ if i < 0 {
+ i = 0
+ }
+ if i >= len(ga.anims["Jump"]) {
+ i = len(ga.anims["Jump"]) - 1
+ }
+ ga.frame = ga.anims["Jump"][i]
+ }
+
+ // set the facing direction of the gopher
+ if phys.vel.X != 0 {
+ if phys.vel.X > 0 {
+ ga.dir = +1
+ } else {
+ ga.dir = -1
+ }
+ }
+}
+
+func (ga *gopherAnim) draw(t pixel.Target, phys *gopherPhys) {
+ if ga.sprite == nil {
+ ga.sprite = pixel.NewSprite(nil, pixel.Rect{})
+ }
+ // draw the correct frame with the correct position and direction
+ ga.sprite.Set(ga.sheet, ga.frame)
+ ga.sprite.Draw(t, pixel.IM.
+ ScaledXY(pixel.ZV, pixel.V(
+ phys.rect.W()/ga.sprite.Frame().W(),
+ phys.rect.H()/ga.sprite.Frame().H(),
+ )).
+ ScaledXY(pixel.ZV, pixel.V(-ga.dir, 1)).
+ Moved(phys.rect.Center()),
+ )
+}
+
+type goal struct {
+ pos pixel.Vec
+ radius float64
+ step float64
+
+ counter float64
+ cols [5]pixel.RGBA
+}
+
+func (g *goal) update(dt float64) {
+ g.counter += dt
+ for g.counter > g.step {
+ g.counter -= g.step
+ for i := len(g.cols) - 2; i >= 0; i-- {
+ g.cols[i+1] = g.cols[i]
+ }
+ g.cols[0] = randomNiceColor()
+ }
+}
+
+func (g *goal) draw(imd *imdraw.IMDraw) {
+ for i := len(g.cols) - 1; i >= 0; i-- {
+ imd.Color = g.cols[i]
+ imd.Push(g.pos)
+ imd.Circle(float64(i+1)*g.radius/float64(len(g.cols)), 0)
+ }
+}
+
+func randomNiceColor() pixel.RGBA {
+again:
+ r := rand.Float64()
+ g := rand.Float64()
+ b := rand.Float64()
+ len := math.Sqrt(r*r + g*g + b*b)
+ if len == 0 {
+ goto again
+ }
+ return pixel.RGB(r/len, g/len, b/len)
+}
+
+func run() {
+ rand.Seed(time.Now().UnixNano())
+
+ sheet, anims, err := loadAnimationSheet("sheet.png", "sheet.csv", 12)
+ if err != nil {
+ panic(err)
+ }
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Platformer",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ phys := &gopherPhys{
+ gravity: -512,
+ runSpeed: 64,
+ jumpSpeed: 192,
+ rect: pixel.R(-6, -7, 6, 7),
+ }
+
+ anim := &gopherAnim{
+ sheet: sheet,
+ anims: anims,
+ rate: 1.0 / 10,
+ dir: +1,
+ }
+
+ // hardcoded level
+ platforms := []platform{
+ {rect: pixel.R(-50, -34, 50, -32)},
+ {rect: pixel.R(20, 0, 70, 2)},
+ {rect: pixel.R(-100, 10, -50, 12)},
+ {rect: pixel.R(120, -22, 140, -20)},
+ {rect: pixel.R(120, -72, 140, -70)},
+ {rect: pixel.R(120, -122, 140, -120)},
+ {rect: pixel.R(-100, -152, 100, -150)},
+ {rect: pixel.R(-150, -127, -140, -125)},
+ {rect: pixel.R(-180, -97, -170, -95)},
+ {rect: pixel.R(-150, -67, -140, -65)},
+ {rect: pixel.R(-180, -37, -170, -35)},
+ {rect: pixel.R(-150, -7, -140, -5)},
+ }
+ for i := range platforms {
+ platforms[i].color = randomNiceColor()
+ }
+
+ gol := &goal{
+ pos: pixel.V(-75, 40),
+ radius: 18,
+ step: 1.0 / 7,
+ }
+
+ canvas := pixelgl.NewCanvas(pixel.R(-160/2, -120/2, 160/2, 120/2))
+ imd := imdraw.New(sheet)
+ imd.Precision = 32
+
+ camPos := pixel.ZV
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ // lerp the camera position towards the gopher
+ camPos = pixel.Lerp(camPos, phys.rect.Center(), 1-math.Pow(1.0/128, dt))
+ cam := pixel.IM.Moved(camPos.Scaled(-1))
+ canvas.SetMatrix(cam)
+
+ // slow motion with tab
+ if win.Pressed(pixelgl.KeyTab) {
+ dt /= 8
+ }
+
+ // restart the level on pressing enter
+ if win.JustPressed(pixelgl.KeyEnter) {
+ phys.rect = phys.rect.Moved(phys.rect.Center().Scaled(-1))
+ phys.vel = pixel.ZV
+ }
+
+ // control the gopher with keys
+ ctrl := pixel.ZV
+ if win.Pressed(pixelgl.KeyLeft) {
+ ctrl.X--
+ }
+ if win.Pressed(pixelgl.KeyRight) {
+ ctrl.X++
+ }
+ if win.JustPressed(pixelgl.KeyUp) {
+ ctrl.Y = 1
+ }
+
+ // update the physics and animation
+ phys.update(dt, ctrl, platforms)
+ gol.update(dt)
+ anim.update(dt, phys)
+
+ // draw the scene to the canvas using IMDraw
+ canvas.Clear(colornames.Black)
+ imd.Clear()
+ for _, p := range platforms {
+ p.draw(imd)
+ }
+ gol.draw(imd)
+ anim.draw(imd, phys)
+ imd.Draw(canvas)
+
+ // stretch the canvas to the window
+ win.Clear(colornames.White)
+ win.SetMatrix(pixel.IM.Scaled(pixel.ZV,
+ math.Min(
+ win.Bounds().W()/canvas.Bounds().W(),
+ win.Bounds().H()/canvas.Bounds().H(),
+ ),
+ ).Moved(win.Bounds().Center()))
+ canvas.Draw(win, pixel.IM.Moved(canvas.Bounds().Center()))
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/platformer/screenshot.png b/examples/platformer/screenshot.png
new file mode 100644
index 0000000..4b8b54b
Binary files /dev/null and b/examples/platformer/screenshot.png differ
diff --git a/examples/platformer/sheet.csv b/examples/platformer/sheet.csv
new file mode 100644
index 0000000..159846d
--- /dev/null
+++ b/examples/platformer/sheet.csv
@@ -0,0 +1,9 @@
+Front,0,0
+FrontBlink,1,1
+LookUp,2,2
+Left,3,7
+LeftRight,4,6
+LeftBlink,7,7
+Walk,8,15
+Run,16,23
+Jump,24,26
\ No newline at end of file
diff --git a/examples/platformer/sheet.png b/examples/platformer/sheet.png
new file mode 100644
index 0000000..8be1b97
Binary files /dev/null and b/examples/platformer/sheet.png differ
diff --git a/examples/shader/.gitignore b/examples/shader/.gitignore
new file mode 100644
index 0000000..d4b20d9
--- /dev/null
+++ b/examples/shader/.gitignore
@@ -0,0 +1,2 @@
+*.exe
+.vscode
\ No newline at end of file
diff --git a/examples/shader/LICENSE b/examples/shader/LICENSE
new file mode 100644
index 0000000..60cde56
--- /dev/null
+++ b/examples/shader/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2018 thegtproject
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/examples/shader/README.md b/examples/shader/README.md
new file mode 100644
index 0000000..6a4793a
--- /dev/null
+++ b/examples/shader/README.md
@@ -0,0 +1,20 @@
+# Examples for Pixel's new custom fragment shader support
+
+A few examples on how to implement custom shaders in Pixel. Please note I am not good with graphics, so hopefully someone more talented than I come up with better demos soon :)
+
+
+## fast radial blurring effect
+
+![](fastblur.gif)
+
+## long exposure-ish effect
+
+![](exposure.gif)
+
+## grayscale (from wiki tutorial)
+
+![](grayscale.png)
+
+## wavy (from wiki tutorial)
+
+![](wavy.gif)
diff --git a/examples/shader/assets/images/thegopherproject.png b/examples/shader/assets/images/thegopherproject.png
new file mode 100644
index 0000000..5ffee86
Binary files /dev/null and b/examples/shader/assets/images/thegopherproject.png differ
diff --git a/examples/shader/assets/shaders/exposure.frag.glsl b/examples/shader/assets/shaders/exposure.frag.glsl
new file mode 100644
index 0000000..4a7e02f
--- /dev/null
+++ b/examples/shader/assets/shaders/exposure.frag.glsl
@@ -0,0 +1,25 @@
+#version 330 core
+
+// Keep in mind, I have very little idea what I'm doing when it comes
+// to these shaders, so take what you see here with a grain of salt.
+
+out vec4 fragColor;
+
+// Pixel default uniforms
+uniform vec4 uTexBounds;
+uniform sampler2D uTexture;
+uniform sampler2D uBackBuffer;
+
+// Our custom uniforms
+uniform float uAmount;
+
+void main() {
+ // It is often very useful to normalize the fragment coordinate. Usually
+ // represented as "uv" we do so here:
+ vec2 uv = gl_FragCoord.xy / uTexBounds.zw;
+ fragColor = texture(uTexture, uv);
+
+ // uAmount is programmed to be adjustable with the left and right keys
+ // inside of Pixel
+ fragColor *= texture(uBackBuffer, uv).a * uAmount;
+}
diff --git a/examples/shader/assets/shaders/fastblur.frag.glsl b/examples/shader/assets/shaders/fastblur.frag.glsl
new file mode 100644
index 0000000..9dbaaf6
--- /dev/null
+++ b/examples/shader/assets/shaders/fastblur.frag.glsl
@@ -0,0 +1,95 @@
+#version 330 core
+
+// base shader code from https://www.shadertoy.com/view/XssSDs
+
+in vec2 vTexCoords;
+
+out vec4 fragColor;
+
+// Pixel default uniforms
+uniform vec4 uTexBounds;
+uniform sampler2D uTexture;
+
+// Our custom uniforms
+uniform float uTime;
+uniform vec4 uMouse;
+
+vec2 Circle(float Start, float Points, float Point)
+{
+ float Rad = (3.141592 * 2.0 * (1.0 / Points)) * (Point + Start);
+ return vec2(sin(Rad), cos(Rad));
+}
+
+void main()
+{
+ // It is often very useful to normalize the fragment coordinate. Usually
+ // represented as "uv" we do so here:
+ //
+ // Normalize the fragments's position, this is the location we use to sample
+ // our two textures/buffers. Note: Pixel passes resolution info through
+ // uTexBounds.zw (x, y)
+ vec2 uv = gl_FragCoord.xy / uTexBounds.zw;
+
+
+ vec2 PixelOffset = 1.0 / uTexBounds.zw;
+ float Start = 4.0 / 14.0;
+ vec2 Scale = 0.66 * 4.0 * 2.0 * PixelOffset.xy;
+
+ vec3 N0 = texture(uTexture, uv + Circle(Start, 14.0, 0.0) * Scale).rgb;
+ vec3 N1 = texture(uTexture, uv + Circle(Start, 14.0, 1.0) * Scale).rgb;
+ vec3 N2 = texture(uTexture, uv + Circle(Start, 14.0, 2.0) * Scale).rgb;
+ vec3 N3 = texture(uTexture, uv + Circle(Start, 14.0, 3.0) * Scale).rgb;
+ vec3 N4 = texture(uTexture, uv + Circle(Start, 14.0, 4.0) * Scale).rgb;
+ vec3 N5 = texture(uTexture, uv + Circle(Start, 14.0, 5.0) * Scale).rgb;
+ vec3 N6 = texture(uTexture, uv + Circle(Start, 14.0, 6.0) * Scale).rgb;
+ vec3 N7 = texture(uTexture, uv + Circle(Start, 14.0, 7.0) * Scale).rgb;
+ vec3 N8 = texture(uTexture, uv + Circle(Start, 14.0, 8.0) * Scale).rgb;
+ vec3 N9 = texture(uTexture, uv + Circle(Start, 14.0, 9.0) * Scale).rgb;
+ vec3 N10 = texture(uTexture, uv + Circle(Start, 14.0, 10.0) * Scale).rgb;
+ vec3 N11 = texture(uTexture, uv + Circle(Start, 14.0, 11.0) * Scale).rgb;
+ vec3 N12 = texture(uTexture, uv + Circle(Start, 14.0, 12.0) * Scale).rgb;
+ vec3 N13 = texture(uTexture, uv + Circle(Start, 14.0, 13.0) * Scale).rgb;
+ vec3 N14 = texture(uTexture, uv).rgb;
+
+ float W = 1.0 / 15.0;
+
+ vec3 color = vec3(0,0,0);
+
+ color.rgb =
+ (N0 * W) +
+ (N1 * W) +
+ (N2 * W) +
+ (N3 * W) +
+ (N4 * W) +
+ (N5 * W) +
+ (N6 * W) +
+ (N7 * W) +
+ (N8 * W) +
+ (N9 * W) +
+ (N10 * W) +
+ (N11 * W) +
+ (N12 * W) +
+ (N13 * W) +
+ (N14 * W);
+
+ // curTexColor is the value of the current fragment color
+ // from Pixel's (the library) input texture.
+ vec4 curTexColor = texture(uTexture, uv);
+
+ float xvalue = 0.0;
+
+ // Left mouse button is currently pressed
+ if (uMouse[2] == 1.0) xvalue = uMouse[0] / uTexBounds.z;
+
+ if(uv.x < xvalue)
+ {
+ color.rgb = curTexColor.rgb;
+ }
+
+ // Draw a black verticle line between our two halves
+ // to distinguish unblurred and blurred
+ if(abs(uv.x - xvalue) < 0.0015)
+ color = vec3(0.0);
+
+ fragColor = vec4(color.rgb, 1.0);
+}
diff --git a/examples/shader/exposure.gif b/examples/shader/exposure.gif
new file mode 100644
index 0000000..384d1c0
Binary files /dev/null and b/examples/shader/exposure.gif differ
diff --git a/examples/shader/exposure/main.go b/examples/shader/exposure/main.go
new file mode 100644
index 0000000..8492490
--- /dev/null
+++ b/examples/shader/exposure/main.go
@@ -0,0 +1,170 @@
+package main
+
+//
+// This example is my (thegtproject) own creation... I am not a designer- you've been warned.
+// This is an attempt at performing a sort of "long-exposure" post effect.
+// See ../assets/shaders/fastblur.frag.glsl for more details and comments
+//
+
+import (
+ "fmt"
+ "image/color"
+ "math"
+ "time"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+var (
+ g = 0.1
+ r1 = 180.0
+ r2 = 90.0
+ m1 = 32.0
+ m2 = 8.0
+ a1v = 0.0
+ a2v = 0.0
+ a1, a2 = a1a2DefaultValues()
+)
+
+func run() {
+ win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Bounds: pixel.R(0, 0, 600, 310),
+ VSync: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+ CenterWindow(win)
+ win.SetSmooth(true)
+ modelMatrix := pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)).Moved(pixel.V(300, 300))
+ viewMatrix := pixel.IM.Moved(win.Bounds().Center())
+
+ // I am putting all shader example initializing stuff here for
+ // easier reference to those learning to use this functionality
+ fragSource, err := LoadFileToString("../assets/shaders/exposure.frag.glsl")
+ if err != nil {
+ panic(err)
+ }
+
+ // Here we setup our uniforms. Think of uniforms as global variables
+ // we can use inside of our fragment shader source code.
+ var uTimeVar float32
+
+ // We'll change this variable around with the arrow keys
+ var uAmountVar float32 = 0.2
+
+ // We will update these uniforms often, so use pointer
+ EasyBindUniforms(win.Canvas(),
+ "uTime", &uTimeVar,
+ "uAmount", &uAmountVar,
+ )
+
+ // Since we are making a post effect, we want to apply the shader
+ // to the entire final render. We will use an intermediate canvas
+ // to complete the draw frame and then draw the canvas to the window's
+ // canvas for shader processing. Otherwise, our shader would only be
+ // running on active vertex positions, in this case, only the line
+ // and circle draws generated by IMDraw.
+ intermediatecanvas := pixelgl.NewCanvas(win.Bounds())
+ intermediatecanvas.SetMatrix(modelMatrix)
+
+ wc := win.Canvas()
+ wc.SetFragmentShader(fragSource)
+
+ sqrPos := win.Bounds().Moved(pixel.V(-300, -10))
+ start := time.Now()
+ for !win.Closed() {
+ // Update our uniform variables
+ uTimeVar = float32(time.Since(start).Seconds())
+
+ switch {
+ case win.Pressed(pixelgl.KeyLeft):
+ uAmountVar -= 0.001
+ win.SetTitle(fmt.Sprint(uAmountVar))
+ case win.Pressed(pixelgl.KeyRight):
+ uAmountVar += 0.001
+ win.SetTitle(fmt.Sprint(uAmountVar))
+ }
+
+ win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
+ if win.JustPressed(pixelgl.KeySpace) {
+ a1, a2 = a1a2DefaultValues()
+ }
+
+ a, b := update()
+
+ imd := imdraw.New(nil)
+
+ // Clearing the background color with a filled rectangle so that
+ // opengl will include the entire space in shader processing instead
+ // of just where the shape objects are drawn.
+ imd.Color = color.NRGBA{44, 44, 84, 255}
+ imd.Push(sqrPos.Min, sqrPos.Max)
+ imd.Rectangle(0)
+
+ imd.Color = color.NRGBA{64, 64, 122, 255}
+ imd.Push(pixel.ZV, a, b)
+ imd.Line(3)
+
+ imd.Color = color.NRGBA{51, 217, 178, 255}
+ imd.Push(a)
+ imd.Circle(m1/2, 0)
+
+ imd.Color = color.NRGBA{255, 0, 0, 255}
+ imd.Push(b)
+ imd.Circle(m2/2, 0)
+
+ imd.Draw(intermediatecanvas)
+ intermediatecanvas.Draw(win, viewMatrix)
+ win.Update()
+ }
+}
+
+func update() (pixel.Vec, pixel.Vec) {
+ a1a := a1aCalculation()
+ a2a := a2aCalculation()
+
+ a1v += a1a
+ a2v += a2a
+
+ a1 += a1v
+ a2 += a2v
+
+ a1v *= 0.9996
+ a2v *= 0.9996
+
+ a := pixel.V(r1*math.Sin(a1), r1*math.Cos(a1))
+ b := pixel.V(a.X+r2*math.Sin(a2), a.Y+r2*math.Cos(a2))
+
+ return a, b
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func a1a2DefaultValues() (float64, float64) {
+ return math.Pi / 2, math.Pi / 3
+}
+
+func a1aCalculation() float64 {
+ num1 := -g * (2*m1 + m2) * math.Sin(a1)
+ num2 := -m2 * g * math.Sin(a1-2*a2)
+ num3 := -2 * math.Sin(a1-a2) * m2
+ num4 := a2v*a2v*r2 + a1v*a1v*r1*math.Cos(a1-a2)
+ den := r1 * (2*m1 + m2 - m2*math.Cos(2*a1-2*a2))
+
+ return (num1 + num2 + num3*num4) / den
+}
+
+func a2aCalculation() float64 {
+ num1 := 2 * math.Sin(a1-a2)
+ num2 := (a1v * a1v * r1 * (m1 + m2))
+ num3 := g * (m1 + m2) * math.Cos(a1)
+ num4 := a2v * a2v * r2 * m2 * math.Cos(a1-a2)
+ den := r2 * (2*m1 + m2 - m2*math.Cos(2*a2-2*a2))
+
+ return (num1 * (num2 + num3 + num4)) / den
+}
diff --git a/examples/shader/exposure/psutil.go b/examples/shader/exposure/psutil.go
new file mode 100644
index 0000000..8fa82e4
--- /dev/null
+++ b/examples/shader/exposure/psutil.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "io/ioutil"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+// Pixel Shader utility functions
+
+// EasyBindUniforms does all the work for you, just pass in a
+// valid array adhering to format: String, Variable, ...
+//
+// example:
+//
+// var uTimeVar float32
+// var uMouseVar mgl32.Vec4
+//
+// EasyBindUniforms(win.GetCanvas(),
+// "uTime", &uTimeVar,
+// "uMouse", &uMouseVar,
+// )
+//
+func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) {
+ if len(unifs)%2 != 0 {
+ panic("needs to be divisable by 2")
+ }
+ for i := 0; i < len(unifs); i += 2 {
+
+ c.SetUniform(unifs[i+0].(string), unifs[i+1])
+ }
+}
+
+// CenterWindow will... center the window
+func CenterWindow(win *pixelgl.Window) {
+ x, y := pixelgl.PrimaryMonitor().Size()
+ width, height := win.Bounds().Size().XY()
+ win.SetPos(
+ pixel.V(
+ x/2-width/2,
+ y/2-height/2,
+ ),
+ )
+}
+
+// LoadFileToString loads the contents of a file into a string
+func LoadFileToString(filename string) (string, error) {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
diff --git a/examples/shader/fastblur.gif b/examples/shader/fastblur.gif
new file mode 100644
index 0000000..e3fa12a
Binary files /dev/null and b/examples/shader/fastblur.gif differ
diff --git a/examples/shader/fastblur/main.go b/examples/shader/fastblur/main.go
new file mode 100644
index 0000000..eee144a
--- /dev/null
+++ b/examples/shader/fastblur/main.go
@@ -0,0 +1,176 @@
+package main
+
+//
+// This example will show you how to port a shader you find from shadertoy.com
+// to Pixel. See ../assets/shaders/fastblur.frag.glsl for more details and comments
+//
+
+import (
+ "image/color"
+ "math"
+ "time"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+ "github.com/go-gl/mathgl/mgl32"
+)
+
+var (
+ g = 0.1
+ r1 = 180.0
+ r2 = 90.0
+ m1 = 32.0
+ m2 = 8.0
+ a1v = 0.0
+ a2v = 0.0
+ a1, a2 = a1a2DefaultValues()
+)
+
+func run() {
+ win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
+ Bounds: pixel.R(0, 0, 600, 310),
+ VSync: true,
+ })
+ if err != nil {
+ panic(err)
+ }
+ CenterWindow(win)
+ win.SetSmooth(true)
+ modelMatrix := pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)).Moved(pixel.V(300, 300))
+ viewMatrix := pixel.IM.Moved(win.Bounds().Center())
+
+ // I am putting all shader example initializing stuff here for
+ // easier reference to those learning to use this functionality
+ fragSource, err := LoadFileToString("../assets/shaders/fastblur.frag.glsl")
+ if err != nil {
+ panic(err)
+ }
+
+ // Here we setup our uniforms. Think of uniforms as global variables
+ // we can use inside of our fragment shader source code.
+ var uTimeVar float32
+
+ // It is common to provide a vec4 for a "mouse" uniform where
+ // uMouse[0] = X, uMouse[1] = Y, uMouse[2] = left mouse button,
+ // and uMouse[3] = right mouse button.
+ var uMouseVar mgl32.Vec4
+
+ // We will update these uniforms often, so use pointer
+ EasyBindUniforms(win.Canvas(),
+ "uTime", &uTimeVar,
+ "uMouse", &uMouseVar,
+ )
+
+ // Since we are making a post effect, we want to apply the shader
+ // to the entire final render. We will use an intermediate canvas
+ // to complete the draw frame and then draw the canvas to the window's
+ // canvas for shader processing. Otherwise, our shader would only be
+ // running on active vertex positions, in this case, only the line
+ // and circle draws generated by IMDraw.
+ intermediatecanvas := pixelgl.NewCanvas(win.Bounds())
+ intermediatecanvas.SetMatrix(modelMatrix)
+
+ wc := win.Canvas()
+ wc.SetFragmentShader(fragSource)
+
+ sqrPos := win.Bounds().Moved(pixel.V(-300, -10))
+ start := time.Now()
+ for !win.Closed() {
+ // Update our uniform variables
+ uTimeVar = float32(time.Since(start).Seconds())
+
+ uMouseVar[0] = float32(win.MousePosition().X)
+ uMouseVar[1] = float32(win.MousePosition().Y)
+
+ if win.Pressed(pixelgl.MouseButton1) {
+ uMouseVar[2] = 1.0
+ } else {
+ uMouseVar[2] = 0.0
+ }
+ if win.Pressed(pixelgl.MouseButton2) {
+ uMouseVar[3] = 1.0
+ } else {
+ uMouseVar[3] = 0.0
+ }
+
+ win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
+ if win.JustPressed(pixelgl.KeySpace) {
+ a1, a2 = a1a2DefaultValues()
+ }
+
+ a, b := update()
+
+ imd := imdraw.New(nil)
+
+ // Clearing the background color with a filled rectangle so that
+ // opengl will include the entire space in shader processing instead
+ // of just where the shape objects are drawn.
+ imd.Color = color.NRGBA{44, 44, 84, 255}
+ imd.Push(sqrPos.Min, sqrPos.Max)
+ imd.Rectangle(0)
+
+ imd.Color = color.NRGBA{64, 64, 122, 255}
+ imd.Push(pixel.ZV, a, b)
+ imd.Line(3)
+
+ imd.Color = color.NRGBA{51, 217, 178, 255}
+ imd.Push(a)
+ imd.Circle(m1/2, 0)
+
+ imd.Color = color.NRGBA{52, 172, 224, 255}
+ imd.Push(b)
+ imd.Circle(m2/2, 0)
+
+ imd.Draw(intermediatecanvas)
+ intermediatecanvas.Draw(win, viewMatrix)
+ win.Update()
+ }
+}
+
+func update() (pixel.Vec, pixel.Vec) {
+ a1a := a1aCalculation()
+ a2a := a2aCalculation()
+
+ a1v += a1a
+ a2v += a2a
+
+ a1 += a1v
+ a2 += a2v
+
+ a1v *= 0.9996
+ a2v *= 0.9996
+
+ a := pixel.V(r1*math.Sin(a1), r1*math.Cos(a1))
+ b := pixel.V(a.X+r2*math.Sin(a2), a.Y+r2*math.Cos(a2))
+
+ return a, b
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+func a1a2DefaultValues() (float64, float64) {
+ return math.Pi / 2, math.Pi / 3
+}
+
+func a1aCalculation() float64 {
+ num1 := -g * (2*m1 + m2) * math.Sin(a1)
+ num2 := -m2 * g * math.Sin(a1-2*a2)
+ num3 := -2 * math.Sin(a1-a2) * m2
+ num4 := a2v*a2v*r2 + a1v*a1v*r1*math.Cos(a1-a2)
+ den := r1 * (2*m1 + m2 - m2*math.Cos(2*a1-2*a2))
+
+ return (num1 + num2 + num3*num4) / den
+}
+
+func a2aCalculation() float64 {
+ num1 := 2 * math.Sin(a1-a2)
+ num2 := (a1v * a1v * r1 * (m1 + m2))
+ num3 := g * (m1 + m2) * math.Cos(a1)
+ num4 := a2v * a2v * r2 * m2 * math.Cos(a1-a2)
+ den := r2 * (2*m1 + m2 - m2*math.Cos(2*a2-2*a2))
+
+ return (num1 * (num2 + num3 + num4)) / den
+}
diff --git a/examples/shader/fastblur/psutil.go b/examples/shader/fastblur/psutil.go
new file mode 100644
index 0000000..8fa82e4
--- /dev/null
+++ b/examples/shader/fastblur/psutil.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "io/ioutil"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+// Pixel Shader utility functions
+
+// EasyBindUniforms does all the work for you, just pass in a
+// valid array adhering to format: String, Variable, ...
+//
+// example:
+//
+// var uTimeVar float32
+// var uMouseVar mgl32.Vec4
+//
+// EasyBindUniforms(win.GetCanvas(),
+// "uTime", &uTimeVar,
+// "uMouse", &uMouseVar,
+// )
+//
+func EasyBindUniforms(c *pixelgl.Canvas, unifs ...interface{}) {
+ if len(unifs)%2 != 0 {
+ panic("needs to be divisable by 2")
+ }
+ for i := 0; i < len(unifs); i += 2 {
+
+ c.SetUniform(unifs[i+0].(string), unifs[i+1])
+ }
+}
+
+// CenterWindow will... center the window
+func CenterWindow(win *pixelgl.Window) {
+ x, y := pixelgl.PrimaryMonitor().Size()
+ width, height := win.Bounds().Size().XY()
+ win.SetPos(
+ pixel.V(
+ x/2-width/2,
+ y/2-height/2,
+ ),
+ )
+}
+
+// LoadFileToString loads the contents of a file into a string
+func LoadFileToString(filename string) (string, error) {
+ b, err := ioutil.ReadFile(filename)
+ if err != nil {
+ return "", err
+ }
+ return string(b), nil
+}
diff --git a/examples/shader/grayscale.png b/examples/shader/grayscale.png
new file mode 100644
index 0000000..23b6528
Binary files /dev/null and b/examples/shader/grayscale.png differ
diff --git a/examples/shader/grayscale/main.go b/examples/shader/grayscale/main.go
new file mode 100644
index 0000000..fd057d9
--- /dev/null
+++ b/examples/shader/grayscale/main.go
@@ -0,0 +1,74 @@
+package main
+
+import (
+ "image/png"
+ "os"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+var gopherimg *pixel.Sprite
+
+func gameloop(win *pixelgl.Window) {
+ win.Canvas().SetFragmentShader(fragmentShader)
+
+ for !win.Closed() {
+ win.Clear(pixel.RGB(0, 0, 0))
+ gopherimg.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+ win.Update()
+ }
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 325, 348),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ f, err := os.Open("../assets/images/thegopherproject.png")
+ if err != nil {
+ panic(err)
+ }
+ img, err := png.Decode(f)
+ if err != nil {
+ panic(err)
+ }
+ pd := pixel.PictureDataFromImage(img)
+ gopherimg = pixel.NewSprite(pd, pd.Bounds())
+
+ gameloop(win)
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+var fragmentShader = `
+#version 330 core
+
+in vec2 vTexCoords;
+
+out vec4 fragColor;
+
+uniform vec4 uTexBounds;
+uniform sampler2D uTexture;
+
+void main() {
+ // Get our current screen coordinate
+ vec2 t = (vTexCoords - uTexBounds.xy) / uTexBounds.zw;
+
+ // Sum our 3 color channels
+ float sum = texture(uTexture, t).r;
+ sum += texture(uTexture, t).g;
+ sum += texture(uTexture, t).b;
+
+ // Divide by 3, and set the output to the result
+ vec4 color = vec4( sum/3, sum/3, sum/3, 1.0);
+ fragColor = color;
+}
+`
diff --git a/examples/shader/wavy.gif b/examples/shader/wavy.gif
new file mode 100644
index 0000000..134ddfd
Binary files /dev/null and b/examples/shader/wavy.gif differ
diff --git a/examples/shader/wavy/main.go b/examples/shader/wavy/main.go
new file mode 100644
index 0000000..d22b073
--- /dev/null
+++ b/examples/shader/wavy/main.go
@@ -0,0 +1,92 @@
+package main
+
+import (
+ "image/png"
+ "os"
+ "time"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+)
+
+var gopherimg *pixel.Sprite
+var imd *imdraw.IMDraw
+
+var uTime, uSpeed float32
+
+func gameloop(win *pixelgl.Window) {
+ win.Canvas().SetUniform("uTime", &uTime)
+ win.Canvas().SetUniform("uSpeed", &uSpeed)
+ uSpeed = 5.0
+ win.Canvas().SetFragmentShader(fragmentShader)
+
+ start := time.Now()
+ for !win.Closed() {
+ win.Clear(pixel.RGB(0, 0, 0))
+ gopherimg.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+ uTime = float32(time.Since(start).Seconds())
+ if win.Pressed(pixelgl.KeyRight) {
+ uSpeed += 0.1
+ }
+ if win.Pressed(pixelgl.KeyLeft) {
+ uSpeed -= 0.1
+ }
+ win.Update()
+ }
+}
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Pixel Rocks!",
+ Bounds: pixel.R(0, 0, 325, 348),
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ f, err := os.Open("../assets/images/thegopherproject.png")
+ if err != nil {
+ panic(err)
+ }
+ img, err := png.Decode(f)
+ if err != nil {
+ panic(err)
+ }
+ pd := pixel.PictureDataFromImage(img)
+ gopherimg = pixel.NewSprite(pd, pd.Bounds())
+
+ gameloop(win)
+}
+
+func main() {
+ pixelgl.Run(run)
+}
+
+var fragmentShader = `
+#version 330 core
+
+in vec2 vTexCoords;
+out vec4 fragColor;
+
+uniform sampler2D uTexture;
+uniform vec4 uTexBounds;
+
+// custom uniforms
+uniform float uSpeed;
+uniform float uTime;
+
+void main() {
+ vec2 t = vTexCoords / uTexBounds.zw;
+ vec3 influence = texture(uTexture, t).rgb;
+
+ if (influence.r + influence.g + influence.b > 0.3) {
+ t.y += cos(t.x * 40.0 + (uTime * uSpeed))*0.005;
+ t.x += cos(t.y * 40.0 + (uTime * uSpeed))*0.01;
+ }
+
+ vec3 col = texture(uTexture, t).rgb;
+ fragColor = vec4(col * vec3(0.6, 0.6, 1.2),1.0);
+}
+`
diff --git a/examples/smoke/README.md b/examples/smoke/README.md
new file mode 100644
index 0000000..ccaf2e0
--- /dev/null
+++ b/examples/smoke/README.md
@@ -0,0 +1,8 @@
+# Smoke
+
+This example implements a smoke particle effect using sprites. It uses a spritesheet with a CSV
+description.
+
+The art in the spritesheet comes from [Kenney](https://kenney.nl/).
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/smoke/blackSmoke.csv b/examples/smoke/blackSmoke.csv
new file mode 100644
index 0000000..7870d7b
--- /dev/null
+++ b/examples/smoke/blackSmoke.csv
@@ -0,0 +1,25 @@
+1543,1146,362,336
+396,0,398,364
+761,1535,386,342
+795,794,351,367
+394,1163,386,364
+1120,1163,377,348
+795,0,368,407
+0,0,395,397
+1164,0,378,415
+781,1163,338,360
+1543,0,372,370
+1148,1535,393,327
+387,1535,373,364
+396,365,371,388
+0,758,378,404
+379,758,378,371
+1543,774,360,371
+1543,1483,350,398
+0,398,382,359
+1164,416,356,382
+1164,799,369,350
+0,1535,386,394
+795,408,366,385
+1543,371,367,402
+0,1163,393,371
\ No newline at end of file
diff --git a/examples/smoke/blackSmoke.png b/examples/smoke/blackSmoke.png
new file mode 100644
index 0000000..c5ebbb8
Binary files /dev/null and b/examples/smoke/blackSmoke.png differ
diff --git a/examples/smoke/main.go b/examples/smoke/main.go
new file mode 100644
index 0000000..9296a40
--- /dev/null
+++ b/examples/smoke/main.go
@@ -0,0 +1,230 @@
+package main
+
+import (
+ "container/list"
+ "encoding/csv"
+ "image"
+ "io"
+ "math"
+ "math/rand"
+ "os"
+ "strconv"
+ "time"
+
+ _ "image/png"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+type particle struct {
+ Sprite *pixel.Sprite
+ Pos pixel.Vec
+ Rot, Scale float64
+ Mask pixel.RGBA
+ Data interface{}
+}
+
+type particles struct {
+ Generate func() *particle
+ Update func(dt float64, p *particle) bool
+ SpawnAvg, SpawnDist float64
+
+ parts list.List
+ spawnTime float64
+}
+
+func (p *particles) UpdateAll(dt float64) {
+ p.spawnTime -= dt
+ for p.spawnTime <= 0 {
+ p.parts.PushFront(p.Generate())
+ p.spawnTime += math.Max(0, p.SpawnAvg+rand.NormFloat64()*p.SpawnDist)
+ }
+
+ for e := p.parts.Front(); e != nil; e = e.Next() {
+ part := e.Value.(*particle)
+ if !p.Update(dt, part) {
+ defer p.parts.Remove(e)
+ }
+ }
+}
+
+func (p *particles) DrawAll(t pixel.Target) {
+ for e := p.parts.Front(); e != nil; e = e.Next() {
+ part := e.Value.(*particle)
+
+ part.Sprite.DrawColorMask(
+ t,
+ pixel.IM.
+ Scaled(pixel.ZV, part.Scale).
+ Rotated(pixel.ZV, part.Rot).
+ Moved(part.Pos),
+ part.Mask,
+ )
+ }
+}
+
+type smokeData struct {
+ Vel pixel.Vec
+ Time float64
+ Life float64
+}
+
+type smokeSystem struct {
+ Sheet pixel.Picture
+ Rects []pixel.Rect
+ Orig pixel.Vec
+
+ VelBasis []pixel.Vec
+ VelDist float64
+
+ LifeAvg, LifeDist float64
+}
+
+func (ss *smokeSystem) Generate() *particle {
+ sd := new(smokeData)
+ for _, base := range ss.VelBasis {
+ c := math.Max(0, 1+rand.NormFloat64()*ss.VelDist)
+ sd.Vel = sd.Vel.Add(base.Scaled(c))
+ }
+ sd.Vel = sd.Vel.Scaled(1 / float64(len(ss.VelBasis)))
+ sd.Life = math.Max(0, ss.LifeAvg+rand.NormFloat64()*ss.LifeDist)
+
+ p := new(particle)
+ p.Data = sd
+
+ p.Pos = ss.Orig
+ p.Scale = 1
+ p.Mask = pixel.Alpha(1)
+ p.Sprite = pixel.NewSprite(ss.Sheet, ss.Rects[rand.Intn(len(ss.Rects))])
+
+ return p
+}
+
+func (ss *smokeSystem) Update(dt float64, p *particle) bool {
+ sd := p.Data.(*smokeData)
+ sd.Time += dt
+
+ frac := sd.Time / sd.Life
+
+ p.Pos = p.Pos.Add(sd.Vel.Scaled(dt))
+ p.Scale = 0.5 + frac*1.5
+
+ const (
+ fadeIn = 0.2
+ fadeOut = 0.4
+ )
+ if frac < fadeIn {
+ p.Mask = pixel.Alpha(math.Pow(frac/fadeIn, 0.75))
+ } else if frac >= fadeOut {
+ p.Mask = pixel.Alpha(math.Pow(1-(frac-fadeOut)/(1-fadeOut), 1.5))
+ } else {
+ p.Mask = pixel.Alpha(1)
+ }
+
+ return sd.Time < sd.Life
+}
+
+func loadSpriteSheet(sheetPath, descriptionPath string) (sheet pixel.Picture, rects []pixel.Rect, err error) {
+ sheetFile, err := os.Open(sheetPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer sheetFile.Close()
+
+ sheetImg, _, err := image.Decode(sheetFile)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ sheet = pixel.PictureDataFromImage(sheetImg)
+
+ descriptionFile, err := os.Open(descriptionPath)
+ if err != nil {
+ return nil, nil, err
+ }
+ defer descriptionFile.Close()
+
+ description := csv.NewReader(descriptionFile)
+ for {
+ record, err := description.Read()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ return nil, nil, err
+ }
+
+ x, _ := strconv.ParseFloat(record[0], 64)
+ y, _ := strconv.ParseFloat(record[1], 64)
+ w, _ := strconv.ParseFloat(record[2], 64)
+ h, _ := strconv.ParseFloat(record[3], 64)
+
+ y = sheet.Bounds().H() - y - h
+
+ rects = append(rects, pixel.R(x, y, x+w, y+h))
+ }
+
+ return sheet, rects, nil
+}
+
+func run() {
+ sheet, rects, err := loadSpriteSheet("blackSmoke.png", "blackSmoke.csv")
+ if err != nil {
+ panic(err)
+ }
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Smoke",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ Resizable: true,
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ ss := &smokeSystem{
+ Rects: rects,
+ Orig: pixel.ZV,
+ VelBasis: []pixel.Vec{pixel.V(-100, 100), pixel.V(100, 100), pixel.V(0, 100)},
+ VelDist: 0.1,
+ LifeAvg: 7,
+ LifeDist: 0.5,
+ }
+
+ p := &particles{
+ Generate: ss.Generate,
+ Update: ss.Update,
+ SpawnAvg: 0.3,
+ SpawnDist: 0.1,
+ }
+
+ batch := pixel.NewBatch(&pixel.TrianglesData{}, sheet)
+
+ last := time.Now()
+ for !win.Closed() {
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ p.UpdateAll(dt)
+
+ win.Clear(colornames.Aliceblue)
+
+ orig := win.Bounds().Center()
+ orig.Y -= win.Bounds().H() / 2
+ win.SetMatrix(pixel.IM.Moved(orig))
+
+ batch.Clear()
+ p.DrawAll(batch)
+ batch.Draw(win)
+
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/smoke/screenshot.png b/examples/smoke/screenshot.png
new file mode 100644
index 0000000..eca3f1f
Binary files /dev/null and b/examples/smoke/screenshot.png differ
diff --git a/examples/typewriter/README.md b/examples/typewriter/README.md
new file mode 100644
index 0000000..3eaebfc
--- /dev/null
+++ b/examples/typewriter/README.md
@@ -0,0 +1,13 @@
+# Typewriter
+
+This example demonstrates text drawing and text input facilities by implementing a fancy typewriter
+with a red laser cursor. Screen shakes a bit when typing and some letters turn bold or italic
+randomly.
+
+ASCII and Latin characters are supported here. Feel free to add support for more characters in the
+code (it's easy, but increases load time).
+
+The seemingly buggy letters (one over another) in the screenshot are not bugs, but a result of using
+the typewriter backspace functionality.
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/typewriter/main.go b/examples/typewriter/main.go
new file mode 100644
index 0000000..ce32bdc
--- /dev/null
+++ b/examples/typewriter/main.go
@@ -0,0 +1,317 @@
+package main
+
+import (
+ "image/color"
+ "math"
+ "math/rand"
+ "sync"
+ "time"
+ "unicode"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+ "github.com/gopxl/pixel/text"
+ "github.com/golang/freetype/truetype"
+ "golang.org/x/image/colornames"
+ "golang.org/x/image/font"
+ "golang.org/x/image/font/gofont/gobold"
+ "golang.org/x/image/font/gofont/goitalic"
+ "golang.org/x/image/font/gofont/goregular"
+)
+
+func ttfFromBytesMust(b []byte, size float64) font.Face {
+ ttf, err := truetype.Parse(b)
+ if err != nil {
+ panic(err)
+ }
+ return truetype.NewFace(ttf, &truetype.Options{
+ Size: size,
+ GlyphCacheEntries: 1,
+ })
+}
+
+type typewriter struct {
+ mu sync.Mutex
+
+ regular *text.Text
+ bold *text.Text
+ italic *text.Text
+
+ offset pixel.Vec
+ position pixel.Vec
+ move pixel.Vec
+}
+
+func newTypewriter(c color.Color, regular, bold, italic *text.Atlas) *typewriter {
+ tw := &typewriter{
+ regular: text.New(pixel.ZV, regular),
+ bold: text.New(pixel.ZV, bold),
+ italic: text.New(pixel.ZV, italic),
+ }
+ tw.regular.Color = c
+ tw.bold.Color = c
+ tw.italic.Color = c
+ return tw
+}
+
+func (tw *typewriter) Ribbon(r rune) {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+
+ dice := rand.Intn(21)
+ switch {
+ case 0 <= dice && dice <= 18:
+ tw.regular.WriteRune(r)
+ case dice == 19:
+ tw.bold.Dot = tw.regular.Dot
+ tw.bold.WriteRune(r)
+ tw.regular.Dot = tw.bold.Dot
+ case dice == 20:
+ tw.italic.Dot = tw.regular.Dot
+ tw.italic.WriteRune(r)
+ tw.regular.Dot = tw.italic.Dot
+ }
+}
+
+func (tw *typewriter) Back() {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ tw.regular.Dot = tw.regular.Dot.Sub(pixel.V(tw.regular.Atlas().Glyph(' ').Advance, 0))
+}
+
+func (tw *typewriter) Offset(off pixel.Vec) {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ tw.offset = tw.offset.Add(off)
+}
+
+func (tw *typewriter) Position() pixel.Vec {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ return tw.position
+}
+
+func (tw *typewriter) Move(vel pixel.Vec) {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ tw.move = vel
+}
+
+func (tw *typewriter) Dot() pixel.Vec {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ return tw.regular.Dot
+}
+
+func (tw *typewriter) Update(dt float64) {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+ tw.position = tw.position.Add(tw.move.Scaled(dt))
+}
+
+func (tw *typewriter) Draw(t pixel.Target, m pixel.Matrix) {
+ tw.mu.Lock()
+ defer tw.mu.Unlock()
+
+ m = pixel.IM.Moved(tw.position.Add(tw.offset)).Chained(m)
+ tw.regular.Draw(t, m)
+ tw.bold.Draw(t, m)
+ tw.italic.Draw(t, m)
+}
+
+func typeRune(tw *typewriter, r rune) {
+ tw.Ribbon(r)
+ if !unicode.IsSpace(r) {
+ go shake(tw, 3, 17)
+ }
+}
+
+func back(tw *typewriter) {
+ tw.Back()
+}
+
+func shake(tw *typewriter, intensity, friction float64) {
+ const (
+ freq = 24
+ dt = 1.0 / freq
+ )
+ ticker := time.NewTicker(time.Second / freq)
+ defer ticker.Stop()
+
+ off := pixel.ZV
+
+ for range ticker.C {
+ tw.Offset(off.Scaled(-1))
+
+ if intensity < 0.01*dt {
+ break
+ }
+
+ off = pixel.V((rand.Float64()-0.5)*intensity*2, (rand.Float64()-0.5)*intensity*2)
+ intensity -= friction * dt
+
+ tw.Offset(off)
+ }
+}
+
+func scroll(tw *typewriter, intensity, speedUp float64) {
+ const (
+ freq = 120
+ dt = 1.0 / freq
+ )
+ ticker := time.NewTicker(time.Second / freq)
+ defer ticker.Stop()
+
+ speed := 0.0
+
+ for range ticker.C {
+ if math.Abs(tw.Dot().Y+tw.Position().Y) < 0.01 {
+ break
+ }
+
+ targetSpeed := -(tw.Dot().Y + tw.Position().Y) * intensity
+ if speed < targetSpeed {
+ speed += speedUp * dt
+ } else {
+ speed = targetSpeed
+ }
+
+ tw.Move(pixel.V(0, speed))
+ }
+}
+
+type dotlight struct {
+ tw *typewriter
+ color color.Color
+ radius float64
+ intensity float64
+ acceleration float64
+ maxSpeed float64
+
+ pos pixel.Vec
+ vel pixel.Vec
+
+ imd *imdraw.IMDraw
+}
+
+func newDotlight(tw *typewriter, c color.Color, radius, intensity, acceleration, maxSpeed float64) *dotlight {
+ return &dotlight{
+ tw: tw,
+ color: c,
+ radius: radius,
+ intensity: intensity,
+ acceleration: acceleration,
+ maxSpeed: maxSpeed,
+ pos: tw.Dot(),
+ vel: pixel.ZV,
+ imd: imdraw.New(nil),
+ }
+}
+
+func (dl *dotlight) Update(dt float64) {
+ targetVel := dl.tw.Dot().Add(dl.tw.Position()).Sub(dl.pos).Scaled(dl.intensity)
+ acc := targetVel.Sub(dl.vel).Scaled(dl.acceleration)
+ dl.vel = dl.vel.Add(acc.Scaled(dt))
+ if dl.vel.Len() > dl.maxSpeed {
+ dl.vel = dl.vel.Unit().Scaled(dl.maxSpeed)
+ }
+ dl.pos = dl.pos.Add(dl.vel.Scaled(dt))
+}
+
+func (dl *dotlight) Draw(t pixel.Target, m pixel.Matrix) {
+ dl.imd.Clear()
+ dl.imd.SetMatrix(m)
+ dl.imd.Color = dl.color
+ dl.imd.Push(dl.pos)
+ dl.imd.Color = pixel.Alpha(0)
+ for i := 0.0; i <= 32; i++ {
+ angle := i * 2 * math.Pi / 32
+ dl.imd.Push(dl.pos.Add(pixel.V(dl.radius, 0).Rotated(angle)))
+ }
+ dl.imd.Polygon(0)
+ dl.imd.Draw(t)
+}
+
+func run() {
+ rand.Seed(time.Now().UnixNano())
+
+ cfg := pixelgl.WindowConfig{
+ Title: "Typewriter",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ Resizable: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+ win.SetSmooth(true)
+
+ var (
+ regular = text.NewAtlas(
+ ttfFromBytesMust(goregular.TTF, 42),
+ text.ASCII, text.RangeTable(unicode.Latin),
+ )
+ bold = text.NewAtlas(
+ ttfFromBytesMust(gobold.TTF, 42),
+ text.ASCII, text.RangeTable(unicode.Latin),
+ )
+ italic = text.NewAtlas(
+ ttfFromBytesMust(goitalic.TTF, 42),
+ text.ASCII, text.RangeTable(unicode.Latin),
+ )
+
+ bgColor = color.RGBA{
+ R: 241,
+ G: 241,
+ B: 212,
+ A: 255,
+ }
+ fgColor = color.RGBA{
+ R: 0,
+ G: 15,
+ B: 85,
+ A: 255,
+ }
+
+ tw = newTypewriter(pixel.ToRGBA(fgColor).Scaled(0.9), regular, bold, italic)
+ dl = newDotlight(tw, colornames.Red, 6, 30, 20, 1600)
+ )
+
+ fps := time.Tick(time.Second / 120)
+ last := time.Now()
+ for !win.Closed() {
+ for _, r := range win.Typed() {
+ go typeRune(tw, r)
+ }
+ if win.JustPressed(pixelgl.KeyTab) || win.Repeated(pixelgl.KeyTab) {
+ go typeRune(tw, '\t')
+ }
+ if win.JustPressed(pixelgl.KeyEnter) || win.Repeated(pixelgl.KeyEnter) {
+ go typeRune(tw, '\n')
+ go scroll(tw, 20, 6400)
+ }
+ if win.JustPressed(pixelgl.KeyBackspace) || win.Repeated(pixelgl.KeyBackspace) {
+ go back(tw)
+ }
+
+ dt := time.Since(last).Seconds()
+ last = time.Now()
+
+ tw.Update(dt)
+ dl.Update(dt)
+
+ win.Clear(bgColor)
+
+ m := pixel.IM.Moved(pixel.V(32, 32))
+ tw.Draw(win, m)
+ dl.Draw(win, m)
+
+ win.Update()
+ <-fps
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/typewriter/screenshot.png b/examples/typewriter/screenshot.png
new file mode 100644
index 0000000..d353e43
Binary files /dev/null and b/examples/typewriter/screenshot.png differ
diff --git a/examples/xor/README.md b/examples/xor/README.md
new file mode 100644
index 0000000..54acd99
--- /dev/null
+++ b/examples/xor/README.md
@@ -0,0 +1,8 @@
+# Xor
+
+This example demonstrates an unusual Porter-Duff composition method: Xor. (And the capability of
+drawing circles.)
+
+Just thought it was cool.
+
+![Screenshot](screenshot.png)
\ No newline at end of file
diff --git a/examples/xor/main.go b/examples/xor/main.go
new file mode 100644
index 0000000..2a1591d
--- /dev/null
+++ b/examples/xor/main.go
@@ -0,0 +1,76 @@
+package main
+
+import (
+ "math"
+ "time"
+
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
+ "github.com/gopxl/pixel/pixelgl"
+ "golang.org/x/image/colornames"
+)
+
+func run() {
+ cfg := pixelgl.WindowConfig{
+ Title: "Xor",
+ Bounds: pixel.R(0, 0, 1024, 768),
+ Resizable: true,
+ VSync: true,
+ }
+ win, err := pixelgl.NewWindow(cfg)
+ if err != nil {
+ panic(err)
+ }
+
+ imd := imdraw.New(nil)
+
+ canvas := pixelgl.NewCanvas(win.Bounds())
+
+ start := time.Now()
+ for !win.Closed() {
+ // in case window got resized, we also need to resize our canvas
+ canvas.SetBounds(win.Bounds())
+
+ offset := math.Sin(time.Since(start).Seconds()) * 300
+
+ // clear the canvas to be totally transparent and set the xor compose method
+ canvas.Clear(pixel.Alpha(0))
+ canvas.SetComposeMethod(pixel.ComposeXor)
+
+ // red circle
+ imd.Clear()
+ imd.Color = pixel.RGB(1, 0, 0)
+ imd.Push(win.Bounds().Center().Add(pixel.V(-offset, 0)))
+ imd.Circle(200, 0)
+ imd.Draw(canvas)
+
+ // blue circle
+ imd.Clear()
+ imd.Color = pixel.RGB(0, 0, 1)
+ imd.Push(win.Bounds().Center().Add(pixel.V(offset, 0)))
+ imd.Circle(150, 0)
+ imd.Draw(canvas)
+
+ // yellow circle
+ imd.Clear()
+ imd.Color = pixel.RGB(1, 1, 0)
+ imd.Push(win.Bounds().Center().Add(pixel.V(0, -offset)))
+ imd.Circle(100, 0)
+ imd.Draw(canvas)
+
+ // magenta circle
+ imd.Clear()
+ imd.Color = pixel.RGB(1, 0, 1)
+ imd.Push(win.Bounds().Center().Add(pixel.V(0, offset)))
+ imd.Circle(50, 0)
+ imd.Draw(canvas)
+
+ win.Clear(colornames.Green)
+ canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center()))
+ win.Update()
+ }
+}
+
+func main() {
+ pixelgl.Run(run)
+}
diff --git a/examples/xor/screenshot.png b/examples/xor/screenshot.png
new file mode 100644
index 0000000..3f14dce
Binary files /dev/null and b/examples/xor/screenshot.png differ
diff --git a/geometry.go b/geometry.go
new file mode 100644
index 0000000..cdd7d50
--- /dev/null
+++ b/geometry.go
@@ -0,0 +1,54 @@
+package pixel
+
+// Bezier is cubic Bézier curve used for interpolation. For more info
+// see https://en.wikipedia.org/wiki/B%C3%A9zier_curve,
+// In case you are looking for visualization see https://www.desmos.com/calculator/d1ofwre0fr
+type Bezier struct {
+ Start, StartHandle, EndHandle, End Vec
+ redundant bool
+}
+
+// ZB is Zero Bezier Curve that skips calculation and always returns V(1, 0)
+// Its mainly because Calculation uses lot of function calls and in case of
+// particles, it can make some difference
+var ZB = Constant(V(1, 0))
+
+// B returns new curve. if curve is just placeholder use constant. Handles are
+// relative to start and end point so:
+//
+// pixel.B(ZV, ZV, ZV, V(1, 0)) == Bezier{ZV, ZV, V(1, 0), V(1, 0)}
+func B(start, startHandle, endHandle, end Vec) Bezier {
+ return Bezier{start, startHandle.Add(start), endHandle.Add(end), end, false}
+}
+
+// Linear returns linear Bezier curve
+func Linear(start, end Vec) Bezier {
+ return B(start, ZV, ZV, end)
+}
+
+// Constant returns Bezier curve that always return same point,
+// This is usefull as placeholder, because it skips calculation
+func Constant(constant Vec) Bezier {
+ return Bezier{
+ Start: constant,
+ redundant: true,
+ }
+}
+
+// Point returns point along the curve determinate by t (0 - 1)
+// You can of course pass any value though its really hard to
+// predict what value will it return
+func (b Bezier) Point(t float64) Vec {
+ if b.redundant || b.Start == b.End {
+ b.redundant = true
+ return b.Start
+ }
+
+ inv := 1.0 - t
+ c, d, e, f := inv*inv*inv, inv*inv*t*3.0, inv*t*t*3.0, t*t*t
+
+ return V(
+ b.Start.X*c+b.StartHandle.X*d+b.EndHandle.X*e+b.End.X*f,
+ b.Start.Y*c+b.StartHandle.Y*d+b.EndHandle.Y*e+b.End.Y*f,
+ )
+}
diff --git a/geometry_test.go b/geometry_test.go
new file mode 100644
index 0000000..0978169
--- /dev/null
+++ b/geometry_test.go
@@ -0,0 +1,58 @@
+package pixel_test
+
+import (
+ "testing"
+
+ pixel "github.com/gopxl/pixel"
+)
+
+type sub struct {
+ result pixel.Vec
+ t float64
+}
+
+func TestBezier(t *testing.T) {
+ tests := []struct {
+ curve pixel.Bezier
+
+ subTest []sub
+ name string
+ }{
+ {
+ pixel.Constant(pixel.V(1, 0)),
+ []sub{
+ {pixel.V(1, 0), 0.0},
+ {pixel.V(1, 0), 100.0},
+ },
+ "constant",
+ },
+ {
+ pixel.Linear(pixel.V(1, 0), pixel.ZV),
+ []sub{
+ {pixel.V(1, 0), 0.0},
+ {pixel.ZV, 1.0},
+ },
+ "lenear",
+ },
+ {
+ pixel.B(pixel.V(0, 1), pixel.V(1, 0), pixel.V(-1, 0), pixel.V(1, 0)),
+ []sub{
+ {pixel.V(0, 1), 0.0},
+ {pixel.V(1, 0), 1.0},
+ {pixel.V(.5, .5), 0.5},
+ },
+ "curved",
+ },
+ }
+
+ for _, c := range tests {
+ t.Run(c.name, func(t *testing.T) {
+ for _, st := range c.subTest {
+ val := c.curve.Point(st.t)
+ if val != st.result {
+ t.Errorf("inputted: %v expected: %v got: %v", st.t, st.result, val)
+ }
+ }
+ })
+ }
+}
diff --git a/go.mod b/go.mod
index 7d42916..4aac57b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,21 +1,21 @@
-module github.com/duysqubix/pixel2
+module github.com/gopxl/pixel
go 1.21
require (
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3
- github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259
- github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be
- github.com/go-gl/mathgl v1.0.0
+ github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b
+ github.com/go-gl/mathgl v1.1.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/pkg/errors v0.9.1
- github.com/stretchr/testify v1.7.0
- golang.org/x/image v0.5.0
+ github.com/stretchr/testify v1.8.4
+ golang.org/x/image v0.13.0
)
require (
- github.com/davecgh/go-spew v1.1.0 // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
)
diff --git a/go.sum b/go.sum
index 309cd0e..44ed4ba 100644
--- a/go.sum
+++ b/go.sum
@@ -1,53 +1,31 @@
-github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
-github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369 h1:gv4BgP50atccdK/1tZHDyP6rMwiiutR2HPreR/OyLzI=
github.com/faiface/glhf v0.0.0-20211013000516-57b20770c369/go.mod h1:dDdUO+G9ZnJ9sc8nIUvhLkE45k8PEKW6+A3TdWsfpV0=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
-github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259 h1:8q7+xl2D2qHPLTII1t4vSMNP2VKwDcn+Avf2WXvdB1A=
github.com/go-gl/gl v0.0.0-20210905235341-f7a045908259/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
+github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/go-gl/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be h1:vEIVIuBApEBQTEJt19GfhoU+zFSV+sNTa9E9FdnRYfk=
-github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210727001814-0db043d8d5be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
+github.com/go-gl/mathgl v1.1.0 h1:0lzZ+rntPX3/oGrDzYGdowSLC2ky8Osirvf5uAwfIEA=
+github.com/go-gl/mathgl v1.1.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
-golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
-golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
-golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
-golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
-golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg=
+golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go
index b5bcaf3..d3021b7 100644
--- a/imdraw/imdraw.go
+++ b/imdraw/imdraw.go
@@ -6,7 +6,7 @@ import (
"image/color"
"math"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
// IMDraw is an immediate-mode-like shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
diff --git a/imdraw/imdraw_test.go b/imdraw/imdraw_test.go
index 2076b95..4b815a4 100644
--- a/imdraw/imdraw_test.go
+++ b/imdraw/imdraw_test.go
@@ -5,8 +5,8 @@ import (
"math/rand"
"testing"
- "github.com/duysqubix/pixel2"
- "github.com/duysqubix/pixel2/imdraw"
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/imdraw"
)
func BenchmarkPush(b *testing.B) {
diff --git a/line_test.go b/line_test.go
index 898f48d..628f26d 100644
--- a/line_test.go
+++ b/line_test.go
@@ -5,7 +5,7 @@ import (
"reflect"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func TestLine_Bounds(t *testing.T) {
diff --git a/math_test.go b/math_test.go
index 0369605..c334d73 100644
--- a/math_test.go
+++ b/math_test.go
@@ -5,7 +5,7 @@ import (
"math"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
// closeEnough will shift the decimal point by the accuracy required, truncates the results and compares them.
diff --git a/matrix_test.go b/matrix_test.go
index d4c4998..d69357f 100644
--- a/matrix_test.go
+++ b/matrix_test.go
@@ -6,7 +6,7 @@ import (
"math/rand"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
"github.com/stretchr/testify/assert"
)
diff --git a/pixel_test.go b/pixel_test.go
index 2132642..6a785d1 100644
--- a/pixel_test.go
+++ b/pixel_test.go
@@ -8,8 +8,8 @@ import (
_ "image/png"
- "github.com/duysqubix/pixel2"
- "github.com/duysqubix/pixel2/pixelgl"
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/pixelgl"
)
// onePixelImage is the byte representation of a 1x1 solid white png file
diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go
index cf10337..992756c 100644
--- a/pixelgl/canvas.go
+++ b/pixelgl/canvas.go
@@ -6,7 +6,7 @@ import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
"github.com/go-gl/mathgl/mgl32"
"github.com/pkg/errors"
)
diff --git a/pixelgl/glframe.go b/pixelgl/glframe.go
index 22950b9..d79b56a 100644
--- a/pixelgl/glframe.go
+++ b/pixelgl/glframe.go
@@ -3,7 +3,7 @@ package pixelgl
import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
// GLFrame is a type that helps implementing OpenGL Targets. It implements most common methods to
diff --git a/pixelgl/glpicture.go b/pixelgl/glpicture.go
index 4a027fe..0cf832d 100644
--- a/pixelgl/glpicture.go
+++ b/pixelgl/glpicture.go
@@ -5,7 +5,7 @@ import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
// GLPicture is a pixel.PictureColor with a Texture. All OpenGL Targets should implement and accept
diff --git a/pixelgl/glshader.go b/pixelgl/glshader.go
index 6352519..f0f2207 100644
--- a/pixelgl/glshader.go
+++ b/pixelgl/glshader.go
@@ -46,10 +46,10 @@ var defaultCanvasVertexFormat = glhf.AttrFormat{
canvasColor: glhf.Attr{Name: "aColor", Type: glhf.Vec4},
canvasTexCoords: glhf.Attr{Name: "aTexCoords", Type: glhf.Vec2},
canvasIntensity: glhf.Attr{Name: "aIntensity", Type: glhf.Float},
- canvasClip: glhf.Attr{Name: "aClipRect", Type: glhf.Vec4},
+ canvasClip: glhf.Attr{Name: "aClipRect", Type: glhf.Vec4},
}
-// Sets up a base shader with everything needed for a Pixel
+// NewGLShader sets up a base shader with everything needed for a Pixel
// canvas to render correctly. The defaults can be overridden
// by simply using the SetUniform function.
func NewGLShader(fragmentShader string) *GLShader {
@@ -109,10 +109,10 @@ func (gs *GLShader) getUniform(Name string) int {
// SetUniform appends a custom uniform name and value to the shader.
// if the uniform already exists, it will simply be overwritten.
//
-// example:
+// Example:
//
-// utime := float32(time.Since(starttime)).Seconds())
-// mycanvas.shader.AddUniform("u_time", &utime)
+// utime := float32(time.Since(starttime)).Seconds())
+// mycanvas.shader.AddUniform("u_time", &utime)
func (gs *GLShader) SetUniform(name string, value interface{}) {
t, p := getAttrType(value)
if loc := gs.getUniform(name); loc > -1 {
diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go
index 346eb5c..423be13 100644
--- a/pixelgl/gltriangles.go
+++ b/pixelgl/gltriangles.go
@@ -5,7 +5,7 @@ import (
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
// GLTriangles are OpenGL triangles implemented using glhf.VertexSlice.
diff --git a/pixelgl/input.go b/pixelgl/input.go
index 7c7c387..01f8f46 100644
--- a/pixelgl/input.go
+++ b/pixelgl/input.go
@@ -4,7 +4,7 @@ import (
"time"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
"github.com/go-gl/glfw/v3.3/glfw"
)
diff --git a/pixelgl/util.go b/pixelgl/util.go
index d85afd9..013e9a3 100644
--- a/pixelgl/util.go
+++ b/pixelgl/util.go
@@ -3,7 +3,7 @@ package pixelgl
import (
"math"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func intBounds(bounds pixel.Rect) (x, y, w, h int) {
diff --git a/pixelgl/window.go b/pixelgl/window.go
index b5fbc16..a94956d 100644
--- a/pixelgl/window.go
+++ b/pixelgl/window.go
@@ -6,9 +6,9 @@ import (
"image/color"
"runtime"
+ pixel "github.com/gopxl/pixel"
"github.com/faiface/glhf"
"github.com/faiface/mainthread"
- "github.com/duysqubix/pixel2"
"github.com/go-gl/gl/v3.3-core/gl"
"github.com/go-gl/glfw/v3.3/glfw"
"github.com/pkg/errors"
@@ -232,7 +232,7 @@ func (w *Window) ClipboardText() string {
}
// SetClipboardText passes the given string to the underlying glfw window to set the
-// systems clipboard.
+// systems clipboard.
func (w *Window) SetClipboardText(text string) {
w.window.SetClipboardString(text)
}
@@ -527,6 +527,15 @@ func (w *Window) Show() {
})
}
+// Hide hides the window, if it was previously visible. If the window is already
+// hidden or is in full screen mode, this function does nothing.
+
+func (w *Window) Hide() {
+ mainthread.Call(func() {
+ w.window.Hide()
+ })
+}
+
// Clipboard returns the contents of the system clipboard.
func (w *Window) Clipboard() string {
var clipboard string
@@ -536,7 +545,7 @@ func (w *Window) Clipboard() string {
return clipboard
}
-// SetClipboardString sets the system clipboard to the specified UTF-8 encoded string.
+// SetClipboard sets the system clipboard to the specified UTF-8 encoded string.
func (w *Window) SetClipboard(str string) {
mainthread.Call(func() {
w.window.SetClipboardString(str)
diff --git a/pixelgl/windowmanager.go b/pixelgl/windowmanager.go
new file mode 100644
index 0000000..d1fe327
--- /dev/null
+++ b/pixelgl/windowmanager.go
@@ -0,0 +1,109 @@
+package pixelgl
+
+import (
+ "errors"
+ "time"
+)
+
+type EasyWindow interface {
+ Win() *Window // get underlying GLFW window
+ Setup() error // setup window
+ Update() error // update window
+ Draw() error // draw to window
+}
+
+type WindowManager struct {
+ Windows []EasyWindow
+ currentFps float64
+ targetDuration time.Duration
+}
+
+func NewWindowManager() *WindowManager {
+ return &WindowManager{}
+}
+
+func (wm *WindowManager) SetFPS(fps int) error {
+ if fps <= 0 {
+ return errors.New("FPS must be greater than 0")
+ }
+ // ms := 1.0 / float64(fps) * 1000000.0
+ us := 1.0 / float64(fps) * 1000000.0
+ wm.targetDuration = time.Duration(us) * time.Microsecond
+ return nil
+}
+
+func (wm *WindowManager) FPS() float64 {
+ return wm.currentFps
+}
+func (wm *WindowManager) InsertWindow(win EasyWindow) error {
+ wm.Windows = append(wm.Windows, win)
+ return nil
+}
+
+func (wm *WindowManager) InsertWindows(wins []EasyWindow) error {
+ for _, win := range wins {
+ if err := wm.InsertWindow(win); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (wm *WindowManager) update() error {
+ for _, win := range wm.Windows {
+ if err := win.Update(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (wm *WindowManager) draw() error {
+ for _, win := range wm.Windows {
+ if err := win.Draw(); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (wm *WindowManager) Loop() error {
+ // assumes first index is main loop
+ win := wm.Windows[0].Win()
+
+ if win == nil {
+ panic("no main window")
+ }
+
+ // setup windows
+ for _, win := range wm.Windows {
+ if err := win.Setup(); err != nil {
+ return err
+ }
+ }
+
+ for !win.Closed() {
+ start := time.Now()
+
+ if err := wm.update(); err != nil {
+ return err
+ }
+ if err := wm.draw(); err != nil {
+ return err
+ }
+
+ // update GFLW window
+ for _, win := range wm.Windows {
+ win.Win().Update()
+ }
+
+ // calculate FPS
+ elapsed := time.Since(start)
+
+ if elapsed < wm.targetDuration {
+ time.Sleep(wm.targetDuration - elapsed)
+ }
+ wm.currentFps = 1000000.0 / float64(time.Since(start).Microseconds())
+ }
+ return nil
+}
diff --git a/rectangle_test.go b/rectangle_test.go
index 0fbb91b..e59d44e 100644
--- a/rectangle_test.go
+++ b/rectangle_test.go
@@ -5,7 +5,7 @@ import (
"reflect"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
func TestRect_Resize(t *testing.T) {
diff --git a/text/atlas.go b/text/atlas.go
index 12ad5ac..023094f 100644
--- a/text/atlas.go
+++ b/text/atlas.go
@@ -6,7 +6,7 @@ import (
"sort"
"unicode"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
)
diff --git a/text/atlas_test.go b/text/atlas_test.go
index 1681304..90e873c 100644
--- a/text/atlas_test.go
+++ b/text/atlas_test.go
@@ -3,7 +3,7 @@ package text_test
import (
"testing"
- "github.com/duysqubix/pixel2/text"
+ "github.com/gopxl/pixel/text"
"golang.org/x/image/font/inconsolata"
)
diff --git a/text/text.go b/text/text.go
index 1e10701..294bea4 100644
--- a/text/text.go
+++ b/text/text.go
@@ -6,7 +6,7 @@ import (
"unicode"
"unicode/utf8"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
"golang.org/x/image/font/basicfont"
)
diff --git a/text/text_test.go b/text/text_test.go
index baf1890..9f48efb 100644
--- a/text/text_test.go
+++ b/text/text_test.go
@@ -9,8 +9,8 @@ import (
"golang.org/x/image/font/basicfont"
"golang.org/x/image/font/gofont/goregular"
- "github.com/duysqubix/pixel2"
- "github.com/duysqubix/pixel2/text"
+ "github.com/gopxl/pixel"
+ "github.com/gopxl/pixel/text"
"github.com/golang/freetype/truetype"
)
diff --git a/vector_test.go b/vector_test.go
index eba19e9..dd696a9 100644
--- a/vector_test.go
+++ b/vector_test.go
@@ -4,7 +4,7 @@ import (
"fmt"
"testing"
- "github.com/duysqubix/pixel2"
+ "github.com/gopxl/pixel"
)
type floorTest struct {