Compare commits
113 Commits
Author | SHA1 | Date |
---|---|---|
|
bdbce3a27b | |
|
0389ee7bf9 | |
|
736f4549a9 | |
|
ef9fd8fe10 | |
|
4246e90215 | |
|
1d0169b2b7 | |
|
2626892e97 | |
|
1e2eb29a4f | |
|
b60b7e5207 | |
|
8165c38c6d | |
|
82fa73952e | |
|
bab33976cc | |
|
0e54ed1080 | |
|
db4f08aefc | |
|
fd6e056f92 | |
|
e93b9a1d4c | |
|
d9a94fd157 | |
|
6e5cbaa493 | |
|
9e150a7a1b | |
|
9c74e66118 | |
|
ee74866587 | |
|
a2ad3dbf7c | |
|
af1ccf885f | |
|
b756edfac6 | |
|
c69ef58898 | |
|
6e63f27d6e | |
|
acfd836a7f | |
|
8679a702a9 | |
|
e9380d7eed | |
|
3ed8c0016c | |
|
2afe44a9c9 | |
|
c028b66b68 | |
|
080735510c | |
|
862f30a004 | |
|
c0ddb0b287 | |
|
3e493c13e1 | |
|
f9f61911a7 | |
|
d30b73fb8b | |
|
4aea56198f | |
|
591fadaaa5 | |
|
497df7842b | |
|
a403cfe50b | |
|
bdbba8f3c1 | |
|
c485793a83 | |
|
97158ba502 | |
|
01ff4230da | |
|
37dcef6ab6 | |
|
ab9d608c45 | |
|
05da8e6f92 | |
|
49c2beeca9 | |
|
f4ce166964 | |
|
93e9ab79b1 | |
|
58be215a76 | |
|
ce083a9a9f | |
|
2ef17e7a95 | |
|
80f48bc0b6 | |
|
420e83bc61 | |
|
b51b3e8f38 | |
|
620784b34c | |
|
368dab0e7c | |
|
9bfb83861a | |
|
4e30fb9f5c | |
|
11c76944b6 | |
|
30af7c6612 | |
|
411ad7c27d | |
|
37b0f0956b | |
|
10684b6add | |
|
cf666b5866 | |
|
f325092c02 | |
|
939a76923d | |
|
fcbc61c570 | |
|
e55d490801 | |
|
b5a2c12175 | |
|
14a01a5522 | |
|
3276c4e4a1 | |
|
c61b677fa9 | |
|
c9e0f7262d | |
|
a2499cf90d | |
|
6d2aeb64e7 | |
|
34a6d020a2 | |
|
9b624ae466 | |
|
f2a0a19f6e | |
|
a555999120 | |
|
aa2c560d4c | |
|
4eca5f2d1e | |
|
4374bb7be1 | |
|
3fa31cdab5 | |
|
219559cf20 | |
|
3d3f1c6e11 | |
|
58e8b8f892 | |
|
13b9e6aee5 | |
|
9e8697cdf6 | |
|
134fe2bf7b | |
|
9df8a3761b | |
|
a081d5b0c8 | |
|
32ae09e1e5 | |
|
317cfb17b0 | |
|
a9e6f41315 | |
|
9cbce8f638 | |
|
e3f7901f2c | |
|
ee19c6b361 | |
|
5ad013b286 | |
|
7fdf8c0d60 | |
|
3a25f78b1a | |
|
ad733f5946 | |
|
8d5879070f | |
|
2155babc5d | |
|
dc9afb5557 | |
|
79f7f4fb42 | |
|
2562f6b754 | |
|
b138fc5d5b | |
|
15a270e689 | |
|
538ad90185 |
147
README.md
|
@ -1,5 +1,144 @@
|
|||
# pixel
|
||||
A simple and fast desktop multimedia/gamedev library.
|
||||
# Pixel [](https://godoc.org/github.com/faiface/pixel) [](https://goreportcard.com/report/github.com/faiface/pixel) [](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
The core features of the library are pretty much completed. I'm doing some proofreading and
|
||||
improving quality of the code. Will **announce** the library after.
|
||||
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
|
||||
```
|
||||
|
||||
See [requirements](#requirements) for the list of libraries necessary for compilation.
|
||||
|
||||
## Tutorial
|
||||
|
||||
The [Wiki of this repo](https://github.com/faiface/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)
|
||||
|
||||
## Examples
|
||||
|
||||
The [examples](https://github.com/faiface/pixel/tree/master/examples) directory contains a few
|
||||
examples demonstrating Pixel's functionality.
|
||||
|
||||
**To run an example**, navigate to it's directory, then `go run` the `main.go` file. For example:
|
||||
|
||||
```
|
||||
$ cd examples/platformer
|
||||
$ go run main.go
|
||||
```
|
||||
|
||||
Here are some screenshots from the examples!
|
||||
|
||||
| [Lights](examples/lights) | [Platformer](examples/platformer) |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
| [Smoke](examples/smoke) | [Xor](examples/xor) |
|
||||
| --- | --- |
|
||||
|  |  |
|
||||
|
||||
## Features
|
||||
|
||||
Here's the list of the main features in Pixel. Although Pixel is still under heavy development,
|
||||
**there should be no major breakage in the API.** This is not a 100% guarantee, though.
|
||||
|
||||
- Fast 2D graphics
|
||||
- Sprites
|
||||
- Primitive shapes with immediate mode style
|
||||
[IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw) (circles, rectangles,
|
||||
lines, ...)
|
||||
- Optimized drawing with [Batch](https://github.com/faiface/pixel/wiki/Drawing-efficiently-with-Batch)
|
||||
- Simple and convenient API
|
||||
- Drawing a sprite to a window is as simple as `sprite.Draw(window)`
|
||||
- Adding and subtracting vectors with `+` and `-` operators... how?
|
||||
- Wanna know where the center of a window is? `window.Bounds().Center()`
|
||||
- [...](https://godoc.org/github.com/faiface/pixel)
|
||||
- Full documentation and tutorial
|
||||
- Works on Linux, macOS and Windows
|
||||
- Window creation and manipulation (resizing, fullscreen, multiple windows, ...)
|
||||
- Keyboard and mouse input without events
|
||||
- Well integrated with the Go standard library
|
||||
- Use `"image"` package for loading pictures
|
||||
- Use `"time"` package for measuring delta time and FPS
|
||||
- Use `"image/color"` for colors, or use Pixel's own `color.Color` format, which supports easy
|
||||
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)
|
||||
- Moving, scaling, rotating
|
||||
- Easy camera implementation
|
||||
- Off-screen drawing to Canvas or any other target (Batch, IMDraw, ...)
|
||||
- Fully garbage collected, no `Close` or `Dispose` methods
|
||||
- Full [Porter-Duff](http://ssp.impulsetrain.com/porterduff.html) composition, which enables
|
||||
- 2D lighting
|
||||
- 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)
|
||||
- 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)
|
||||
package
|
||||
|
||||
## Missing features
|
||||
|
||||
Pixel is in development and still missing few critical features. Here're the most critical ones.
|
||||
|
||||
- Audio
|
||||
- Drawing text
|
||||
- Antialiasing (filtering is supported, though)
|
||||
- Advanced window manipulation (cursor hiding, window icon, ...)
|
||||
- Better support for Hi-DPI displays
|
||||
- Mobile (and perhaps HTML5?) backend
|
||||
- More advanced graphical effects (e.g. blur)
|
||||
- Tests and benchmarks
|
||||
|
||||
**Implementing these features will get us to the 1.0 release.** Contribute, so that it's as soon as
|
||||
possible!
|
||||
|
||||
## Requirements
|
||||
|
||||
[PixelGL](https://godoc.org/github.com/faiface/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).
|
||||
|
||||
- On macOS, you need Xcode or Command Line Tools for Xcode (`xcode-select --install`) for required
|
||||
headers and libraries.
|
||||
- On Ubuntu/Debian-like Linux distributions, you need `libgl1-mesa-dev` and `xorg-dev` packages.
|
||||
- On CentOS/Fedora-like Linux distributions, you need `libX11-devel libXcursor-devel libXrandr-devel
|
||||
libXinerama-devel mesa-libGL-devel libXi-devel` packages.
|
||||
- See [here](http://www.glfw.org/docs/latest/compile.html#compile_deps) for full details.
|
||||
|
||||
## Contributing
|
||||
|
||||
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**.
|
||||
|
||||
**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.
|
||||
|
||||
**Don't start working on a pull request before submiting an issue or commenting on one. Proposals
|
||||
also take the form of issues.**
|
||||
|
||||
For any kind of discussion, feel free to use our
|
||||
[Gitter](https://gitter.im/pixellib/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
community.
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
||||
|
|
34
batch.go
|
@ -5,8 +5,7 @@ import (
|
|||
"image/color"
|
||||
)
|
||||
|
||||
// Batch is a Target that allows for efficient drawing of many objects with the same Picture (but
|
||||
// different slices of the same Picture are allowed).
|
||||
// Batch is a Target that allows for efficient drawing of many objects with the same Picture.
|
||||
//
|
||||
// To put an object into a Batch, just draw it onto it:
|
||||
// object.Draw(batch)
|
||||
|
@ -14,7 +13,7 @@ type Batch struct {
|
|||
cont Drawer
|
||||
|
||||
mat Matrix
|
||||
col NRGBA
|
||||
col RGBA
|
||||
}
|
||||
|
||||
var _ BasicTarget = (*Batch)(nil)
|
||||
|
@ -29,7 +28,7 @@ var _ BasicTarget = (*Batch)(nil)
|
|||
func NewBatch(container Triangles, pic Picture) *Batch {
|
||||
b := &Batch{cont: Drawer{Triangles: container, Picture: pic}}
|
||||
b.SetMatrix(IM)
|
||||
b.SetColorMask(NRGBA{1, 1, 1, 1})
|
||||
b.SetColorMask(Alpha(1))
|
||||
return b
|
||||
}
|
||||
|
||||
|
@ -63,10 +62,10 @@ func (b *Batch) SetMatrix(m Matrix) {
|
|||
// SetColorMask sets a mask color used in the following draws onto the Batch.
|
||||
func (b *Batch) SetColorMask(c color.Color) {
|
||||
if c == nil {
|
||||
b.col = NRGBA{1, 1, 1, 1}
|
||||
b.col = Alpha(1)
|
||||
return
|
||||
}
|
||||
b.col = ToNRGBA(c)
|
||||
b.col = ToRGBA(c)
|
||||
}
|
||||
|
||||
// MakeTriangles returns a specialized copy of the provided Triangles that draws onto this Batch.
|
||||
|
@ -81,21 +80,19 @@ func (b *Batch) MakeTriangles(t Triangles) TargetTriangles {
|
|||
|
||||
// MakePicture returns a specialized copy of the provided Picture that draws onto this Batch.
|
||||
func (b *Batch) MakePicture(p Picture) TargetPicture {
|
||||
if p.Original() != b.cont.Picture.Original() {
|
||||
panic(fmt.Errorf("(%T).MakePicture: Picture is not a slice of Batch's Picture", b))
|
||||
if p != b.cont.Picture {
|
||||
panic(fmt.Errorf("(%T).MakePicture: Picture is not the Batch's Picture", b))
|
||||
}
|
||||
bp := &batchPicture{
|
||||
pic: p,
|
||||
dst: b,
|
||||
}
|
||||
bp.orig = bp
|
||||
return bp
|
||||
}
|
||||
|
||||
type batchTriangles struct {
|
||||
tri Triangles
|
||||
tmp *TrianglesData
|
||||
|
||||
dst *Batch
|
||||
}
|
||||
|
||||
|
@ -149,27 +146,14 @@ func (bt *batchTriangles) Draw() {
|
|||
}
|
||||
|
||||
type batchPicture struct {
|
||||
pic Picture
|
||||
orig *batchPicture
|
||||
dst *Batch
|
||||
pic Picture
|
||||
dst *Batch
|
||||
}
|
||||
|
||||
func (bp *batchPicture) Bounds() Rect {
|
||||
return bp.pic.Bounds()
|
||||
}
|
||||
|
||||
func (bp *batchPicture) Slice(r Rect) Picture {
|
||||
return &batchPicture{
|
||||
pic: bp.pic.Slice(r),
|
||||
orig: bp.orig,
|
||||
dst: bp.dst,
|
||||
}
|
||||
}
|
||||
|
||||
func (bp *batchPicture) Original() Picture {
|
||||
return bp.orig
|
||||
}
|
||||
|
||||
func (bp *batchPicture) Draw(t TargetTriangles) {
|
||||
bt := t.(*batchTriangles)
|
||||
if bp.dst != bt.dst {
|
||||
|
|
90
color.go
|
@ -2,17 +2,30 @@ package pixel
|
|||
|
||||
import "image/color"
|
||||
|
||||
// NRGBA represents a non-alpha-premultiplied RGBA color with components within range [0, 1].
|
||||
// RGBA represents an alpha-premultiplied RGBA color with components within range [0, 1].
|
||||
//
|
||||
// The difference between color.NRGBA is that the value range is [0, 1] and the values are floats.
|
||||
type NRGBA struct {
|
||||
// The difference between color.RGBA is that the value range is [0, 1] and the values are floats.
|
||||
type RGBA struct {
|
||||
R, G, B, A float64
|
||||
}
|
||||
|
||||
// RGB returns a fully opaque RGBA color with the given RGB values.
|
||||
//
|
||||
// A common way to construct a transparent color is to create one with RGB constructor, then
|
||||
// multiply it by a color obtained from the Alpha constructor.
|
||||
func RGB(r, g, b float64) RGBA {
|
||||
return RGBA{r, g, b, 1}
|
||||
}
|
||||
|
||||
// Alpha returns a white RGBA color with the given alpha component.
|
||||
func Alpha(a float64) RGBA {
|
||||
return RGBA{a, a, a, a}
|
||||
}
|
||||
|
||||
// Add adds color d to color c component-wise and returns the result (the components are not
|
||||
// clamped).
|
||||
func (c NRGBA) Add(d NRGBA) NRGBA {
|
||||
return NRGBA{
|
||||
func (c RGBA) Add(d RGBA) RGBA {
|
||||
return RGBA{
|
||||
R: c.R + d.R,
|
||||
G: c.G + d.G,
|
||||
B: c.B + d.B,
|
||||
|
@ -22,8 +35,8 @@ func (c NRGBA) Add(d NRGBA) NRGBA {
|
|||
|
||||
// Sub subtracts color d from color c component-wise and returns the result (the components
|
||||
// are not clamped).
|
||||
func (c NRGBA) Sub(d NRGBA) NRGBA {
|
||||
return NRGBA{
|
||||
func (c RGBA) Sub(d RGBA) RGBA {
|
||||
return RGBA{
|
||||
R: c.R - d.R,
|
||||
G: c.G - d.G,
|
||||
B: c.B - d.B,
|
||||
|
@ -32,8 +45,8 @@ func (c NRGBA) Sub(d NRGBA) NRGBA {
|
|||
}
|
||||
|
||||
// Mul multiplies color c by color d component-wise (the components are not clamped).
|
||||
func (c NRGBA) Mul(d NRGBA) NRGBA {
|
||||
return NRGBA{
|
||||
func (c RGBA) Mul(d RGBA) RGBA {
|
||||
return RGBA{
|
||||
R: c.R * d.R,
|
||||
G: c.G * d.G,
|
||||
B: c.B * d.B,
|
||||
|
@ -43,8 +56,8 @@ func (c NRGBA) Mul(d NRGBA) NRGBA {
|
|||
|
||||
// Scaled multiplies each component of color c by scale and returns the result (the components
|
||||
// are not clamped).
|
||||
func (c NRGBA) Scaled(scale float64) NRGBA {
|
||||
return NRGBA{
|
||||
func (c RGBA) Scaled(scale float64) RGBA {
|
||||
return RGBA{
|
||||
R: c.R * scale,
|
||||
G: c.G * scale,
|
||||
B: c.B * scale,
|
||||
|
@ -52,37 +65,23 @@ func (c NRGBA) Scaled(scale float64) NRGBA {
|
|||
}
|
||||
}
|
||||
|
||||
// RGBA returns alpha-premultiplied red, green, blue and alpha components of the NRGBA color.
|
||||
func (c NRGBA) RGBA() (r, g, b, a uint32) {
|
||||
c.R = clamp(c.R, 0, 1)
|
||||
c.G = clamp(c.G, 0, 1)
|
||||
c.B = clamp(c.B, 0, 1)
|
||||
c.A = clamp(c.A, 0, 1)
|
||||
r = uint32(0xffff * c.R * c.A)
|
||||
g = uint32(0xffff * c.G * c.A)
|
||||
b = uint32(0xffff * c.B * c.A)
|
||||
// RGBA returns alpha-premultiplied red, green, blue and alpha components of the RGBA color.
|
||||
func (c RGBA) RGBA() (r, g, b, a uint32) {
|
||||
r = uint32(0xffff * c.R)
|
||||
g = uint32(0xffff * c.G)
|
||||
b = uint32(0xffff * c.B)
|
||||
a = uint32(0xffff * c.A)
|
||||
return
|
||||
}
|
||||
|
||||
func clamp(x, low, high float64) float64 {
|
||||
if x < low {
|
||||
return low
|
||||
}
|
||||
if x > high {
|
||||
return high
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// ToNRGBA converts a color to NRGBA format. Using this function is preferred to using NRGBAModel,
|
||||
// for performance (using NRGBAModel introduced additional unnecessary allocations).
|
||||
func ToNRGBA(c color.Color) NRGBA {
|
||||
if c, ok := c.(NRGBA); ok {
|
||||
// ToRGBA converts a color to RGBA format. Using this function is preferred to using RGBAModel, for
|
||||
// performance (using RGBAModel introduced additional unnecessary allocations).
|
||||
func ToRGBA(c color.Color) RGBA {
|
||||
if c, ok := c.(RGBA); ok {
|
||||
return c
|
||||
}
|
||||
if c, ok := c.(color.NRGBA); ok {
|
||||
return NRGBA{
|
||||
if c, ok := c.(color.RGBA); ok {
|
||||
return RGBA{
|
||||
R: float64(c.R) / 255,
|
||||
G: float64(c.G) / 255,
|
||||
B: float64(c.B) / 255,
|
||||
|
@ -90,20 +89,17 @@ func ToNRGBA(c color.Color) NRGBA {
|
|||
}
|
||||
}
|
||||
r, g, b, a := c.RGBA()
|
||||
if a == 0 {
|
||||
return NRGBA{0, 0, 0, 0}
|
||||
}
|
||||
return NRGBA{
|
||||
float64(r) / float64(a),
|
||||
float64(g) / float64(a),
|
||||
float64(b) / float64(a),
|
||||
return RGBA{
|
||||
float64(r) / 0xffff,
|
||||
float64(g) / 0xffff,
|
||||
float64(b) / 0xffff,
|
||||
float64(a) / 0xffff,
|
||||
}
|
||||
}
|
||||
|
||||
// NRGBAModel converts colors to NRGBA format.
|
||||
var NRGBAModel = color.ModelFunc(nrgbaModel)
|
||||
// RGBAModel converts colors to RGBA format.
|
||||
var RGBAModel = color.ModelFunc(rgbaModel)
|
||||
|
||||
func nrgbaModel(c color.Color) color.Color {
|
||||
return ToNRGBA(c)
|
||||
func rgbaModel(c color.Color) color.Color {
|
||||
return ToRGBA(c)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package pixel
|
||||
|
||||
import "errors"
|
||||
|
||||
// ComposeTarget is a BasicTarget capable of Porter-Duff composition.
|
||||
type ComposeTarget interface {
|
||||
BasicTarget
|
||||
|
||||
// SetComposeMethod sets a Porter-Duff composition method to be used.
|
||||
SetComposeMethod(ComposeMethod)
|
||||
}
|
||||
|
||||
// ComposeMethod is a Porter-Duff composition method.
|
||||
type ComposeMethod int
|
||||
|
||||
// Here's the list of all available Porter-Duff composition methods. Use ComposeOver for the basic
|
||||
// alpha blending.
|
||||
const (
|
||||
ComposeOver ComposeMethod = iota
|
||||
ComposeIn
|
||||
ComposeOut
|
||||
ComposeAtop
|
||||
ComposeRover
|
||||
ComposeRin
|
||||
ComposeRout
|
||||
ComposeRatop
|
||||
ComposeXor
|
||||
ComposePlus
|
||||
ComposeCopy
|
||||
)
|
||||
|
||||
// Compose composes two colors together according to the ComposeMethod. A is the foreground, B is
|
||||
// the background.
|
||||
func (cm ComposeMethod) Compose(a, b RGBA) RGBA {
|
||||
var fa, fb float64
|
||||
|
||||
switch cm {
|
||||
case ComposeOver:
|
||||
fa, fb = 1, 1-a.A
|
||||
case ComposeIn:
|
||||
fa, fb = b.A, 0
|
||||
case ComposeOut:
|
||||
fa, fb = 1-b.A, 0
|
||||
case ComposeAtop:
|
||||
fa, fb = b.A, 1-a.A
|
||||
case ComposeRover:
|
||||
fa, fb = 1-b.A, 1
|
||||
case ComposeRin:
|
||||
fa, fb = 0, a.A
|
||||
case ComposeRout:
|
||||
fa, fb = 0, 1-a.A
|
||||
case ComposeRatop:
|
||||
fa, fb = 1-b.A, a.A
|
||||
case ComposeXor:
|
||||
fa, fb = 1-b.A, 1-a.A
|
||||
case ComposePlus:
|
||||
fa, fb = 1, 1
|
||||
case ComposeCopy:
|
||||
fa, fb = 1, 0
|
||||
default:
|
||||
panic(errors.New("Compose: invalid ComposeMethod"))
|
||||
}
|
||||
|
||||
return a.Mul(Alpha(fa)).Add(b.Mul(Alpha(fb)))
|
||||
}
|
108
data.go
|
@ -12,7 +12,7 @@ import (
|
|||
// TrianglesPosition, TrianglesColor and TrianglesPicture.
|
||||
type TrianglesData []struct {
|
||||
Position Vec
|
||||
Color NRGBA
|
||||
Color RGBA
|
||||
Picture Vec
|
||||
Intensity float64
|
||||
}
|
||||
|
@ -42,10 +42,10 @@ func (td *TrianglesData) SetLen(len int) {
|
|||
for i := 0; i < needAppend; i++ {
|
||||
*td = append(*td, struct {
|
||||
Position Vec
|
||||
Color NRGBA
|
||||
Color RGBA
|
||||
Picture Vec
|
||||
Intensity float64
|
||||
}{V(0, 0), NRGBA{1, 1, 1, 1}, V(0, 0), 0})
|
||||
}{V(0, 0), Alpha(1), V(0, 0), 0})
|
||||
}
|
||||
}
|
||||
if len < td.Len() {
|
||||
|
@ -108,7 +108,7 @@ func (td *TrianglesData) Position(i int) Vec {
|
|||
}
|
||||
|
||||
// Color returns the color property of i-th vertex.
|
||||
func (td *TrianglesData) Color(i int) NRGBA {
|
||||
func (td *TrianglesData) Color(i int) RGBA {
|
||||
return (*td)[i].Color
|
||||
}
|
||||
|
||||
|
@ -126,13 +126,12 @@ func (td *TrianglesData) Picture(i int) (pic Vec, intensity float64) {
|
|||
//
|
||||
// The struct's innards are exposed for convenience, manual modification is at your own risk.
|
||||
//
|
||||
// The format of the pixels is color.NRGBA and not pixel.NRGBA for a very serious reason:
|
||||
// pixel.NRGBA takes up 8x more memory than color.NRGBA.
|
||||
// The format of the pixels is color.RGBA and not pixel.RGBA for a very serious reason:
|
||||
// pixel.RGBA takes up 8x more memory than color.RGBA.
|
||||
type PictureData struct {
|
||||
Pix []color.NRGBA
|
||||
Pix []color.RGBA
|
||||
Stride int
|
||||
Rect Rect
|
||||
Orig *PictureData
|
||||
}
|
||||
|
||||
// MakePictureData creates a zero-initialized PictureData covering the given rectangle.
|
||||
|
@ -143,19 +142,18 @@ func MakePictureData(rect Rect) *PictureData {
|
|||
Stride: w,
|
||||
Rect: rect,
|
||||
}
|
||||
pd.Pix = make([]color.NRGBA, w*h)
|
||||
pd.Orig = pd
|
||||
pd.Pix = make([]color.RGBA, w*h)
|
||||
return pd
|
||||
}
|
||||
|
||||
func verticalFlip(nrgba *image.NRGBA) {
|
||||
bounds := nrgba.Bounds()
|
||||
func verticalFlip(rgba *image.RGBA) {
|
||||
bounds := rgba.Bounds()
|
||||
width := bounds.Dx()
|
||||
|
||||
tmpRow := make([]uint8, width*4)
|
||||
for i, j := 0, bounds.Dy()-1; i < j; i, j = i+1, j-1 {
|
||||
iRow := nrgba.Pix[i*nrgba.Stride : i*nrgba.Stride+width*4]
|
||||
jRow := nrgba.Pix[j*nrgba.Stride : j*nrgba.Stride+width*4]
|
||||
iRow := rgba.Pix[i*rgba.Stride : i*rgba.Stride+width*4]
|
||||
jRow := rgba.Pix[j*rgba.Stride : j*rgba.Stride+width*4]
|
||||
|
||||
copy(tmpRow, iRow)
|
||||
copy(iRow, jRow)
|
||||
|
@ -167,28 +165,28 @@ func verticalFlip(nrgba *image.NRGBA) {
|
|||
//
|
||||
// The resulting PictureData's Bounds will be the equivalent of the supplied image.Image's Bounds.
|
||||
func PictureDataFromImage(img image.Image) *PictureData {
|
||||
var nrgba *image.NRGBA
|
||||
if nrgbaImg, ok := img.(*image.NRGBA); ok {
|
||||
nrgba = nrgbaImg
|
||||
var rgba *image.RGBA
|
||||
if rgbaImg, ok := img.(*image.RGBA); ok {
|
||||
rgba = rgbaImg
|
||||
} else {
|
||||
nrgba = image.NewNRGBA(img.Bounds())
|
||||
draw.Draw(nrgba, nrgba.Bounds(), img, img.Bounds().Min, draw.Src)
|
||||
rgba = image.NewRGBA(img.Bounds())
|
||||
draw.Draw(rgba, rgba.Bounds(), img, img.Bounds().Min, draw.Src)
|
||||
}
|
||||
|
||||
verticalFlip(nrgba)
|
||||
verticalFlip(rgba)
|
||||
|
||||
pd := MakePictureData(R(
|
||||
float64(nrgba.Bounds().Min.X),
|
||||
float64(nrgba.Bounds().Min.Y),
|
||||
float64(nrgba.Bounds().Dx()),
|
||||
float64(nrgba.Bounds().Dy()),
|
||||
float64(rgba.Bounds().Min.X),
|
||||
float64(rgba.Bounds().Min.Y),
|
||||
float64(rgba.Bounds().Dx()),
|
||||
float64(rgba.Bounds().Dy()),
|
||||
))
|
||||
|
||||
for i := range pd.Pix {
|
||||
pd.Pix[i].R = nrgba.Pix[i*4+0]
|
||||
pd.Pix[i].G = nrgba.Pix[i*4+1]
|
||||
pd.Pix[i].B = nrgba.Pix[i*4+2]
|
||||
pd.Pix[i].A = nrgba.Pix[i*4+3]
|
||||
pd.Pix[i].R = rgba.Pix[i*4+0]
|
||||
pd.Pix[i].G = rgba.Pix[i*4+1]
|
||||
pd.Pix[i].B = rgba.Pix[i*4+2]
|
||||
pd.Pix[i].A = rgba.Pix[i*4+3]
|
||||
}
|
||||
|
||||
return pd
|
||||
|
@ -222,33 +220,33 @@ func PictureDataFromPicture(pic Picture) *PictureData {
|
|||
return pd
|
||||
}
|
||||
|
||||
// Image converts PictureData into an image.NRGBA.
|
||||
// Image converts PictureData into an image.RGBA.
|
||||
//
|
||||
// The resulting image.NRGBA's Bounds will be equivalent of the PictureData's Bounds.
|
||||
func (pd *PictureData) Image() *image.NRGBA {
|
||||
// The resulting image.RGBA's Bounds will be equivalent of the PictureData's Bounds.
|
||||
func (pd *PictureData) Image() *image.RGBA {
|
||||
bounds := image.Rect(
|
||||
int(math.Floor(pd.Rect.Min.X())),
|
||||
int(math.Floor(pd.Rect.Min.Y())),
|
||||
int(math.Ceil(pd.Rect.Max.X())),
|
||||
int(math.Ceil(pd.Rect.Max.Y())),
|
||||
)
|
||||
nrgba := image.NewNRGBA(bounds)
|
||||
rgba := image.NewRGBA(bounds)
|
||||
|
||||
i := 0
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
off := pd.Index(V(float64(x), float64(y)))
|
||||
nrgba.Pix[i*4+0] = pd.Pix[off].R
|
||||
nrgba.Pix[i*4+1] = pd.Pix[off].G
|
||||
nrgba.Pix[i*4+2] = pd.Pix[off].B
|
||||
nrgba.Pix[i*4+3] = pd.Pix[off].A
|
||||
rgba.Pix[i*4+0] = pd.Pix[off].R
|
||||
rgba.Pix[i*4+1] = pd.Pix[off].G
|
||||
rgba.Pix[i*4+2] = pd.Pix[off].B
|
||||
rgba.Pix[i*4+3] = pd.Pix[off].A
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
verticalFlip(nrgba)
|
||||
verticalFlip(rgba)
|
||||
|
||||
return nrgba
|
||||
return rgba
|
||||
}
|
||||
|
||||
// Index returns the index of the pixel at the specified position inside the Pix slice.
|
||||
|
@ -263,28 +261,12 @@ func (pd *PictureData) Bounds() Rect {
|
|||
return pd.Rect
|
||||
}
|
||||
|
||||
// Slice returns a sub-Picture of this PictureData inside the supplied rectangle.
|
||||
func (pd *PictureData) Slice(r Rect) Picture {
|
||||
return &PictureData{
|
||||
Pix: pd.Pix[pd.Index(r.Min):],
|
||||
Stride: pd.Stride,
|
||||
Rect: r,
|
||||
Orig: pd.Orig,
|
||||
}
|
||||
}
|
||||
|
||||
// Original returns the most original PictureData that this PictureData was obtained from using
|
||||
// Slice-ing.
|
||||
func (pd *PictureData) Original() Picture {
|
||||
return pd.Orig
|
||||
}
|
||||
|
||||
// Color returns the color located at the given position.
|
||||
func (pd *PictureData) Color(at Vec) NRGBA {
|
||||
func (pd *PictureData) Color(at Vec) RGBA {
|
||||
if !pd.Rect.Contains(at) {
|
||||
return NRGBA{0, 0, 0, 0}
|
||||
return RGBA{0, 0, 0, 0}
|
||||
}
|
||||
return ToNRGBA(pd.Pix[pd.Index(at)])
|
||||
return ToRGBA(pd.Pix[pd.Index(at)])
|
||||
}
|
||||
|
||||
// SetColor changes the color located at the given position.
|
||||
|
@ -292,11 +274,11 @@ func (pd *PictureData) SetColor(at Vec, col color.Color) {
|
|||
if !pd.Rect.Contains(at) {
|
||||
return
|
||||
}
|
||||
nrgba := ToNRGBA(col)
|
||||
pd.Pix[pd.Index(at)] = color.NRGBA{
|
||||
R: uint8(nrgba.R * 255),
|
||||
G: uint8(nrgba.G * 255),
|
||||
B: uint8(nrgba.B * 255),
|
||||
A: uint8(nrgba.A * 255),
|
||||
rgba := ToRGBA(col)
|
||||
pd.Pix[pd.Index(at)] = color.RGBA{
|
||||
R: uint8(rgba.R * 255),
|
||||
G: uint8(rgba.G * 255),
|
||||
B: uint8(rgba.B * 255),
|
||||
A: uint8(rgba.A * 255),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Package pixel implements platform and backend agnostic core of the Pixel game development
|
||||
// library.
|
||||
//
|
||||
// It specifies the core Target, Triangles, Picture pattern and implements standard elements, such
|
||||
// as Sprite, Batch, Vec, Matrix and RGBA in addition to the basic Triangles and Picture
|
||||
// implementations: TrianglesData and PictureData.
|
||||
package pixel
|
|
@ -14,7 +14,7 @@ package pixel
|
|||
// Picture.
|
||||
//
|
||||
// Whenever you change the Triangles, call Dirty to notify Drawer that Triangles changed. You don't
|
||||
// need to notify Drawer about a change of Picture.
|
||||
// need to notify Drawer about a change of the Picture.
|
||||
type Drawer struct {
|
||||
Triangles Triangles
|
||||
Picture Picture
|
||||
|
@ -78,12 +78,11 @@ func (d *Drawer) Draw(t Target) {
|
|||
return
|
||||
}
|
||||
|
||||
pic := d.pics[targetPicturePair{t, d.Picture.Original()}]
|
||||
pic := d.pics[targetPicturePair{t, d.Picture}]
|
||||
if pic == nil {
|
||||
pic = t.MakePicture(d.Picture.Original())
|
||||
d.pics[targetPicturePair{t, d.Picture.Original()}] = pic
|
||||
pic = t.MakePicture(d.Picture)
|
||||
d.pics[targetPicturePair{t, d.Picture}] = pic
|
||||
}
|
||||
pic = pic.Slice(d.Picture.Bounds()).(TargetPicture)
|
||||
|
||||
pic.Draw(tri)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/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)
|
||||
}
|
After Width: | Height: | Size: 68 KiB |
|
@ -0,0 +1,57 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/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.SetMatrix(pixel.IM.Moved(win.Bounds().Center()))
|
||||
sprite.Draw(win)
|
||||
|
||||
for !win.Closed() {
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 68 KiB |
|
@ -0,0 +1,71 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/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(0, angle)
|
||||
mat = mat.Moved(win.Bounds().Center())
|
||||
sprite.SetMatrix(mat)
|
||||
sprite.Draw(win)
|
||||
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/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.V(0, 0)
|
||||
camSpeed = 500.0
|
||||
camZoom = 1.0
|
||||
camZoomSpeed = 1.2
|
||||
trees []*pixel.Sprite
|
||||
)
|
||||
|
||||
last := time.Now()
|
||||
for !win.Closed() {
|
||||
dt := time.Since(last).Seconds()
|
||||
last = time.Now()
|
||||
|
||||
cam := pixel.IM.Scaled(camPos, camZoom).Moved(win.Bounds().Center() - camPos)
|
||||
win.SetMatrix(cam)
|
||||
|
||||
if win.JustPressed(pixelgl.MouseButtonLeft) {
|
||||
tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
|
||||
mouse := cam.Unproject(win.MousePosition())
|
||||
tree.SetMatrix(pixel.IM.Scaled(0, 4).Moved(mouse))
|
||||
trees = append(trees, tree)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyLeft) {
|
||||
camPos -= pixel.X(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyRight) {
|
||||
camPos += pixel.X(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyDown) {
|
||||
camPos -= pixel.Y(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyUp) {
|
||||
camPos += pixel.Y(camSpeed * dt)
|
||||
}
|
||||
camZoom *= math.Pow(camZoomSpeed, win.MouseScroll().Y())
|
||||
|
||||
win.Clear(colornames.Forestgreen)
|
||||
|
||||
for _, tree := range trees {
|
||||
tree.Draw(win)
|
||||
}
|
||||
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,111 @@
|
|||
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"
|
||||
)
|
||||
|
||||
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.V(0, 0)
|
||||
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() - camPos)
|
||||
win.SetMatrix(cam)
|
||||
|
||||
if win.Pressed(pixelgl.MouseButtonLeft) {
|
||||
tree := pixel.NewSprite(spritesheet, treesFrames[rand.Intn(len(treesFrames))])
|
||||
mouse := cam.Unproject(win.MousePosition())
|
||||
tree.SetMatrix(pixel.IM.Scaled(0, 4).Moved(mouse))
|
||||
tree.Draw(batch)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyLeft) {
|
||||
camPos -= pixel.X(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyRight) {
|
||||
camPos += pixel.X(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyDown) {
|
||||
camPos -= pixel.Y(camSpeed * dt)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyUp) {
|
||||
camPos += pixel.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)
|
||||
}
|
After Width: | Height: | Size: 2.5 KiB |
|
@ -0,0 +1,53 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/pixel/imdraw"
|
||||
"github.com/faiface/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)
|
||||
}
|
|
@ -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).
|
||||
|
||||

|
|
@ -0,0 +1,201 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"image"
|
||||
"math"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/pixel/imdraw"
|
||||
"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
|
||||
}
|
||||
|
||||
type drawer interface {
|
||||
Draw(pixel.Target)
|
||||
}
|
||||
|
||||
type colorlight struct {
|
||||
color pixel.RGBA
|
||||
point pixel.Vec
|
||||
angle float64
|
||||
radius float64
|
||||
dust float64
|
||||
|
||||
spread float64
|
||||
|
||||
imd *imdraw.IMDraw
|
||||
}
|
||||
|
||||
func (cl *colorlight) apply(src, noise drawer, dst pixel.ComposeTarget) {
|
||||
// create the light arc if not created already
|
||||
if cl.imd == nil {
|
||||
imd := imdraw.New(nil)
|
||||
imd.Color(pixel.Alpha(1))
|
||||
imd.Push(0)
|
||||
imd.Color(pixel.Alpha(0))
|
||||
for angle := -cl.spread / 2; angle <= cl.spread/2; angle += cl.spread / 64 {
|
||||
imd.Push(pixel.X(1).Rotated(angle))
|
||||
}
|
||||
imd.Polygon(0)
|
||||
cl.imd = imd
|
||||
}
|
||||
|
||||
// draw the light arc
|
||||
dst.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, 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)
|
||||
|
||||
// draw an image inside the noisy light
|
||||
dst.SetColorMask(cl.color)
|
||||
dst.SetComposeMethod(pixel.ComposeIn)
|
||||
src.Draw(dst)
|
||||
|
||||
// draw the light reflected from the dust
|
||||
dst.SetMatrix(pixel.IM.Scaled(0, cl.radius).Rotated(0, 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())
|
||||
panda.SetMatrix(pixel.IM.Moved(win.Bounds().Center()))
|
||||
noise := pixel.NewSprite(noisePic, noisePic.Bounds())
|
||||
noise.SetMatrix(pixel.IM.Moved(win.Bounds().Center()))
|
||||
|
||||
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{
|
||||
pixel.V(win.Bounds().Min.X(), win.Bounds().Min.Y()),
|
||||
pixel.V(win.Bounds().Max.X(), win.Bounds().Min.Y()),
|
||||
pixel.V(win.Bounds().Max.X(), win.Bounds().Max.Y()),
|
||||
pixel.V(win.Bounds().Min.X(), 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)
|
||||
|
||||
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(panda, noise, oneLight)
|
||||
oneLight.Draw(allLight)
|
||||
}
|
||||
|
||||
// compose the final result
|
||||
win.SetColorMask(pixel.Alpha(1))
|
||||
allLight.Draw(win)
|
||||
|
||||
win.Update()
|
||||
|
||||
<-fps30 // maintain 30 fps, because my computer couldn't handle 60 here
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 838 KiB |
After Width: | Height: | Size: 1.2 MiB |
After Width: | Height: | Size: 1.1 MiB |
|
@ -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).
|
||||
|
||||

|
|
@ -0,0 +1,395 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"image"
|
||||
"image/color"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/pixel/imdraw"
|
||||
"github.com/faiface/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 = gp.vel.WithX(-gp.runSpeed)
|
||||
case ctrl.X() > 0:
|
||||
gp.vel = gp.vel.WithX(+gp.runSpeed)
|
||||
default:
|
||||
gp.vel = gp.vel.WithX(0)
|
||||
}
|
||||
|
||||
// apply gravity and velocity
|
||||
gp.vel += pixel.Y(gp.gravity).Scaled(dt)
|
||||
gp.rect = gp.rect.Moved(gp.vel.Scaled(dt))
|
||||
|
||||
// check collisions agains 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 = gp.vel.WithY(0)
|
||||
gp.rect = gp.rect.Moved(pixel.Y(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 = gp.vel.WithY(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 positon and direction
|
||||
ga.sprite.Set(ga.sheet, ga.frame)
|
||||
ga.sprite.SetMatrix(pixel.IM.
|
||||
ScaledXY(0, pixel.V(
|
||||
phys.rect.W()/ga.sprite.Frame().W(),
|
||||
phys.rect.H()/ga.sprite.Frame().H(),
|
||||
)).
|
||||
ScaledXY(0, pixel.V(-ga.dir, 1)).
|
||||
Moved(phys.rect.Center()),
|
||||
)
|
||||
ga.sprite.Draw(t)
|
||||
}
|
||||
|
||||
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.V(0, 0)
|
||||
|
||||
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)
|
||||
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())
|
||||
phys.vel = 0
|
||||
}
|
||||
|
||||
// control the gopher with keys
|
||||
ctrl := pixel.V(0, 0)
|
||||
if win.Pressed(pixelgl.KeyLeft) {
|
||||
ctrl -= pixel.X(1)
|
||||
}
|
||||
if win.Pressed(pixelgl.KeyRight) {
|
||||
ctrl += pixel.X(1)
|
||||
}
|
||||
if win.JustPressed(pixelgl.KeyUp) {
|
||||
ctrl = ctrl.WithY(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(0,
|
||||
math.Min(
|
||||
win.Bounds().W()/canvas.Bounds().W(),
|
||||
win.Bounds().H()/canvas.Bounds().H(),
|
||||
),
|
||||
).Moved(win.Bounds().Center()))
|
||||
canvas.Draw(win)
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 8.3 KiB |
|
@ -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
|
|
After Width: | Height: | Size: 530 B |
|
@ -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/).
|
||||
|
||||

|
|
@ -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
|
|
After Width: | Height: | Size: 3.0 MiB |
|
@ -0,0 +1,226 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"encoding/csv"
|
||||
"image"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
_ "image/png"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/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.SetMatrix(pixel.IM.
|
||||
Scaled(0, part.Scale).
|
||||
Rotated(0, part.Rot).
|
||||
Moved(part.Pos),
|
||||
)
|
||||
part.Sprite.SetColorMask(part.Mask)
|
||||
part.Sprite.Draw(t)
|
||||
}
|
||||
}
|
||||
|
||||
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 += 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 += 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: 0,
|
||||
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)
|
||||
win.SetMatrix(pixel.IM.Moved(win.Bounds().Center() - pixel.Y(win.Bounds().H()/2)))
|
||||
|
||||
batch.Clear()
|
||||
p.DrawAll(batch)
|
||||
batch.Draw(win)
|
||||
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 381 KiB |
|
@ -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.
|
||||
|
||||

|
|
@ -0,0 +1,76 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/faiface/pixel"
|
||||
"github.com/faiface/pixel/imdraw"
|
||||
"github.com/faiface/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() - pixel.X(offset))
|
||||
imd.Circle(200, 0)
|
||||
imd.Draw(canvas)
|
||||
|
||||
// blue circle
|
||||
imd.Clear()
|
||||
imd.Color(pixel.RGB(0, 0, 1))
|
||||
imd.Push(win.Bounds().Center() + pixel.X(offset))
|
||||
imd.Circle(150, 0)
|
||||
imd.Draw(canvas)
|
||||
|
||||
// yellow circle
|
||||
imd.Clear()
|
||||
imd.Color(pixel.RGB(1, 1, 0))
|
||||
imd.Push(win.Bounds().Center() - pixel.Y(offset))
|
||||
imd.Circle(100, 0)
|
||||
imd.Draw(canvas)
|
||||
|
||||
// magenta circle
|
||||
imd.Clear()
|
||||
imd.Color(pixel.RGB(1, 0, 1))
|
||||
imd.Push(win.Bounds().Center() + pixel.Y(offset))
|
||||
imd.Circle(50, 0)
|
||||
imd.Draw(canvas)
|
||||
|
||||
win.Clear(colornames.Green)
|
||||
canvas.Draw(win)
|
||||
win.Update()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
pixelgl.Run(run)
|
||||
}
|
After Width: | Height: | Size: 10 KiB |
35
geometry.go
|
@ -78,7 +78,7 @@ func (u Vec) Len() float64 {
|
|||
return cmplx.Abs(complex128(u))
|
||||
}
|
||||
|
||||
// Angle returns the angle between the vector u and the x-axis. The result is in the range [-Pi, Pi].
|
||||
// Angle returns the angle between the vector u and the x-axis. The result is in range [-Pi, Pi].
|
||||
func (u Vec) Angle() float64 {
|
||||
return cmplx.Phase(complex128(u))
|
||||
}
|
||||
|
@ -165,6 +165,15 @@ func R(minX, minY, maxX, maxY float64) Rect {
|
|||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of the Rect.
|
||||
//
|
||||
// r := pixel.R(100, 50, 200, 300)
|
||||
// r.String() // returns "Rect(100, 50, 200, 300)"
|
||||
// fmt.Println(r) // Rect(100, 50, 200, 300)
|
||||
func (r Rect) String() string {
|
||||
return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X(), r.Min.Y(), r.Max.X(), r.Max.Y())
|
||||
}
|
||||
|
||||
// Norm returns the Rect in normal form, such that Max is component-wise greater or equal than Min.
|
||||
func (r Rect) Norm() Rect {
|
||||
return Rect{
|
||||
|
@ -179,15 +188,6 @@ func (r Rect) Norm() Rect {
|
|||
}
|
||||
}
|
||||
|
||||
// String returns the string representation of the Rect.
|
||||
//
|
||||
// r := pixel.R(100, 50, 200, 300)
|
||||
// r.String() // returns "Rect(100, 50, 200, 300)"
|
||||
// fmt.Println(r) // Rect(100, 50, 200, 300)
|
||||
func (r Rect) String() string {
|
||||
return fmt.Sprintf("Rect(%v, %v, %v, %v)", r.Min.X(), r.Min.Y(), r.Max.X(), r.Max.Y())
|
||||
}
|
||||
|
||||
// W returns the width of the Rect.
|
||||
func (r Rect) W() float64 {
|
||||
return r.Max.X() - r.Min.X()
|
||||
|
@ -249,7 +249,7 @@ func (r Rect) Resized(anchor, size Vec) Rect {
|
|||
if r.W()*r.H() == 0 || size.X()*size.Y() == 0 {
|
||||
panic(fmt.Errorf("(%T).Resize: zero area", r))
|
||||
}
|
||||
fraction := size.ScaledXY(V(1/r.W(), 1/r.H()))
|
||||
fraction := V(size.X()/r.W(), size.Y()/r.H())
|
||||
return Rect{
|
||||
Min: anchor + (r.Min - anchor).ScaledXY(fraction),
|
||||
Max: anchor + (r.Max - anchor).ScaledXY(fraction),
|
||||
|
@ -287,6 +287,19 @@ type Matrix [9]float64
|
|||
// IM stands for identity matrix. Does nothing, no transformation.
|
||||
var IM = Matrix(mgl64.Ident3())
|
||||
|
||||
// String returns a string representation of the Matrix.
|
||||
//
|
||||
// m := pixel.IM
|
||||
// fmt.Println(m) // Matrix(1 0 0 | 0 1 0 | 0 0 1)
|
||||
func (m Matrix) String() string {
|
||||
return fmt.Sprintf(
|
||||
"Matrix(%v %v %v | %v %v %v | %v %v %v)",
|
||||
m[0], m[3], m[6],
|
||||
m[1], m[4], m[7],
|
||||
m[2], m[5], m[8],
|
||||
)
|
||||
}
|
||||
|
||||
// Moved moves everything by the delta vector.
|
||||
func (m Matrix) Moved(delta Vec) Matrix {
|
||||
m3 := mgl64.Mat3(m)
|
||||
|
|
116
imdraw/imdraw.go
|
@ -1,3 +1,5 @@
|
|||
// Package imdraw implements a basic primitive geometry shape and pictured polygon drawing for Pixel
|
||||
// with a nice immediate-mode-like API.
|
||||
package imdraw
|
||||
|
||||
import (
|
||||
|
@ -7,7 +9,7 @@ import (
|
|||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
// IMDraw is an immediate-like-mode shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
|
||||
// IMDraw is an immediate-mode-like shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
|
||||
// TrianglesColor, TrianglesPicture and PictureColor.
|
||||
//
|
||||
// IMDraw, other than a regular BasicTarget, is used to draw shapes. To draw shapes, you first need
|
||||
|
@ -23,7 +25,7 @@ import (
|
|||
//
|
||||
// Use various methods to change properties of Pushed points:
|
||||
//
|
||||
// imd.Color(pixel.NRGBA{R: 1, G: 0, B: 0, A: 1})
|
||||
// imd.Color(pixel.RGB(1, 0, 0))
|
||||
// imd.Push(pixel.V(200, 200))
|
||||
// imd.Circle(400, 0)
|
||||
//
|
||||
|
@ -46,7 +48,7 @@ type IMDraw struct {
|
|||
points []point
|
||||
opts point
|
||||
matrix pixel.Matrix
|
||||
mask pixel.NRGBA
|
||||
mask pixel.RGBA
|
||||
|
||||
tri *pixel.TrianglesData
|
||||
batch *pixel.Batch
|
||||
|
@ -56,7 +58,7 @@ var _ pixel.BasicTarget = (*IMDraw)(nil)
|
|||
|
||||
type point struct {
|
||||
pos pixel.Vec
|
||||
col pixel.NRGBA
|
||||
col pixel.RGBA
|
||||
pic pixel.Vec
|
||||
in float64
|
||||
precision int
|
||||
|
@ -87,7 +89,7 @@ func New(pic pixel.Picture) *IMDraw {
|
|||
batch: pixel.NewBatch(tri, pic),
|
||||
}
|
||||
im.SetMatrix(pixel.IM)
|
||||
im.SetColorMask(pixel.NRGBA{R: 1, G: 1, B: 1, A: 1})
|
||||
im.SetColorMask(pixel.Alpha(1))
|
||||
im.Reset()
|
||||
return im
|
||||
}
|
||||
|
@ -108,6 +110,8 @@ func (imd *IMDraw) Reset() {
|
|||
}
|
||||
|
||||
// Draw draws all currently drawn shapes inside the IM onto another Target.
|
||||
//
|
||||
// Note, that IMDraw's matrix and color mask have no effect here.
|
||||
func (imd *IMDraw) Draw(t pixel.Target) {
|
||||
imd.batch.Draw(t)
|
||||
}
|
||||
|
@ -127,7 +131,7 @@ func (imd *IMDraw) pushPt(pos pixel.Vec, pt point) {
|
|||
|
||||
// Color sets the color of the next Pushed points.
|
||||
func (imd *IMDraw) Color(color color.Color) {
|
||||
imd.opts.col = pixel.ToNRGBA(color)
|
||||
imd.opts.col = pixel.ToRGBA(color)
|
||||
}
|
||||
|
||||
// Picture sets the Picture coordinates of the next Pushed points.
|
||||
|
@ -160,7 +164,7 @@ func (imd *IMDraw) SetMatrix(m pixel.Matrix) {
|
|||
|
||||
// SetColorMask sets a color that all further point's color will be multiplied by.
|
||||
func (imd *IMDraw) SetColorMask(color color.Color) {
|
||||
imd.mask = pixel.ToNRGBA(color)
|
||||
imd.mask = pixel.ToRGBA(color)
|
||||
imd.batch.SetColorMask(imd.mask)
|
||||
}
|
||||
|
||||
|
@ -179,6 +183,20 @@ func (imd *IMDraw) Line(thickness float64) {
|
|||
imd.polyline(thickness, false)
|
||||
}
|
||||
|
||||
// Rectangle draws a rectangle between each two subsequent Pushed points. Drawing a rectangle
|
||||
// between two points means drawing a rectangle with sides parallel to the axes of the coordinate
|
||||
// system, where the two points specify it's two opposite corners.
|
||||
//
|
||||
// If the thickness is 0, rectangles will be filled, otherwise will be outlined with the given
|
||||
// thickness.
|
||||
func (imd *IMDraw) Rectangle(thickness float64) {
|
||||
if thickness == 0 {
|
||||
imd.fillRectangle()
|
||||
} else {
|
||||
imd.outlineRectangle(thickness)
|
||||
}
|
||||
}
|
||||
|
||||
// Polygon draws a polygon from the Pushed points. If the thickness is 0, the convex polygon will be
|
||||
// filled. Otherwise, an outline of the specified thickness will be drawn. The outline does not have
|
||||
// to be convex.
|
||||
|
@ -260,6 +278,64 @@ func (imd *IMDraw) applyMatrixAndMask(off int) {
|
|||
}
|
||||
}
|
||||
|
||||
func (imd *IMDraw) fillRectangle() {
|
||||
points := imd.getAndClearPoints()
|
||||
|
||||
if len(points) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
off := imd.tri.Len()
|
||||
imd.tri.SetLen(imd.tri.Len() + 6*(len(points)-1))
|
||||
|
||||
for i, j := 0, off; i+1 < len(points); i, j = i+1, j+6 {
|
||||
a, b := points[i], points[i+1]
|
||||
c := point{
|
||||
pos: pixel.V(a.pos.X(), b.pos.Y()),
|
||||
col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
|
||||
pic: pixel.V(a.pic.X(), b.pic.Y()),
|
||||
in: (a.in + b.in) / 2,
|
||||
}
|
||||
d := point{
|
||||
pos: pixel.V(b.pos.X(), a.pos.Y()),
|
||||
col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
|
||||
pic: pixel.V(b.pic.X(), a.pic.Y()),
|
||||
in: (a.in + b.in) / 2,
|
||||
}
|
||||
|
||||
for k, p := range []point{a, b, c, a, b, d} {
|
||||
(*imd.tri)[j+k].Position = p.pos
|
||||
(*imd.tri)[j+k].Color = p.col
|
||||
(*imd.tri)[j+k].Picture = p.pic
|
||||
(*imd.tri)[j+k].Intensity = p.in
|
||||
}
|
||||
}
|
||||
|
||||
imd.applyMatrixAndMask(off)
|
||||
imd.batch.Dirty()
|
||||
}
|
||||
|
||||
func (imd *IMDraw) outlineRectangle(thickness float64) {
|
||||
points := imd.getAndClearPoints()
|
||||
|
||||
if len(points) < 2 {
|
||||
return
|
||||
}
|
||||
|
||||
for i := 0; i+1 < len(points); i++ {
|
||||
a, b := points[i], points[i+1]
|
||||
mid := a
|
||||
mid.col = a.col.Add(b.col).Mul(pixel.Alpha(0.5))
|
||||
mid.in = (a.in + b.in) / 2
|
||||
|
||||
imd.pushPt(a.pos, a)
|
||||
imd.pushPt(pixel.V(a.pos.X(), b.pos.Y()), mid)
|
||||
imd.pushPt(b.pos, b)
|
||||
imd.pushPt(pixel.V(b.pos.X(), a.pos.Y()), mid)
|
||||
imd.polyline(thickness, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (imd *IMDraw) fillPolygon() {
|
||||
points := imd.getAndClearPoints()
|
||||
|
||||
|
@ -271,20 +347,12 @@ func (imd *IMDraw) fillPolygon() {
|
|||
imd.tri.SetLen(imd.tri.Len() + 3*(len(points)-2))
|
||||
|
||||
for i, j := 1, off; i+1 < len(points); i, j = i+1, j+3 {
|
||||
(*imd.tri)[j+0].Position = points[0].pos
|
||||
(*imd.tri)[j+0].Color = points[0].col
|
||||
(*imd.tri)[j+0].Picture = points[0].pic
|
||||
(*imd.tri)[j+0].Intensity = points[0].in
|
||||
|
||||
(*imd.tri)[j+1].Position = points[i].pos
|
||||
(*imd.tri)[j+1].Color = points[i].col
|
||||
(*imd.tri)[j+1].Picture = points[i].pic
|
||||
(*imd.tri)[j+1].Intensity = points[i].in
|
||||
|
||||
(*imd.tri)[j+2].Position = points[i+1].pos
|
||||
(*imd.tri)[j+2].Color = points[i+1].col
|
||||
(*imd.tri)[j+2].Picture = points[i+1].pic
|
||||
(*imd.tri)[j+2].Intensity = points[i+1].in
|
||||
for k, p := range []point{points[0], points[i], points[i+1]} {
|
||||
(*imd.tri)[j+k].Position = p.pos
|
||||
(*imd.tri)[j+k].Color = p.col
|
||||
(*imd.tri)[j+k].Picture = p.pic
|
||||
(*imd.tri)[j+k].Intensity = p.in
|
||||
}
|
||||
}
|
||||
|
||||
imd.applyMatrixAndMask(off)
|
||||
|
@ -469,13 +537,13 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
|
|||
|
||||
closing := false
|
||||
if j >= len(points) {
|
||||
if !closed {
|
||||
break
|
||||
}
|
||||
j %= len(points)
|
||||
closing = true
|
||||
}
|
||||
if k >= len(points) {
|
||||
if !closed {
|
||||
break
|
||||
}
|
||||
k %= len(points)
|
||||
}
|
||||
|
||||
|
|
19
interface.go
|
@ -89,7 +89,7 @@ type TrianglesPosition interface {
|
|||
// TrianglesColor specifies Triangles with Color property.
|
||||
type TrianglesColor interface {
|
||||
Triangles
|
||||
Color(i int) NRGBA
|
||||
Color(i int) RGBA
|
||||
}
|
||||
|
||||
// TrianglesPicture specifies Triangles with Picture propery.
|
||||
|
@ -108,19 +108,6 @@ type Picture interface {
|
|||
// Bounds returns the rectangle of the Picture. All data is located witih this rectangle.
|
||||
// Querying properties outside the rectangle should return default value of that property.
|
||||
Bounds() Rect
|
||||
|
||||
// Slice returns a sub-Picture with specified Bounds.
|
||||
//
|
||||
// A result of Slice-ing outside the original Bounds is unspecified.
|
||||
Slice(Rect) Picture
|
||||
|
||||
// Original returns the most original Picture (may be itself) that this Picture was created
|
||||
// from using Slice-ing.
|
||||
//
|
||||
// Since the Original and this Picture should share the underlying data and this Picture can
|
||||
// be obtained just by slicing the Original, this method can be used for more efficient
|
||||
// caching of Pictures.
|
||||
Original() Picture
|
||||
}
|
||||
|
||||
// TargetPicture is a Picture generated by a Target using MakePicture method. This Picture can be drawn onto
|
||||
|
@ -139,8 +126,8 @@ type TargetPicture interface {
|
|||
// PictureColor specifies Picture with Color property, so that every position inside the Picture's
|
||||
// Bounds has a color.
|
||||
//
|
||||
// Positions outside the Picture's Bounds must return transparent black (NRGBA{R: 0, G: 0, B: 0, A: 0}).
|
||||
// Positions outside the Picture's Bounds must return full transparent (Alpha(0)).
|
||||
type PictureColor interface {
|
||||
Picture
|
||||
Color(at Vec) NRGBA
|
||||
Color(at Vec) RGBA
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package pixelgl
|
|||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"github.com/faiface/glhf"
|
||||
"github.com/faiface/mainthread"
|
||||
|
@ -17,35 +16,33 @@ import (
|
|||
//
|
||||
// It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor.
|
||||
type Canvas struct {
|
||||
// these should **only** be accessed through orig
|
||||
f *glhf.Frame
|
||||
borders pixel.Rect
|
||||
pixels []uint8
|
||||
dirty bool
|
||||
gf *GLFrame
|
||||
shader *glhf.Shader
|
||||
|
||||
// these should **never** be accessed through orig
|
||||
s *glhf.Shader
|
||||
bounds pixel.Rect
|
||||
cmp pixel.ComposeMethod
|
||||
mat mgl32.Mat3
|
||||
col mgl32.Vec4
|
||||
smooth bool
|
||||
|
||||
orig *Canvas
|
||||
sprite *pixel.Sprite
|
||||
}
|
||||
|
||||
// NewCanvas creates a new empty, fully transparent Canvas with given bounds. If the smooth flag is
|
||||
// set, then stretched Pictures will be smoothed and will not be drawn pixely onto this Canvas.
|
||||
func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas {
|
||||
c := &Canvas{
|
||||
smooth: smooth,
|
||||
mat: mgl32.Ident3(),
|
||||
col: mgl32.Vec4{1, 1, 1, 1},
|
||||
}
|
||||
c.orig = c
|
||||
var _ pixel.ComposeTarget = (*Canvas)(nil)
|
||||
|
||||
// NewCanvas creates a new empty, fully transparent Canvas with given bounds.
|
||||
func NewCanvas(bounds pixel.Rect) *Canvas {
|
||||
c := &Canvas{
|
||||
gf: NewGLFrame(bounds),
|
||||
mat: mgl32.Ident3(),
|
||||
col: mgl32.Vec4{1, 1, 1, 1},
|
||||
}
|
||||
|
||||
c.SetBounds(bounds)
|
||||
|
||||
var shader *glhf.Shader
|
||||
mainthread.Call(func() {
|
||||
var err error
|
||||
c.s, err = glhf.NewShader(
|
||||
shader, err = glhf.NewShader(
|
||||
canvasVertexFormat,
|
||||
canvasUniformFormat,
|
||||
canvasVertexShader,
|
||||
|
@ -55,8 +52,7 @@ func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas {
|
|||
panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader"))
|
||||
}
|
||||
})
|
||||
|
||||
c.SetBounds(bounds)
|
||||
c.shader = shader
|
||||
|
||||
return c
|
||||
}
|
||||
|
@ -66,7 +62,7 @@ func NewCanvas(bounds pixel.Rect, smooth bool) *Canvas {
|
|||
// TrianglesPosition, TrianglesColor and TrianglesPicture are supported.
|
||||
func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
|
||||
return &canvasTriangles{
|
||||
GLTriangles: NewGLTriangles(c.s, t),
|
||||
GLTriangles: NewGLTriangles(c.shader, t),
|
||||
dst: c,
|
||||
}
|
||||
}
|
||||
|
@ -75,80 +71,22 @@ func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
|
|||
//
|
||||
// PictureColor is supported.
|
||||
func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture {
|
||||
// short paths
|
||||
if cp, ok := p.(*canvasPicture); ok {
|
||||
tp := new(canvasPicture)
|
||||
*tp = *cp
|
||||
tp.dst = c
|
||||
return tp
|
||||
}
|
||||
if ccp, ok := p.(*canvasCanvasPicture); ok {
|
||||
tp := new(canvasCanvasPicture)
|
||||
*tp = *ccp
|
||||
tp.dst = c
|
||||
return tp
|
||||
}
|
||||
|
||||
// Canvas special case
|
||||
if canvas, ok := p.(*Canvas); ok {
|
||||
return &canvasCanvasPicture{
|
||||
src: canvas,
|
||||
dst: c,
|
||||
bounds: c.bounds,
|
||||
return &canvasPicture{
|
||||
GLPicture: cp.GLPicture,
|
||||
dst: c,
|
||||
}
|
||||
}
|
||||
|
||||
bounds := p.Bounds()
|
||||
bx, by, bw, bh := intBounds(bounds)
|
||||
|
||||
pixels := make([]uint8, 4*bw*bh)
|
||||
|
||||
if pd, ok := p.(*pixel.PictureData); ok {
|
||||
// PictureData short path
|
||||
for y := 0; y < bh; y++ {
|
||||
for x := 0; x < bw; x++ {
|
||||
nrgba := pd.Pix[y*pd.Stride+x]
|
||||
off := (y*bw + x) * 4
|
||||
pixels[off+0] = nrgba.R
|
||||
pixels[off+1] = nrgba.G
|
||||
pixels[off+2] = nrgba.B
|
||||
pixels[off+3] = nrgba.A
|
||||
}
|
||||
}
|
||||
} else if p, ok := p.(pixel.PictureColor); ok {
|
||||
for y := 0; y < bh; y++ {
|
||||
for x := 0; x < bw; x++ {
|
||||
at := pixel.V(
|
||||
math.Max(float64(bx+x), bounds.Min.X()),
|
||||
math.Max(float64(by+y), bounds.Min.Y()),
|
||||
)
|
||||
color := p.Color(at)
|
||||
off := (y*bw + x) * 4
|
||||
pixels[off+0] = uint8(color.R * 255)
|
||||
pixels[off+1] = uint8(color.G * 255)
|
||||
pixels[off+2] = uint8(color.B * 255)
|
||||
pixels[off+3] = uint8(color.A * 255)
|
||||
}
|
||||
if gp, ok := p.(GLPicture); ok {
|
||||
return &canvasPicture{
|
||||
GLPicture: gp,
|
||||
dst: c,
|
||||
}
|
||||
}
|
||||
|
||||
var tex *glhf.Texture
|
||||
mainthread.Call(func() {
|
||||
tex = glhf.NewTexture(bw, bh, c.smooth, pixels)
|
||||
})
|
||||
|
||||
cp := &canvasPicture{
|
||||
tex: tex,
|
||||
pixels: pixels,
|
||||
borders: pixel.R(
|
||||
float64(bx), float64(by),
|
||||
float64(bw), float64(bh),
|
||||
),
|
||||
bounds: bounds,
|
||||
dst: c,
|
||||
return &canvasPicture{
|
||||
GLPicture: NewGLPicture(p),
|
||||
dst: c,
|
||||
}
|
||||
cp.orig = cp
|
||||
return cp
|
||||
}
|
||||
|
||||
// SetMatrix sets a Matrix that every point will be projected by.
|
||||
|
@ -160,58 +98,37 @@ func (c *Canvas) SetMatrix(m pixel.Matrix) {
|
|||
|
||||
// SetColorMask sets a color that every color in triangles or a picture will be multiplied by.
|
||||
func (c *Canvas) SetColorMask(col color.Color) {
|
||||
nrgba := pixel.NRGBA{R: 1, G: 1, B: 1, A: 1}
|
||||
rgba := pixel.Alpha(1)
|
||||
if col != nil {
|
||||
nrgba = pixel.ToNRGBA(col)
|
||||
rgba = pixel.ToRGBA(col)
|
||||
}
|
||||
c.col = mgl32.Vec4{
|
||||
float32(nrgba.R),
|
||||
float32(nrgba.G),
|
||||
float32(nrgba.B),
|
||||
float32(nrgba.A),
|
||||
float32(rgba.R),
|
||||
float32(rgba.G),
|
||||
float32(rgba.B),
|
||||
float32(rgba.A),
|
||||
}
|
||||
}
|
||||
|
||||
// SetComposeMethod sets a Porter-Duff composition method to be used in the following draws onto
|
||||
// this Canvas.
|
||||
func (c *Canvas) SetComposeMethod(cmp pixel.ComposeMethod) {
|
||||
c.cmp = cmp
|
||||
}
|
||||
|
||||
// SetBounds resizes the Canvas to the new bounds. Old content will be preserved.
|
||||
//
|
||||
// If the new Bounds fit into the Original borders, no new Canvas will be allocated.
|
||||
func (c *Canvas) SetBounds(bounds pixel.Rect) {
|
||||
c.bounds = bounds
|
||||
|
||||
// if this bounds fit into the original bounds, no need to reallocate
|
||||
if c.orig.borders.Contains(bounds.Min) && c.orig.borders.Contains(bounds.Max) {
|
||||
return
|
||||
c.gf.SetBounds(bounds)
|
||||
if c.sprite == nil {
|
||||
c.sprite = pixel.NewSprite(nil, pixel.Rect{})
|
||||
}
|
||||
|
||||
mainthread.Call(func() {
|
||||
oldF := c.orig.f
|
||||
|
||||
_, _, w, h := intBounds(bounds)
|
||||
c.f = glhf.NewFrame(w, h, c.smooth)
|
||||
|
||||
// preserve old content
|
||||
if oldF != nil {
|
||||
relBounds := bounds
|
||||
relBounds = relBounds.Moved(-c.orig.borders.Min)
|
||||
ox, oy, ow, oh := intBounds(relBounds)
|
||||
oldF.Blit(
|
||||
c.f,
|
||||
ox, oy, ox+ow, oy+oh,
|
||||
ox, oy, ox+ow, oy+oh,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// detach from orig
|
||||
c.borders = bounds
|
||||
c.pixels = nil
|
||||
c.dirty = true
|
||||
c.orig = c
|
||||
c.sprite.Set(c, c.Bounds())
|
||||
c.sprite.SetMatrix(pixel.IM.Moved(c.Bounds().Center()))
|
||||
}
|
||||
|
||||
// Bounds returns the rectangular bounds of the Canvas.
|
||||
func (c *Canvas) Bounds() pixel.Rect {
|
||||
return c.bounds
|
||||
return c.gf.Bounds()
|
||||
}
|
||||
|
||||
// SetSmooth sets whether stretched Pictures drawn onto this Canvas should be drawn smooth or
|
||||
|
@ -228,20 +145,48 @@ func (c *Canvas) Smooth() bool {
|
|||
|
||||
// must be manually called inside mainthread
|
||||
func (c *Canvas) setGlhfBounds() {
|
||||
bounds := c.bounds
|
||||
bounds.Moved(c.orig.borders.Min)
|
||||
bx, by, bw, bh := intBounds(bounds)
|
||||
glhf.Bounds(bx, by, bw, bh)
|
||||
_, _, bw, bh := intBounds(c.gf.Bounds())
|
||||
glhf.Bounds(0, 0, bw, bh)
|
||||
}
|
||||
|
||||
// must be manually called inside mainthread
|
||||
func setBlendFunc(cmp pixel.ComposeMethod) {
|
||||
switch cmp {
|
||||
case pixel.ComposeOver:
|
||||
glhf.BlendFunc(glhf.One, glhf.OneMinusSrcAlpha)
|
||||
case pixel.ComposeIn:
|
||||
glhf.BlendFunc(glhf.DstAlpha, glhf.Zero)
|
||||
case pixel.ComposeOut:
|
||||
glhf.BlendFunc(glhf.OneMinusDstAlpha, glhf.Zero)
|
||||
case pixel.ComposeAtop:
|
||||
glhf.BlendFunc(glhf.DstAlpha, glhf.OneMinusSrcAlpha)
|
||||
case pixel.ComposeRover:
|
||||
glhf.BlendFunc(glhf.OneMinusDstAlpha, glhf.One)
|
||||
case pixel.ComposeRin:
|
||||
glhf.BlendFunc(glhf.Zero, glhf.SrcAlpha)
|
||||
case pixel.ComposeRout:
|
||||
glhf.BlendFunc(glhf.Zero, glhf.OneMinusSrcAlpha)
|
||||
case pixel.ComposeRatop:
|
||||
glhf.BlendFunc(glhf.OneMinusDstAlpha, glhf.SrcAlpha)
|
||||
case pixel.ComposeXor:
|
||||
glhf.BlendFunc(glhf.OneMinusDstAlpha, glhf.OneMinusSrcAlpha)
|
||||
case pixel.ComposePlus:
|
||||
glhf.BlendFunc(glhf.One, glhf.One)
|
||||
case pixel.ComposeCopy:
|
||||
glhf.BlendFunc(glhf.One, glhf.Zero)
|
||||
default:
|
||||
panic(errors.New("Canvas: invalid compose method"))
|
||||
}
|
||||
}
|
||||
|
||||
// Clear fills the whole Canvas with a single color.
|
||||
func (c *Canvas) Clear(color color.Color) {
|
||||
c.orig.dirty = true
|
||||
c.gf.Dirty()
|
||||
|
||||
nrgba := pixel.ToNRGBA(color)
|
||||
rgba := pixel.ToRGBA(color)
|
||||
|
||||
// color masking
|
||||
nrgba = nrgba.Mul(pixel.NRGBA{
|
||||
rgba = rgba.Mul(pixel.RGBA{
|
||||
R: float64(c.col[0]),
|
||||
G: float64(c.col[1]),
|
||||
B: float64(c.col[2]),
|
||||
|
@ -250,87 +195,70 @@ func (c *Canvas) Clear(color color.Color) {
|
|||
|
||||
mainthread.CallNonBlock(func() {
|
||||
c.setGlhfBounds()
|
||||
c.orig.f.Begin()
|
||||
c.gf.Frame().Begin()
|
||||
glhf.Clear(
|
||||
float32(nrgba.R),
|
||||
float32(nrgba.G),
|
||||
float32(nrgba.B),
|
||||
float32(nrgba.A),
|
||||
float32(rgba.R),
|
||||
float32(rgba.G),
|
||||
float32(rgba.B),
|
||||
float32(rgba.A),
|
||||
)
|
||||
c.orig.f.End()
|
||||
c.gf.Frame().End()
|
||||
})
|
||||
}
|
||||
|
||||
// Slice returns a sub-Canvas with the specified Bounds.
|
||||
//
|
||||
// The type of the returned value is *Canvas, the type of the return value is a general
|
||||
// pixel.Picture just so that Canvas implements pixel.Picture interface.
|
||||
func (c *Canvas) Slice(bounds pixel.Rect) pixel.Picture {
|
||||
sc := new(Canvas)
|
||||
*sc = *c
|
||||
sc.bounds = bounds
|
||||
return sc
|
||||
}
|
||||
|
||||
// Original returns the most original Canvas that this Canvas was created from using Slice-ing.
|
||||
//
|
||||
// The type of the returned value is *Canvas, the type of the return value is a general
|
||||
// pixel.Picture just so that Canvas implements pixel.Picture interface.
|
||||
func (c *Canvas) Original() pixel.Picture {
|
||||
return c.orig
|
||||
}
|
||||
|
||||
// Color returns the color of the pixel over the given position inside the Canvas.
|
||||
func (c *Canvas) Color(at pixel.Vec) pixel.NRGBA {
|
||||
if c.orig.dirty {
|
||||
mainthread.Call(func() {
|
||||
tex := c.orig.f.Texture()
|
||||
tex.Begin()
|
||||
c.orig.pixels = tex.Pixels(0, 0, tex.Width(), tex.Height())
|
||||
tex.End()
|
||||
})
|
||||
c.orig.dirty = false
|
||||
}
|
||||
if !c.bounds.Contains(at) {
|
||||
return pixel.NRGBA{}
|
||||
}
|
||||
bx, by, bw, _ := intBounds(c.orig.borders)
|
||||
x, y := int(at.X())-bx, int(at.Y())-by
|
||||
off := y*bw + x
|
||||
return pixel.NRGBA{
|
||||
R: float64(c.orig.pixels[off*4+0]) / 255,
|
||||
G: float64(c.orig.pixels[off*4+1]) / 255,
|
||||
B: float64(c.orig.pixels[off*4+2]) / 255,
|
||||
A: float64(c.orig.pixels[off*4+3]) / 255,
|
||||
}
|
||||
func (c *Canvas) Color(at pixel.Vec) pixel.RGBA {
|
||||
return c.gf.Color(at)
|
||||
}
|
||||
|
||||
// Texture returns the underlying OpenGL Texture of this Canvas.
|
||||
//
|
||||
// Implements GLPicture interface.
|
||||
func (c *Canvas) Texture() *glhf.Texture {
|
||||
return c.gf.Texture()
|
||||
}
|
||||
|
||||
// Draw draws a rectangle equal to Canvas's Bounds containing the Canvas's content to another
|
||||
// Target.
|
||||
//
|
||||
// Note, that the matrix and the color mask of this Canvas have no effect here.
|
||||
func (c *Canvas) Draw(t pixel.Target) {
|
||||
c.sprite.Draw(t)
|
||||
}
|
||||
|
||||
type canvasTriangles struct {
|
||||
*GLTriangles
|
||||
|
||||
dst *Canvas
|
||||
}
|
||||
|
||||
func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
|
||||
ct.dst.orig.dirty = true
|
||||
func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) {
|
||||
ct.dst.gf.Dirty()
|
||||
|
||||
// save the current state vars to avoid race condition
|
||||
cmp := ct.dst.cmp
|
||||
mat := ct.dst.mat
|
||||
col := ct.dst.col
|
||||
smt := ct.dst.smooth
|
||||
|
||||
mainthread.CallNonBlock(func() {
|
||||
ct.dst.setGlhfBounds()
|
||||
ct.dst.orig.f.Begin()
|
||||
ct.dst.s.Begin()
|
||||
setBlendFunc(cmp)
|
||||
|
||||
ct.dst.s.SetUniformAttr(canvasBounds, mgl32.Vec4{
|
||||
float32(ct.dst.bounds.Min.X()),
|
||||
float32(ct.dst.bounds.Min.Y()),
|
||||
float32(ct.dst.bounds.W()),
|
||||
float32(ct.dst.bounds.H()),
|
||||
frame := ct.dst.gf.Frame()
|
||||
shader := ct.dst.shader
|
||||
|
||||
frame.Begin()
|
||||
shader.Begin()
|
||||
|
||||
dstBounds := ct.dst.Bounds()
|
||||
shader.SetUniformAttr(canvasBounds, mgl32.Vec4{
|
||||
float32(dstBounds.Min.X()),
|
||||
float32(dstBounds.Min.Y()),
|
||||
float32(dstBounds.W()),
|
||||
float32(dstBounds.H()),
|
||||
})
|
||||
ct.dst.s.SetUniformAttr(canvasTransform, mat)
|
||||
ct.dst.s.SetUniformAttr(canvasColorMask, col)
|
||||
shader.SetUniformAttr(canvasTransform, mat)
|
||||
shader.SetUniformAttr(canvasColorMask, col)
|
||||
|
||||
if tex == nil {
|
||||
ct.vs.Begin()
|
||||
|
@ -339,21 +267,16 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
|
|||
} else {
|
||||
tex.Begin()
|
||||
|
||||
ct.dst.s.SetUniformAttr(canvasTexBorders, mgl32.Vec4{
|
||||
float32(borders.Min.X()),
|
||||
float32(borders.Min.Y()),
|
||||
float32(borders.W()),
|
||||
float32(borders.H()),
|
||||
})
|
||||
ct.dst.s.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
|
||||
float32(bounds.Min.X()),
|
||||
float32(bounds.Min.Y()),
|
||||
float32(bounds.W()),
|
||||
float32(bounds.H()),
|
||||
bx, by, bw, bh := intBounds(bounds)
|
||||
shader.SetUniformAttr(canvasTexBounds, mgl32.Vec4{
|
||||
float32(bx),
|
||||
float32(by),
|
||||
float32(bw),
|
||||
float32(bh),
|
||||
})
|
||||
|
||||
if tex.Smooth() != ct.dst.smooth {
|
||||
tex.SetSmooth(ct.dst.smooth)
|
||||
if tex.Smooth() != smt {
|
||||
tex.SetSmooth(smt)
|
||||
}
|
||||
|
||||
ct.vs.Begin()
|
||||
|
@ -363,53 +286,18 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, borders, bounds pixel.Rect) {
|
|||
tex.End()
|
||||
}
|
||||
|
||||
ct.dst.s.End()
|
||||
ct.dst.orig.f.End()
|
||||
shader.End()
|
||||
frame.End()
|
||||
})
|
||||
}
|
||||
|
||||
func (ct *canvasTriangles) Draw() {
|
||||
ct.draw(nil, pixel.Rect{}, pixel.Rect{})
|
||||
ct.draw(nil, pixel.Rect{})
|
||||
}
|
||||
|
||||
type canvasPicture struct {
|
||||
tex *glhf.Texture
|
||||
pixels []uint8
|
||||
borders pixel.Rect
|
||||
bounds pixel.Rect
|
||||
|
||||
orig *canvasPicture
|
||||
dst *Canvas
|
||||
}
|
||||
|
||||
func (cp *canvasPicture) Bounds() pixel.Rect {
|
||||
return cp.bounds
|
||||
}
|
||||
|
||||
func (cp *canvasPicture) Slice(r pixel.Rect) pixel.Picture {
|
||||
sp := new(canvasPicture)
|
||||
*sp = *cp
|
||||
sp.bounds = r
|
||||
return sp
|
||||
}
|
||||
|
||||
func (cp *canvasPicture) Original() pixel.Picture {
|
||||
return cp.orig
|
||||
}
|
||||
|
||||
func (cp *canvasPicture) Color(at pixel.Vec) pixel.NRGBA {
|
||||
if !cp.bounds.Contains(at) {
|
||||
return pixel.NRGBA{}
|
||||
}
|
||||
bx, by, bw, _ := intBounds(cp.borders)
|
||||
x, y := int(at.X())-bx, int(at.Y())-by
|
||||
off := y*bw + x
|
||||
return pixel.NRGBA{
|
||||
R: float64(cp.pixels[off*4+0]) / 255,
|
||||
G: float64(cp.pixels[off*4+1]) / 255,
|
||||
B: float64(cp.pixels[off*4+2]) / 255,
|
||||
A: float64(cp.pixels[off*4+3]) / 255,
|
||||
}
|
||||
GLPicture
|
||||
dst *Canvas
|
||||
}
|
||||
|
||||
func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
|
||||
|
@ -417,43 +305,7 @@ func (cp *canvasPicture) Draw(t pixel.TargetTriangles) {
|
|||
if cp.dst != ct.dst {
|
||||
panic(fmt.Errorf("(%T).Draw: TargetTriangles generated by different Canvas", cp))
|
||||
}
|
||||
ct.draw(cp.tex, cp.borders, cp.bounds)
|
||||
}
|
||||
|
||||
type canvasCanvasPicture struct {
|
||||
src, dst *Canvas
|
||||
bounds pixel.Rect
|
||||
orig *canvasCanvasPicture
|
||||
}
|
||||
|
||||
func (ccp *canvasCanvasPicture) Bounds() pixel.Rect {
|
||||
return ccp.bounds
|
||||
}
|
||||
|
||||
func (ccp *canvasCanvasPicture) Slice(r pixel.Rect) pixel.Picture {
|
||||
sp := new(canvasCanvasPicture)
|
||||
*sp = *ccp
|
||||
sp.bounds = r
|
||||
return sp
|
||||
}
|
||||
|
||||
func (ccp *canvasCanvasPicture) Original() pixel.Picture {
|
||||
return ccp.orig
|
||||
}
|
||||
|
||||
func (ccp *canvasCanvasPicture) Color(at pixel.Vec) pixel.NRGBA {
|
||||
if !ccp.bounds.Contains(at) {
|
||||
return pixel.NRGBA{}
|
||||
}
|
||||
return ccp.src.Color(at)
|
||||
}
|
||||
|
||||
func (ccp *canvasCanvasPicture) Draw(t pixel.TargetTriangles) {
|
||||
ct := t.(*canvasTriangles)
|
||||
if ccp.dst != ct.dst {
|
||||
panic(fmt.Errorf("(%T).Draw: TargetTriangles generated by different Canvas", ccp))
|
||||
}
|
||||
ct.draw(ccp.src.orig.f.Texture(), ccp.src.orig.borders, ccp.bounds)
|
||||
ct.draw(cp.GLPicture.Texture(), cp.GLPicture.Bounds())
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -474,16 +326,14 @@ const (
|
|||
canvasTransform int = iota
|
||||
canvasColorMask
|
||||
canvasBounds
|
||||
canvasTexBorders
|
||||
canvasTexBounds
|
||||
)
|
||||
|
||||
var canvasUniformFormat = glhf.AttrFormat{
|
||||
canvasTransform: {Name: "transform", Type: glhf.Mat3},
|
||||
canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
|
||||
canvasBounds: {Name: "bounds", Type: glhf.Vec4},
|
||||
canvasTexBorders: {Name: "texBorders", Type: glhf.Vec4},
|
||||
canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4},
|
||||
canvasTransform: {Name: "transform", Type: glhf.Mat3},
|
||||
canvasColorMask: {Name: "colorMask", Type: glhf.Vec4},
|
||||
canvasBounds: {Name: "bounds", Type: glhf.Vec4},
|
||||
canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4},
|
||||
}
|
||||
|
||||
var canvasVertexShader = `
|
||||
|
@ -499,12 +349,11 @@ out vec2 Texture;
|
|||
out float Intensity;
|
||||
|
||||
uniform mat3 transform;
|
||||
uniform vec4 borders;
|
||||
uniform vec4 bounds;
|
||||
|
||||
void main() {
|
||||
vec2 transPos = (transform * vec3(position, 1.0)).xy;
|
||||
vec2 normPos = (transPos - bounds.xy) / (bounds.zw) * 2 - vec2(1, 1);
|
||||
vec2 normPos = (transPos - bounds.xy) / bounds.zw * 2 - vec2(1, 1);
|
||||
gl_Position = vec4(normPos, 0.0, 1.0);
|
||||
Color = color;
|
||||
Texture = texture;
|
||||
|
@ -522,7 +371,6 @@ in float Intensity;
|
|||
out vec4 color;
|
||||
|
||||
uniform vec4 colorMask;
|
||||
uniform vec4 texBorders;
|
||||
uniform vec4 texBounds;
|
||||
uniform sampler2D tex;
|
||||
|
||||
|
@ -531,16 +379,10 @@ void main() {
|
|||
color = colorMask * Color;
|
||||
} else {
|
||||
color = vec4(0, 0, 0, 0);
|
||||
color += (1 - Intensity) * colorMask * Color;
|
||||
|
||||
float bx = texBounds.x;
|
||||
float by = texBounds.y;
|
||||
float bw = texBounds.z;
|
||||
float bh = texBounds.w;
|
||||
if (bx <= Texture.x && Texture.x <= bx + bw && by <= Texture.y && Texture.y <= by + bh) {
|
||||
vec2 t = (Texture - texBorders.xy) / texBorders.zw;
|
||||
color += Intensity * colorMask * Color * texture(tex, t);
|
||||
}
|
||||
color += (1 - Intensity) * Color;
|
||||
vec2 t = (Texture - texBounds.xy) / texBounds.zw;
|
||||
color += Intensity * Color * texture(tex, t);
|
||||
color *= colorMask;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
// Package pixelgl implements efficient OpenGL targets and utilities for the Pixel game development
|
||||
// library, specifically Window and Canvas.
|
||||
//
|
||||
// It also contains a few additional utilities to help extend Pixel with OpenGL graphical effects.
|
||||
package pixelgl
|
|
@ -0,0 +1,99 @@
|
|||
package pixelgl
|
||||
|
||||
import (
|
||||
"github.com/faiface/glhf"
|
||||
"github.com/faiface/mainthread"
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
// GLFrame is a type that helps implementing OpenGL Targets. It implements most common methods to
|
||||
// avoid code redundancy. It contains an glhf.Frame that you can draw on.
|
||||
type GLFrame struct {
|
||||
frame *glhf.Frame
|
||||
bounds pixel.Rect
|
||||
pixels []uint8
|
||||
dirty bool
|
||||
}
|
||||
|
||||
// NewGLFrame creates a new GLFrame with the given bounds.
|
||||
func NewGLFrame(bounds pixel.Rect) *GLFrame {
|
||||
gf := new(GLFrame)
|
||||
gf.SetBounds(bounds)
|
||||
return gf
|
||||
}
|
||||
|
||||
// SetBounds resizes the GLFrame to the new bounds.
|
||||
func (gf *GLFrame) SetBounds(bounds pixel.Rect) {
|
||||
if bounds == gf.Bounds() {
|
||||
return
|
||||
}
|
||||
|
||||
mainthread.Call(func() {
|
||||
oldF := gf.frame
|
||||
|
||||
_, _, w, h := intBounds(bounds)
|
||||
gf.frame = glhf.NewFrame(w, h, false)
|
||||
|
||||
// preserve old content
|
||||
if oldF != nil {
|
||||
ox, oy, ow, oh := intBounds(bounds)
|
||||
oldF.Blit(
|
||||
gf.frame,
|
||||
ox, oy, ox+ow, oy+oh,
|
||||
ox, oy, ox+ow, oy+oh,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
gf.bounds = bounds
|
||||
gf.pixels = nil
|
||||
gf.dirty = true
|
||||
}
|
||||
|
||||
// Bounds returns the current GLFrame's bounds.
|
||||
func (gf *GLFrame) Bounds() pixel.Rect {
|
||||
return gf.bounds
|
||||
}
|
||||
|
||||
// Color returns the color of the pixel under the specified position.
|
||||
func (gf *GLFrame) Color(at pixel.Vec) pixel.RGBA {
|
||||
if gf.dirty {
|
||||
mainthread.Call(func() {
|
||||
tex := gf.frame.Texture()
|
||||
tex.Begin()
|
||||
gf.pixels = tex.Pixels(0, 0, tex.Width(), tex.Height())
|
||||
tex.End()
|
||||
})
|
||||
gf.dirty = false
|
||||
}
|
||||
if !gf.bounds.Contains(at) {
|
||||
return pixel.Alpha(0)
|
||||
}
|
||||
bx, by, bw, _ := intBounds(gf.bounds)
|
||||
x, y := int(at.X())-bx, int(at.Y())-by
|
||||
off := y*bw + x
|
||||
return pixel.RGBA{
|
||||
R: float64(gf.pixels[off*4+0]) / 255,
|
||||
G: float64(gf.pixels[off*4+1]) / 255,
|
||||
B: float64(gf.pixels[off*4+2]) / 255,
|
||||
A: float64(gf.pixels[off*4+3]) / 255,
|
||||
}
|
||||
}
|
||||
|
||||
// Frame returns the GLFrame's Frame that you can draw on.
|
||||
func (gf *GLFrame) Frame() *glhf.Frame {
|
||||
return gf.frame
|
||||
}
|
||||
|
||||
// Texture returns the underlying Texture of the GLFrame's Frame.
|
||||
//
|
||||
// Implements GLPicture interface.
|
||||
func (gf *GLFrame) Texture() *glhf.Texture {
|
||||
return gf.frame.Texture()
|
||||
}
|
||||
|
||||
// Dirty marks the GLFrame as changed. Always call this method when you draw onto the GLFrame's
|
||||
// Frame.
|
||||
func (gf *GLFrame) Dirty() {
|
||||
gf.dirty = true
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
package pixelgl
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/faiface/glhf"
|
||||
"github.com/faiface/mainthread"
|
||||
"github.com/faiface/pixel"
|
||||
)
|
||||
|
||||
// GLPicture is a pixel.PictureColor with a Texture. All OpenGL Targets should implement and accept
|
||||
// this interface, because it enables seamless drawing of one to another.
|
||||
//
|
||||
// Implementing this interface on an OpenGL Target enables other OpenGL Targets to efficiently draw
|
||||
// that Target onto them.
|
||||
type GLPicture interface {
|
||||
pixel.PictureColor
|
||||
Texture() *glhf.Texture
|
||||
}
|
||||
|
||||
// NewGLPicture creates a new GLPicture with it's own static OpenGL texture. This function always
|
||||
// allocates a new texture that cannot (shouldn't) be further modified.
|
||||
func NewGLPicture(p pixel.Picture) GLPicture {
|
||||
bounds := p.Bounds()
|
||||
bx, by, bw, bh := intBounds(bounds)
|
||||
|
||||
pixels := make([]uint8, 4*bw*bh)
|
||||
|
||||
if pd, ok := p.(*pixel.PictureData); ok {
|
||||
// PictureData short path
|
||||
for y := 0; y < bh; y++ {
|
||||
for x := 0; x < bw; x++ {
|
||||
rgba := pd.Pix[y*pd.Stride+x]
|
||||
off := (y*bw + x) * 4
|
||||
pixels[off+0] = rgba.R
|
||||
pixels[off+1] = rgba.G
|
||||
pixels[off+2] = rgba.B
|
||||
pixels[off+3] = rgba.A
|
||||
}
|
||||
}
|
||||
} else if p, ok := p.(pixel.PictureColor); ok {
|
||||
for y := 0; y < bh; y++ {
|
||||
for x := 0; x < bw; x++ {
|
||||
at := pixel.V(
|
||||
math.Max(float64(bx+x), bounds.Min.X()),
|
||||
math.Max(float64(by+y), bounds.Min.Y()),
|
||||
)
|
||||
color := p.Color(at)
|
||||
off := (y*bw + x) * 4
|
||||
pixels[off+0] = uint8(color.R * 255)
|
||||
pixels[off+1] = uint8(color.G * 255)
|
||||
pixels[off+2] = uint8(color.B * 255)
|
||||
pixels[off+3] = uint8(color.A * 255)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tex *glhf.Texture
|
||||
mainthread.Call(func() {
|
||||
tex = glhf.NewTexture(bw, bh, false, pixels)
|
||||
})
|
||||
|
||||
gp := &glPicture{
|
||||
bounds: bounds,
|
||||
tex: tex,
|
||||
pixels: pixels,
|
||||
}
|
||||
return gp
|
||||
}
|
||||
|
||||
type glPicture struct {
|
||||
bounds pixel.Rect
|
||||
tex *glhf.Texture
|
||||
pixels []uint8
|
||||
}
|
||||
|
||||
func (gp *glPicture) Bounds() pixel.Rect {
|
||||
return gp.bounds
|
||||
}
|
||||
|
||||
func (gp *glPicture) Texture() *glhf.Texture {
|
||||
return gp.tex
|
||||
}
|
||||
|
||||
func (gp *glPicture) Color(at pixel.Vec) pixel.RGBA {
|
||||
if !gp.bounds.Contains(at) {
|
||||
return pixel.Alpha(0)
|
||||
}
|
||||
bx, by, bw, _ := intBounds(gp.bounds)
|
||||
x, y := int(at.X())-bx, int(at.Y())-by
|
||||
off := y*bw + x
|
||||
return pixel.RGBA{
|
||||
R: float64(gp.pixels[off*4+0]) / 255,
|
||||
G: float64(gp.pixels[off*4+1]) / 255,
|
||||
B: float64(gp.pixels[off*4+2]) / 255,
|
||||
A: float64(gp.pixels[off*4+3]) / 255,
|
||||
}
|
||||
}
|
|
@ -75,6 +75,7 @@ func (gt *GLTriangles) SetLen(len int) {
|
|||
if len < gt.Len() {
|
||||
gt.data = gt.data[:len*gt.vs.Stride()]
|
||||
}
|
||||
gt.submitData()
|
||||
}
|
||||
|
||||
// Slice returns a sub-Triangles of this GLTriangles in range [i, j).
|
||||
|
@ -142,14 +143,26 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
|
|||
}
|
||||
|
||||
func (gt *GLTriangles) submitData() {
|
||||
data := append([]float32{}, gt.data...) // avoid race condition
|
||||
mainthread.CallNonBlock(func() {
|
||||
gt.vs.Begin()
|
||||
dataLen := len(data) / gt.vs.Stride()
|
||||
gt.vs.SetLen(dataLen)
|
||||
gt.vs.SetVertexData(gt.data)
|
||||
gt.vs.End()
|
||||
})
|
||||
// this code is supposed to copy the vertex data and CallNonBlock the update if
|
||||
// the data is small enough, otherwise it'll block and not copy the data
|
||||
if len(gt.data) < 256 { // arbitrary heurestic constant
|
||||
data := append([]float32{}, gt.data...)
|
||||
mainthread.CallNonBlock(func() {
|
||||
gt.vs.Begin()
|
||||
dataLen := len(data) / gt.vs.Stride()
|
||||
gt.vs.SetLen(dataLen)
|
||||
gt.vs.SetVertexData(data)
|
||||
gt.vs.End()
|
||||
})
|
||||
} else {
|
||||
mainthread.Call(func() {
|
||||
gt.vs.Begin()
|
||||
dataLen := len(gt.data) / gt.vs.Stride()
|
||||
gt.vs.SetLen(dataLen)
|
||||
gt.vs.SetVertexData(gt.data)
|
||||
gt.vs.End()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Update copies vertex properties from the supplied Triangles into this GLTriangles.
|
||||
|
@ -178,12 +191,12 @@ func (gt *GLTriangles) Position(i int) pixel.Vec {
|
|||
}
|
||||
|
||||
// Color returns the Color property of the i-th vertex.
|
||||
func (gt *GLTriangles) Color(i int) pixel.NRGBA {
|
||||
func (gt *GLTriangles) Color(i int) pixel.RGBA {
|
||||
r := gt.data[i*gt.vs.Stride()+2]
|
||||
g := gt.data[i*gt.vs.Stride()+3]
|
||||
b := gt.data[i*gt.vs.Stride()+4]
|
||||
a := gt.data[i*gt.vs.Stride()+5]
|
||||
return pixel.NRGBA{
|
||||
return pixel.RGBA{
|
||||
R: float64(r),
|
||||
G: float64(g),
|
||||
B: float64(b),
|
||||
|
|
|
@ -23,25 +23,16 @@ type WindowConfig struct {
|
|||
// Bounds specify the bounds of the Window in pixels.
|
||||
Bounds pixel.Rect
|
||||
|
||||
// If set to nil, a Window will be windowed. Otherwise it will be fullscreen on the
|
||||
// If set to nil, the Window will be windowed. Otherwise it will be fullscreen on the
|
||||
// specified Monitor.
|
||||
Fullscreen *Monitor
|
||||
Monitor *Monitor
|
||||
|
||||
// Whether a Window is resizable.
|
||||
// Whether the Window is resizable.
|
||||
Resizable bool
|
||||
|
||||
// If set to true, the Window will be initially invisible.
|
||||
Hidden bool
|
||||
|
||||
// Undecorated Window ommits the borders and decorations (close button, etc.).
|
||||
Undecorated bool
|
||||
|
||||
// If set to true, a Window will not get focused upon showing up.
|
||||
Unfocused bool
|
||||
|
||||
// Whether a Window is maximized.
|
||||
Maximized bool
|
||||
|
||||
// VSync (vertical synchronization) synchronizes Window's framerate with the framerate of
|
||||
// the monitor.
|
||||
VSync bool
|
||||
|
@ -89,10 +80,7 @@ func NewWindow(cfg WindowConfig) (*Window, error) {
|
|||
glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
|
||||
|
||||
glfw.WindowHint(glfw.Resizable, bool2int[cfg.Resizable])
|
||||
glfw.WindowHint(glfw.Visible, bool2int[!cfg.Hidden])
|
||||
glfw.WindowHint(glfw.Decorated, bool2int[!cfg.Undecorated])
|
||||
glfw.WindowHint(glfw.Focused, bool2int[!cfg.Unfocused])
|
||||
glfw.WindowHint(glfw.Maximized, bool2int[cfg.Maximized])
|
||||
|
||||
var share *glfw.Window
|
||||
if currWin != nil {
|
||||
|
@ -123,9 +111,9 @@ func NewWindow(cfg WindowConfig) (*Window, error) {
|
|||
w.SetVSync(cfg.VSync)
|
||||
|
||||
w.initInput()
|
||||
w.SetMonitor(cfg.Fullscreen)
|
||||
w.SetMonitor(cfg.Monitor)
|
||||
|
||||
w.canvas = NewCanvas(cfg.Bounds, false)
|
||||
w.canvas = NewCanvas(cfg.Bounds)
|
||||
w.Update()
|
||||
|
||||
runtime.SetFinalizer(w, (*Window).Destroy)
|
||||
|
@ -156,16 +144,17 @@ func (w *Window) Update() {
|
|||
mainthread.Call(func() {
|
||||
w.begin()
|
||||
|
||||
glhf.Bounds(0, 0, w.canvas.f.Texture().Width(), w.canvas.f.Texture().Height())
|
||||
framebufferWidth, framebufferHeight := w.window.GetFramebufferSize()
|
||||
glhf.Bounds(0, 0, framebufferWidth, framebufferHeight)
|
||||
|
||||
glhf.Clear(0, 0, 0, 0)
|
||||
w.canvas.f.Begin()
|
||||
w.canvas.f.Blit(
|
||||
w.canvas.gf.Frame().Begin()
|
||||
w.canvas.gf.Frame().Blit(
|
||||
nil,
|
||||
0, 0, w.canvas.f.Texture().Width(), w.canvas.f.Texture().Height(),
|
||||
0, 0, w.canvas.f.Texture().Width(), w.canvas.f.Texture().Height(),
|
||||
0, 0, w.canvas.Texture().Width(), w.canvas.Texture().Height(),
|
||||
0, 0, framebufferWidth, framebufferHeight,
|
||||
)
|
||||
w.canvas.f.End()
|
||||
w.canvas.gf.Frame().End()
|
||||
|
||||
if w.vsync {
|
||||
glfw.SwapInterval(1)
|
||||
|
@ -222,20 +211,6 @@ func (w *Window) Bounds() pixel.Rect {
|
|||
return w.bounds
|
||||
}
|
||||
|
||||
// Show makes the Window visible if it was hidden.
|
||||
func (w *Window) Show() {
|
||||
mainthread.Call(func() {
|
||||
w.window.Show()
|
||||
})
|
||||
}
|
||||
|
||||
// Hide hides the Window if it was visible.
|
||||
func (w *Window) Hide() {
|
||||
mainthread.Call(func() {
|
||||
w.window.Hide()
|
||||
})
|
||||
}
|
||||
|
||||
func (w *Window) setFullscreen(monitor *Monitor) {
|
||||
mainthread.Call(func() {
|
||||
w.restore.xpos, w.restore.ypos = w.window.GetPos()
|
||||
|
@ -282,11 +257,6 @@ func (w *Window) SetMonitor(monitor *Monitor) {
|
|||
}
|
||||
}
|
||||
|
||||
// IsFullscreen returns true if the Window is in fullscreen mode.
|
||||
func (w *Window) IsFullscreen() bool {
|
||||
return w.Monitor() != nil
|
||||
}
|
||||
|
||||
// Monitor returns a monitor the Window is fullscreen on. If the Window is not fullscreen, this
|
||||
// function returns nil.
|
||||
func (w *Window) Monitor() *Monitor {
|
||||
|
@ -302,13 +272,6 @@ func (w *Window) Monitor() *Monitor {
|
|||
}
|
||||
}
|
||||
|
||||
// Focus brings the Window to the front and sets input focus.
|
||||
func (w *Window) Focus() {
|
||||
mainthread.Call(func() {
|
||||
w.window.Focus()
|
||||
})
|
||||
}
|
||||
|
||||
// Focused returns true if the Window has input focus.
|
||||
func (w *Window) Focused() bool {
|
||||
var focused bool
|
||||
|
@ -318,20 +281,6 @@ func (w *Window) Focused() bool {
|
|||
return focused
|
||||
}
|
||||
|
||||
// Maximize puts the Window to the maximized state.
|
||||
func (w *Window) Maximize() {
|
||||
mainthread.Call(func() {
|
||||
w.window.Maximize()
|
||||
})
|
||||
}
|
||||
|
||||
// Restore restores the Window from the maximized state.
|
||||
func (w *Window) Restore() {
|
||||
mainthread.Call(func() {
|
||||
w.window.Restore()
|
||||
})
|
||||
}
|
||||
|
||||
// SetVSync sets whether the Window's Update should synchronize with the monitor refresh rate.
|
||||
func (w *Window) SetVSync(vsync bool) {
|
||||
w.vsync = vsync
|
||||
|
@ -366,7 +315,7 @@ func (w *Window) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
|
|||
|
||||
// MakePicture generates a specialized copy of the supplied Picture that will draw onto this Window.
|
||||
//
|
||||
// Window support PictureColor.
|
||||
// Window supports PictureColor.
|
||||
func (w *Window) MakePicture(p pixel.Picture) pixel.TargetPicture {
|
||||
return w.canvas.MakePicture(p)
|
||||
}
|
||||
|
@ -381,6 +330,12 @@ func (w *Window) SetColorMask(c color.Color) {
|
|||
w.canvas.SetColorMask(c)
|
||||
}
|
||||
|
||||
// SetComposeMethod sets a Porter-Duff composition method to be used in the following draws onto
|
||||
// this Window.
|
||||
func (w *Window) SetComposeMethod(cmp pixel.ComposeMethod) {
|
||||
w.canvas.SetComposeMethod(cmp)
|
||||
}
|
||||
|
||||
// SetSmooth sets whether the stretched Pictures drawn onto this Window should be drawn smooth or
|
||||
// pixely.
|
||||
func (w *Window) SetSmooth(smooth bool) {
|
||||
|
@ -397,3 +352,8 @@ func (w *Window) Smooth() bool {
|
|||
func (w *Window) Clear(c color.Color) {
|
||||
w.canvas.Clear(c)
|
||||
}
|
||||
|
||||
// Color returns the color of the pixel over the given position inside the Window.
|
||||
func (w *Window) Color(at pixel.Vec) pixel.RGBA {
|
||||
return w.canvas.Color(at)
|
||||
}
|
||||
|
|
63
sprite.go
|
@ -2,43 +2,44 @@ package pixel
|
|||
|
||||
import "image/color"
|
||||
|
||||
// Sprite is a drawable Picture. It's anchored by the center of it's Picture.
|
||||
// Sprite is a drawable frame of a Picture. It's anchored by the center of it's Picture's frame.
|
||||
//
|
||||
// Frame specifies a rectangular portion of the Picture that will be drawn. For example, this
|
||||
// creates a Sprite that draws the whole Picture:
|
||||
//
|
||||
// sprite := pixel.NewSprite(pic, pic.Bounds())
|
||||
//
|
||||
// To achieve different anchoring, transformations and color masking, use SetMatrix and SetColorMask
|
||||
// methods.
|
||||
type Sprite struct {
|
||||
tri *TrianglesData
|
||||
bounds Rect
|
||||
d Drawer
|
||||
tri *TrianglesData
|
||||
frame Rect
|
||||
d Drawer
|
||||
|
||||
matrix Matrix
|
||||
mask NRGBA
|
||||
mask RGBA
|
||||
}
|
||||
|
||||
// NewSprite creates a Sprite from the supplied Picture.
|
||||
func NewSprite(pic Picture) *Sprite {
|
||||
// NewSprite creates a Sprite from the supplied frame of a Picture.
|
||||
func NewSprite(pic Picture, frame Rect) *Sprite {
|
||||
tri := MakeTrianglesData(6)
|
||||
s := &Sprite{
|
||||
tri: tri,
|
||||
d: Drawer{Triangles: tri},
|
||||
}
|
||||
s.matrix = IM
|
||||
s.mask = NRGBA{1, 1, 1, 1}
|
||||
s.SetPicture(pic)
|
||||
s.mask = Alpha(1)
|
||||
s.Set(pic, frame)
|
||||
return s
|
||||
}
|
||||
|
||||
// SetPicture changes the Sprite's Picture. The new Picture may have a different size, everything
|
||||
// works.
|
||||
func (s *Sprite) SetPicture(pic Picture) {
|
||||
// Set sets a new frame of a Picture for this Sprite.
|
||||
func (s *Sprite) Set(pic Picture, frame Rect) {
|
||||
s.d.Picture = pic
|
||||
|
||||
if s.bounds == pic.Bounds() {
|
||||
return
|
||||
if frame != s.frame {
|
||||
s.frame = frame
|
||||
s.calcData()
|
||||
}
|
||||
s.bounds = pic.Bounds()
|
||||
|
||||
s.calcData()
|
||||
}
|
||||
|
||||
// Picture returns the current Sprite's Picture.
|
||||
|
@ -46,14 +47,21 @@ func (s *Sprite) Picture() Picture {
|
|||
return s.d.Picture
|
||||
}
|
||||
|
||||
// Frame returns the current Sprite's frame.
|
||||
func (s *Sprite) Frame() Rect {
|
||||
return s.frame
|
||||
}
|
||||
|
||||
// SetMatrix sets a Matrix that this Sprite will be transformed by. This overrides any previously
|
||||
// set Matrix.
|
||||
//
|
||||
// Note, that this has nothing to do with BasicTarget's SetMatrix method. This only affects this
|
||||
// Sprite and is usable with any Target.
|
||||
func (s *Sprite) SetMatrix(matrix Matrix) {
|
||||
s.matrix = matrix
|
||||
s.calcData()
|
||||
if s.matrix != matrix {
|
||||
s.matrix = matrix
|
||||
s.calcData()
|
||||
}
|
||||
}
|
||||
|
||||
// Matrix returns the currently set Matrix.
|
||||
|
@ -67,12 +75,15 @@ func (s *Sprite) Matrix() Matrix {
|
|||
// Note, that this has nothing to do with BasicTarget's SetColorMask method. This only affects this
|
||||
// Sprite and is usable with any Target.
|
||||
func (s *Sprite) SetColorMask(mask color.Color) {
|
||||
s.mask = ToNRGBA(mask)
|
||||
s.calcData()
|
||||
rgba := ToRGBA(mask)
|
||||
if s.mask != rgba {
|
||||
s.mask = ToRGBA(mask)
|
||||
s.calcData()
|
||||
}
|
||||
}
|
||||
|
||||
// ColorMask returns the currently set color mask.
|
||||
func (s *Sprite) ColorMask() NRGBA {
|
||||
func (s *Sprite) ColorMask() RGBA {
|
||||
return s.mask
|
||||
}
|
||||
|
||||
|
@ -83,9 +94,9 @@ func (s *Sprite) Draw(t Target) {
|
|||
|
||||
func (s *Sprite) calcData() {
|
||||
var (
|
||||
center = s.bounds.Center()
|
||||
horizontal = X(s.bounds.W() / 2)
|
||||
vertical = Y(s.bounds.H() / 2)
|
||||
center = s.frame.Center()
|
||||
horizontal = X(s.frame.W() / 2)
|
||||
vertical = Y(s.frame.H() / 2)
|
||||
)
|
||||
|
||||
(*s.tri)[0].Position = -horizontal - vertical
|
||||
|
|