Merge branch 'master' into gl2.1

This commit is contained in:
faiface 2017-06-09 19:26:14 +02:00
commit c331fe2583
14 changed files with 629 additions and 58 deletions

View File

@ -20,6 +20,7 @@ covering several topics of Pixel. Here's the content of the tutorial parts so fa
- [Pressing keys and clicking mouse](https://github.com/faiface/pixel/wiki/Pressing-keys-and-clicking-mouse) - [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 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) - [Drawing shapes with IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw)
- [Typing text on the screen](https://github.com/faiface/pixel/wiki/Typing-text-on-the-screen)
## Examples ## Examples

View File

@ -45,7 +45,7 @@ func (td *TrianglesData) SetLen(len int) {
Color RGBA Color RGBA
Picture Vec Picture Vec
Intensity float64 Intensity float64
}{ZV, Alpha(1), ZV, 0}) }{Color: RGBA{1, 1, 1, 1}})
} }
} }
if len < td.Len() { if len < td.Len() {

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 stephen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,19 @@
# Maze generator in Go
Created by [Stephen Chavez](https://github.com/redragonx)
This uses the game engine: Pixel. Install it here: https://github.com/faiface/pixel
I made this to improve my understanding of Go and some game concepts with some basic maze generating algorithms.
Controls: Press 'R' to restart the maze.
Optional command-line arguments: `go run ./maze-generator.go`
- `-w` sets the maze's width in pixels.
- `-h` sets the maze's height in pixels.
- `-c` sets the maze cell's size in pixels.
Code based on the Recursive backtracker algorithm.
- https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
![Screenshot](screenshot.png)

View File

@ -0,0 +1,317 @@
package main
// Code based on the Recursive backtracker algorithm.
// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker
// See https://youtu.be/HyK_Q5rrcr4 as an example
// YouTube example ported to Go for the Pixel library.
// Created by Stephen Chavez
import (
"crypto/rand"
"errors"
"flag"
"fmt"
"math/big"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/examples/community/maze/stack"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
"github.com/pkg/profile"
"golang.org/x/image/colornames"
)
var visitedColor = pixel.RGB(0.5, 0, 1).Mul(pixel.Alpha(0.35))
var hightlightColor = pixel.RGB(0.3, 0, 0).Mul(pixel.Alpha(0.45))
var debug = false
type cell struct {
walls [4]bool // Wall order: top, right, bottom, left
row int
col int
visited bool
}
func (c *cell) Draw(imd *imdraw.IMDraw, wallSize int) {
drawCol := c.col * wallSize // x
drawRow := c.row * wallSize // y
imd.Color = colornames.White
if c.walls[0] {
// top line
imd.Push(pixel.V(float64(drawCol), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow)))
imd.Line(3)
}
if c.walls[1] {
// right Line
imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
imd.Line(3)
}
if c.walls[2] {
// bottom line
imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow+wallSize)))
imd.Line(3)
}
if c.walls[3] {
// left line
imd.Push(pixel.V(float64(drawCol), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow)))
imd.Line(3)
}
imd.EndShape = imdraw.SharpEndShape
if c.visited {
imd.Color = visitedColor
imd.Push(pixel.V(float64(drawCol), (float64(drawRow))), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)))
imd.Rectangle(0)
}
}
func (c *cell) GetNeighbors(grid []*cell, cols int, rows int) ([]*cell, error) {
neighbors := []*cell{}
j := c.row
i := c.col
top, _ := getCellAt(i, j-1, cols, rows, grid)
right, _ := getCellAt(i+1, j, cols, rows, grid)
bottom, _ := getCellAt(i, j+1, cols, rows, grid)
left, _ := getCellAt(i-1, j, cols, rows, grid)
if top != nil && !top.visited {
neighbors = append(neighbors, top)
}
if right != nil && !right.visited {
neighbors = append(neighbors, right)
}
if bottom != nil && !bottom.visited {
neighbors = append(neighbors, bottom)
}
if left != nil && !left.visited {
neighbors = append(neighbors, left)
}
if len(neighbors) == 0 {
return nil, errors.New("We checked all cells...")
}
return neighbors, nil
}
func (c *cell) GetRandomNeighbor(grid []*cell, cols int, rows int) (*cell, error) {
neighbors, err := c.GetNeighbors(grid, cols, rows)
if neighbors == nil {
return nil, err
}
nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors))))
if err != nil {
panic(err)
}
randomIndex := nBig.Int64()
return neighbors[randomIndex], nil
}
func (c *cell) hightlight(imd *imdraw.IMDraw, wallSize int) {
x := c.col * wallSize
y := c.row * wallSize
imd.Color = hightlightColor
imd.Push(pixel.V(float64(x), float64(y)), pixel.V(float64(x+wallSize), float64(y+wallSize)))
imd.Rectangle(0)
}
func newCell(col int, row int) *cell {
newCell := new(cell)
newCell.row = row
newCell.col = col
for i := range newCell.walls {
newCell.walls[i] = true
}
return newCell
}
// Creates the inital maze slice for use.
func initGrid(cols, rows int) []*cell {
grid := []*cell{}
for j := 0; j < rows; j++ {
for i := 0; i < cols; i++ {
newCell := newCell(i, j)
grid = append(grid, newCell)
}
}
return grid
}
func setupMaze(cols, rows int) ([]*cell, *stack.Stack, *cell) {
// Make an empty grid
grid := initGrid(cols, rows)
backTrackStack := stack.NewStack(len(grid))
currentCell := grid[0]
return grid, backTrackStack, currentCell
}
func cellIndex(i, j, cols, rows int) int {
if i < 0 || j < 0 || i > cols-1 || j > rows-1 {
return -1
}
return i + j*cols
}
func getCellAt(i int, j int, cols int, rows int, grid []*cell) (*cell, error) {
possibleIndex := cellIndex(i, j, cols, rows)
if possibleIndex == -1 {
return nil, fmt.Errorf("cellIndex: CellIndex is a negative number %d", possibleIndex)
}
return grid[possibleIndex], nil
}
func removeWalls(a *cell, b *cell) {
x := a.col - b.col
if x == 1 {
a.walls[3] = false
b.walls[1] = false
} else if x == -1 {
a.walls[1] = false
b.walls[3] = false
}
y := a.row - b.row
if y == 1 {
a.walls[0] = false
b.walls[2] = false
} else if y == -1 {
a.walls[2] = false
b.walls[0] = false
}
}
func run() {
// unsiged integers, because easier parsing error checks.
// We must convert these to intergers, as done below...
uScreenWidth, uScreenHeight, uWallSize := parseArgs()
var (
// In pixels
// Defualt is 800x800x40 = 20x20 wallgrid
screenWidth = int(uScreenWidth)
screenHeight = int(uScreenHeight)
wallSize = int(uWallSize)
frames = 0
second = time.Tick(time.Second)
grid = []*cell{}
cols = screenWidth / wallSize
rows = screenHeight / wallSize
currentCell = new(cell)
backTrackStack = stack.NewStack(1)
)
// Set game FPS manually
fps := time.Tick(time.Second / 60)
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks! - Maze example",
Bounds: pixel.R(0, 0, float64(screenHeight), float64(screenWidth)),
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
grid, backTrackStack, currentCell = setupMaze(cols, rows)
gridIMDraw := imdraw.New(nil)
for !win.Closed() {
if win.JustReleased(pixelgl.KeyR) {
fmt.Println("R pressed")
grid, backTrackStack, currentCell = setupMaze(cols, rows)
}
win.Clear(colornames.Gray)
gridIMDraw.Clear()
for i := range grid {
grid[i].Draw(gridIMDraw, wallSize)
}
// step 1
// Make the initial cell the current cell and mark it as visited
currentCell.visited = true
currentCell.hightlight(gridIMDraw, wallSize)
// step 2.1
// If the current cell has any neighbours which have not been visited
// Choose a random unvisited cell
nextCell, _ := currentCell.GetRandomNeighbor(grid, cols, rows)
if nextCell != nil && !nextCell.visited {
// step 2.2
// Push the current cell to the stack
backTrackStack.Push(currentCell)
// step 2.3
// Remove the wall between the current cell and the chosen cell
removeWalls(currentCell, nextCell)
// step 2.4
// Make the chosen cell the current cell and mark it as visited
nextCell.visited = true
currentCell = nextCell
} else if backTrackStack.Len() > 0 {
currentCell = backTrackStack.Pop().(*cell)
}
gridIMDraw.Draw(win)
win.Update()
<-fps
updateFPSDisplay(win, &cfg, &frames, grid, second)
}
}
// Parses the maze arguments, all of them are optional.
// Uses uint as implicit error checking :)
func parseArgs() (uint, uint, uint) {
var mazeWidthPtr = flag.Uint("w", 800, "w sets the maze's width in pixels.")
var mazeHeightPtr = flag.Uint("h", 800, "h sets the maze's height in pixels.")
var wallSizePtr = flag.Uint("c", 40, "c sets the maze cell's size in pixels.")
flag.Parse()
// If these aren't default values AND if they're not the same values.
// We should warn the user that the maze will look funny.
if *mazeWidthPtr != 800 || *mazeHeightPtr != 800 {
if *mazeWidthPtr != *mazeHeightPtr {
fmt.Printf("WARNING: maze width: %d and maze height: %d don't match. \n", *mazeWidthPtr, *mazeHeightPtr)
fmt.Println("Maze will look funny because the maze size is bond to the window size!")
}
}
return *mazeWidthPtr, *mazeHeightPtr, *wallSizePtr
}
func updateFPSDisplay(win *pixelgl.Window, cfg *pixelgl.WindowConfig, frames *int, grid []*cell, second <-chan time.Time) {
*frames++
select {
case <-second:
win.SetTitle(fmt.Sprintf("%s | FPS: %d with %d Cells", cfg.Title, *frames, len(grid)))
*frames = 0
default:
}
}
func main() {
if debug {
defer profile.Start().Stop()
}
pixelgl.Run(run)
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,86 @@
package stack
type Stack struct {
top *Element
size int
max int
}
type Element struct {
value interface{}
next *Element
}
func NewStack(max int) *Stack {
return &Stack{max: max}
}
// Return the stack's length
func (s *Stack) Len() int {
return s.size
}
// Return the stack's max
func (s *Stack) Max() int {
return s.max
}
// Push a new element onto the stack
func (s *Stack) Push(value interface{}) {
if s.size+1 > s.max {
if last := s.PopLast(); last == nil {
panic("Unexpected nil in stack")
}
}
s.top = &Element{value, s.top}
s.size++
}
// Remove the top element from the stack and return it's value
// If the stack is empty, return nil
func (s *Stack) Pop() (value interface{}) {
if s.size > 0 {
value, s.top = s.top.value, s.top.next
s.size--
return
}
return nil
}
func (s *Stack) PopLast() (value interface{}) {
if lastElem := s.popLast(s.top); lastElem != nil {
return lastElem.value
}
return nil
}
//Peek returns a top without removing it from list
func (s *Stack) Peek() (value interface{}, exists bool) {
exists = false
if s.size > 0 {
value = s.top.value
exists = true
}
return
}
func (s *Stack) popLast(elem *Element) *Element {
if elem == nil {
return nil
}
// not last because it has next and a grandchild
if elem.next != nil && elem.next.next != nil {
return s.popLast(elem.next)
}
// current elem is second from bottom, as next elem has no child
if elem.next != nil && elem.next.next == nil {
last := elem.next
// make current elem bottom of stack by removing its next element
elem.next = nil
s.size--
return last
}
return nil
}

View File

@ -0,0 +1,78 @@
package main
import (
"io/ioutil"
"os"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"github.com/faiface/pixel/text"
"github.com/golang/freetype/truetype"
"golang.org/x/image/colornames"
"golang.org/x/image/font"
)
func loadTTF(path string, size float64) (font.Face, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
bytes, err := ioutil.ReadAll(file)
if err != nil {
return nil, err
}
font, err := truetype.Parse(bytes)
if err != nil {
return nil, err
}
return truetype.NewFace(font, &truetype.Options{
Size: size,
GlyphCacheEntries: 1,
}), nil
}
func run() {
cfg := pixelgl.WindowConfig{
Title: "Pixel Rocks!",
Bounds: pixel.R(0, 0, 1024, 768),
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.SetSmooth(true)
face, err := loadTTF("intuitive.ttf", 80)
if err != nil {
panic(err)
}
atlas := text.NewAtlas(face, text.ASCII)
txt := text.New(pixel.V(50, 500), atlas)
txt.Color = colornames.Lightgrey
fps := time.Tick(time.Second / 120)
for !win.Closed() {
txt.WriteString(win.Typed())
if win.JustPressed(pixelgl.KeyEnter) || win.Repeated(pixelgl.KeyEnter) {
txt.WriteRune('\n')
}
win.Clear(colornames.Darkcyan)
txt.Draw(win, pixel.IM.Moved(win.Bounds().Center().Sub(txt.Bounds().Center())))
win.Update()
<-fps
}
}
func main() {
pixelgl.Run(run)
}

View File

@ -227,10 +227,7 @@ func (dl *dotlight) Draw(t pixel.Target, m pixel.Matrix) {
dl.imd.Color = pixel.Alpha(0) dl.imd.Color = pixel.Alpha(0)
for i := 0.0; i <= 32; i++ { for i := 0.0; i <= 32; i++ {
angle := i * 2 * math.Pi / 32 angle := i * 2 * math.Pi / 32
dl.imd.Push(dl.pos.Add(pixel.V( dl.imd.Push(dl.pos.Add(pixel.V(dl.radius, 0).Rotated(angle)))
math.Cos(angle)*dl.radius,
math.Sin(angle)*dl.radius,
)))
} }
dl.imd.Polygon(0) dl.imd.Polygon(0)
dl.imd.Draw(t) dl.imd.Draw(t)

View File

@ -3,8 +3,6 @@ package pixel
import ( import (
"fmt" "fmt"
"math" "math"
"github.com/go-gl/mathgl/mgl64"
) )
// Vec is a 2D vector type with X and Y coordinates. // Vec is a 2D vector type with X and Y coordinates.
@ -251,7 +249,7 @@ func (r Rect) Union(s Rect) Rect {
) )
} }
// Matrix is a 3x3 transformation matrix that can be used for all kinds of spacial transforms, such // Matrix is a 3x2 affine matrix that can be used for all kinds of spatial transforms, such
// as movement, scaling and rotations. // as movement, scaling and rotations.
// //
// Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For // Matrix has a handful of useful methods, each of which adds a transformation to the matrix. For
@ -261,38 +259,41 @@ func (r Rect) Union(s Rect) Rect {
// //
// This code creates a Matrix that first moves everything by 100 units horizontally and 200 units // This code creates a Matrix that first moves everything by 100 units horizontally and 200 units
// vertically and then rotates everything by 90 degrees around the origin. // vertically and then rotates everything by 90 degrees around the origin.
type Matrix [9]float64 //
// Layout is:
// [0] [2] [4]
// [1] [3] [5]
// 0 0 1 (implicit row)
type Matrix [6]float64
// IM stands for identity matrix. Does nothing, no transformation. // IM stands for identity matrix. Does nothing, no transformation.
var IM = Matrix(mgl64.Ident3()) var IM = Matrix{1, 0, 0, 1, 0, 0}
// String returns a string representation of the Matrix. // String returns a string representation of the Matrix.
// //
// m := pixel.IM // m := pixel.IM
// fmt.Println(m) // Matrix(1 0 0 | 0 1 0 | 0 0 1) // fmt.Println(m) // Matrix(1 0 0 | 0 1 0)
func (m Matrix) String() string { func (m Matrix) String() string {
return fmt.Sprintf( return fmt.Sprintf(
"Matrix(%v %v %v | %v %v %v | %v %v %v)", "Matrix(%v %v %v | %v %v %v)",
m[0], m[3], m[6], m[0], m[2], m[4],
m[1], m[4], m[7], m[1], m[3], m[5],
m[2], m[5], m[8],
) )
} }
// Moved moves everything by the delta vector. // Moved moves everything by the delta vector.
func (m Matrix) Moved(delta Vec) Matrix { func (m Matrix) Moved(delta Vec) Matrix {
m3 := mgl64.Mat3(m) m[4], m[5] = m[4]+delta.X, m[5]+delta.Y
m3 = mgl64.Translate2D(delta.XY()).Mul3(m3) return m
return Matrix(m3)
} }
// ScaledXY scales everything around a given point by the scale factor in each axis respectively. // ScaledXY scales everything around a given point by the scale factor in each axis respectively.
func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix { func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix {
m3 := mgl64.Mat3(m) m[4], m[5] = m[4]-around.X, m[5]-around.Y
m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3) m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X
m3 = mgl64.Scale2D(scale.XY()).Mul3(m3) m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y
m3 = mgl64.Translate2D(around.XY()).Mul3(m3) m[4], m[5] = m[4]+around.X, m[5]+around.Y
return Matrix(m3) return m
} }
// Scaled scales everything around a given point by the scale factor. // Scaled scales everything around a given point by the scale factor.
@ -302,36 +303,42 @@ func (m Matrix) Scaled(around Vec, scale float64) Matrix {
// Rotated rotates everything around a given point by the given angle in radians. // Rotated rotates everything around a given point by the given angle in radians.
func (m Matrix) Rotated(around Vec, angle float64) Matrix { func (m Matrix) Rotated(around Vec, angle float64) Matrix {
m3 := mgl64.Mat3(m) sint, cost := math.Sincos(angle)
m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3) m[4], m[5] = m[4]-around.X, m[5]-around.Y
m3 = mgl64.Rotate3DZ(angle).Mul3(m3) m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0})
m3 = mgl64.Translate2D(around.XY()).Mul3(m3) m[4], m[5] = m[4]+around.X, m[5]+around.Y
return Matrix(m3) return m
} }
// Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied // Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied
// after the transformations of this Matrix. // after the transformations of this Matrix.
func (m Matrix) Chained(next Matrix) Matrix { func (m Matrix) Chained(next Matrix) Matrix {
m3 := mgl64.Mat3(m) return Matrix{
m3 = mgl64.Mat3(next).Mul3(m3) m[0]*next[0] + m[2]*next[1],
return Matrix(m3) m[1]*next[0] + m[3]*next[1],
m[0]*next[2] + m[2]*next[3],
m[1]*next[2] + m[3]*next[3],
m[0]*next[4] + m[2]*next[5] + m[4],
m[1]*next[4] + m[3]*next[5] + m[5],
}
} }
// Project applies all transformations added to the Matrix to a vector u and returns the result. // Project applies all transformations added to the Matrix to a vector u and returns the result.
// //
// Time complexity is O(1). // Time complexity is O(1).
func (m Matrix) Project(u Vec) Vec { func (m Matrix) Project(u Vec) Vec {
m3 := mgl64.Mat3(m) return Vec{X: m[0]*u.X + m[2]*u.Y + m[4], Y: m[1]*u.X + m[3]*u.Y + m[5]}
proj := m3.Mul3x1(mgl64.Vec3{u.X, u.Y, 1})
return V(proj.X(), proj.Y())
} }
// Unproject does the inverse operation to Project. // Unproject does the inverse operation to Project.
// //
// It turns out that multiplying a vector by the inverse matrix of m can be nearly-accomplished by
// subtracting the translate part of the matrix and multplying by the inverse of the top-left 2x2
// matrix, and the inverse of a 2x2 matrix is simple enough to just be inlined in the computation.
//
// Time complexity is O(1). // Time complexity is O(1).
func (m Matrix) Unproject(u Vec) Vec { func (m Matrix) Unproject(u Vec) Vec {
m3 := mgl64.Mat3(m) d := (m[0] * m[3]) - (m[1] * m[2])
inv := m3.Inv() u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d
unproj := inv.Mul3x1(mgl64.Vec3{u.X, u.Y, 1}) return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]}
return V(unproj.X(), unproj.Y())
} }

View File

@ -52,6 +52,7 @@ type IMDraw struct {
EndShape EndShape EndShape EndShape
points []point points []point
pool [][]point
matrix pixel.Matrix matrix pixel.Matrix
mask pixel.RGBA mask pixel.RGBA
@ -109,7 +110,7 @@ func (imd *IMDraw) Clear() {
// //
// This does not affect matrix and color mask set by SetMatrix and SetColorMask. // This does not affect matrix and color mask set by SetMatrix and SetColorMask.
func (imd *IMDraw) Reset() { func (imd *IMDraw) Reset() {
imd.points = nil imd.points = imd.points[:0]
imd.Color = pixel.Alpha(1) imd.Color = pixel.Alpha(1)
imd.Picture = pixel.ZV imd.Picture = pixel.ZV
imd.Intensity = 0 imd.Intensity = 0
@ -256,10 +257,22 @@ func (imd *IMDraw) EllipseArc(radius pixel.Vec, low, high, thickness float64) {
func (imd *IMDraw) getAndClearPoints() []point { func (imd *IMDraw) getAndClearPoints() []point {
points := imd.points points := imd.points
// use one of the existing pools so we don't reallocate as often
if len(imd.pool) > 0 {
pos := len(imd.pool) - 1
imd.points = imd.pool[pos][:0]
imd.pool = imd.pool[:pos]
} else {
imd.points = nil imd.points = nil
}
return points return points
} }
func (imd *IMDraw) restorePoints(points []point) {
imd.pool = append(imd.pool, imd.points)
imd.points = points[:0]
}
func (imd *IMDraw) applyMatrixAndMask(off int) { func (imd *IMDraw) applyMatrixAndMask(off int) {
for i := range (*imd.tri)[off:] { for i := range (*imd.tri)[off:] {
(*imd.tri)[off+i].Position = imd.matrix.Project((*imd.tri)[off+i].Position) (*imd.tri)[off+i].Position = imd.matrix.Project((*imd.tri)[off+i].Position)
@ -271,6 +284,7 @@ func (imd *IMDraw) fillRectangle() {
points := imd.getAndClearPoints() points := imd.getAndClearPoints()
if len(points) < 2 { if len(points) < 2 {
imd.restorePoints(points)
return return
} }
@ -292,7 +306,7 @@ func (imd *IMDraw) fillRectangle() {
in: (a.in + b.in) / 2, in: (a.in + b.in) / 2,
} }
for k, p := range []point{a, b, c, a, b, d} { for k, p := range [...]point{a, b, c, a, b, d} {
(*imd.tri)[j+k].Position = p.pos (*imd.tri)[j+k].Position = p.pos
(*imd.tri)[j+k].Color = p.col (*imd.tri)[j+k].Color = p.col
(*imd.tri)[j+k].Picture = p.pic (*imd.tri)[j+k].Picture = p.pic
@ -302,12 +316,15 @@ func (imd *IMDraw) fillRectangle() {
imd.applyMatrixAndMask(off) imd.applyMatrixAndMask(off)
imd.batch.Dirty() imd.batch.Dirty()
imd.restorePoints(points)
} }
func (imd *IMDraw) outlineRectangle(thickness float64) { func (imd *IMDraw) outlineRectangle(thickness float64) {
points := imd.getAndClearPoints() points := imd.getAndClearPoints()
if len(points) < 2 { if len(points) < 2 {
imd.restorePoints(points)
return return
} }
@ -323,12 +340,15 @@ func (imd *IMDraw) outlineRectangle(thickness float64) {
imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid) imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid)
imd.polyline(thickness, true) imd.polyline(thickness, true)
} }
imd.restorePoints(points)
} }
func (imd *IMDraw) fillPolygon() { func (imd *IMDraw) fillPolygon() {
points := imd.getAndClearPoints() points := imd.getAndClearPoints()
if len(points) < 3 { if len(points) < 3 {
imd.restorePoints(points)
return return
} }
@ -336,16 +356,19 @@ func (imd *IMDraw) fillPolygon() {
imd.tri.SetLen(imd.tri.Len() + 3*(len(points)-2)) 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 { for i, j := 1, off; i+1 < len(points); i, j = i+1, j+3 {
for k, p := range []point{points[0], points[i], points[i+1]} { for k, p := range [...]int{0, i, i + 1} {
(*imd.tri)[j+k].Position = p.pos tri := &(*imd.tri)[j+k]
(*imd.tri)[j+k].Color = p.col tri.Position = points[p].pos
(*imd.tri)[j+k].Picture = p.pic tri.Color = points[p].col
(*imd.tri)[j+k].Intensity = p.in tri.Picture = points[p].pic
tri.Intensity = points[p].in
} }
} }
imd.applyMatrixAndMask(off) imd.applyMatrixAndMask(off)
imd.batch.Dirty() imd.batch.Dirty()
imd.restorePoints(points)
} }
func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) { func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) {
@ -387,6 +410,8 @@ func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) {
imd.applyMatrixAndMask(off) imd.applyMatrixAndMask(off)
imd.batch.Dirty() imd.batch.Dirty()
} }
imd.restorePoints(points)
} }
func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness float64, doEndShape bool) { func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness float64, doEndShape bool) {
@ -485,12 +510,15 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
} }
} }
} }
imd.restorePoints(points)
} }
func (imd *IMDraw) polyline(thickness float64, closed bool) { func (imd *IMDraw) polyline(thickness float64, closed bool) {
points := imd.getAndClearPoints() points := imd.getAndClearPoints()
if len(points) == 0 { if len(points) == 0 {
imd.restorePoints(points)
return return
} }
if len(points) == 1 { if len(points) == 1 {
@ -521,6 +549,8 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
imd.pushPt(points[j].pos.Sub(normal), points[j]) imd.pushPt(points[j].pos.Sub(normal), points[j])
// middle points // middle points
// compute "previous" normal:
ijNormal := points[1].pos.Sub(points[0].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
for i := 0; i < len(points); i++ { for i := 0; i < len(points); i++ {
j, k := i+1, i+2 j, k := i+1, i+2
@ -536,7 +566,6 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
k %= len(points) k %= len(points)
} }
ijNormal := points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
jkNormal := points[k].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2) jkNormal := points[k].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
orientation := 1.0 orientation := 1.0
@ -567,6 +596,8 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
imd.pushPt(points[j].pos.Add(jkNormal), points[j]) imd.pushPt(points[j].pos.Add(jkNormal), points[j])
imd.pushPt(points[j].pos.Sub(jkNormal), points[j]) imd.pushPt(points[j].pos.Sub(jkNormal), points[j])
} }
// "next" normal becomes previous normal
ijNormal = jkNormal
} }
// last point // last point
@ -591,4 +622,6 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()-math.Pi) imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()-math.Pi)
} }
} }
imd.restorePoints(points)
} }

View File

@ -91,8 +91,13 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture {
// SetMatrix sets a Matrix that every point will be projected by. // SetMatrix sets a Matrix that every point will be projected by.
func (c *Canvas) SetMatrix(m pixel.Matrix) { func (c *Canvas) SetMatrix(m pixel.Matrix) {
for i := range m { // pixel.Matrix is 3x2 with an implicit 0, 0, 1 row after it. So
c.mat[i] = float32(m[i]) // [0] [2] [4] [0] [3] [6]
// [1] [3] [5] => [1] [4] [7]
// 0 0 1 0 0 1
// since all matrix ops are affine, the last row never changes, and we don't need to copy it
for i, j := range [...]int{0, 1, 3, 4, 6, 7} {
c.mat[j] = float32(m[i])
} }
} }
@ -218,6 +223,11 @@ func (c *Canvas) Texture() *glhf.Texture {
return c.gf.Texture() return c.gf.Texture()
} }
// Frame returns the underlying OpenGL Frame of this Canvas.
func (c *Canvas) Frame() *glhf.Frame {
return c.gf.frame
}
// SetPixels replaces the content of the Canvas with the provided pixels. The provided slice must be // SetPixels replaces the content of the Canvas with the provided pixels. The provided slice must be
// an alpha-premultiplied RGBA sequence of correct length (4 * width * height). // an alpha-premultiplied RGBA sequence of correct length (4 * width * height).
func (c *Canvas) SetPixels(pixels []uint8) { func (c *Canvas) SetPixels(pixels []uint8) {

View File

@ -103,15 +103,17 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) {
tx, ty = (*t)[i].Picture.XY() tx, ty = (*t)[i].Picture.XY()
in = (*t)[i].Intensity in = (*t)[i].Intensity
) )
gt.data[i*gt.vs.Stride()+0] = float32(px) s := gt.vs.Stride()
gt.data[i*gt.vs.Stride()+1] = float32(py) d := gt.data[i*s : i*s+9]
gt.data[i*gt.vs.Stride()+2] = float32(col.R) d[0] = float32(px)
gt.data[i*gt.vs.Stride()+3] = float32(col.G) d[1] = float32(py)
gt.data[i*gt.vs.Stride()+4] = float32(col.B) d[2] = float32(col.R)
gt.data[i*gt.vs.Stride()+5] = float32(col.A) d[3] = float32(col.G)
gt.data[i*gt.vs.Stride()+6] = float32(tx) d[4] = float32(col.B)
gt.data[i*gt.vs.Stride()+7] = float32(ty) d[5] = float32(col.A)
gt.data[i*gt.vs.Stride()+8] = float32(in) d[6] = float32(tx)
d[7] = float32(ty)
d[8] = float32(in)
} }
return return
} }