From 9e0c65d8dda80b08f5e1d709510f607cf5295472 Mon Sep 17 00:00:00 2001 From: faiface Date: Sat, 27 May 2017 19:19:31 +0200 Subject: [PATCH 01/14] remove profiling from typewriter example --- examples/typewriter/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/typewriter/main.go b/examples/typewriter/main.go index 820fb2c..d7691ff 100644 --- a/examples/typewriter/main.go +++ b/examples/typewriter/main.go @@ -13,7 +13,6 @@ import ( "github.com/faiface/pixel/pixelgl" "github.com/faiface/pixel/text" "github.com/golang/freetype/truetype" - "github.com/pkg/profile" "golang.org/x/image/colornames" "golang.org/x/image/font" "golang.org/x/image/font/gofont/gobold" @@ -317,6 +316,5 @@ func run() { } func main() { - defer profile.Start(profile.MemProfile).Stop() pixelgl.Run(run) } From e06acda99b99e2c9a1d884e489377588ebd662d9 Mon Sep 17 00:00:00 2001 From: faiface Date: Sun, 28 May 2017 00:06:29 +0200 Subject: [PATCH 02/14] minorly simplify typewriter example --- examples/typewriter/main.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/examples/typewriter/main.go b/examples/typewriter/main.go index d7691ff..24ee972 100644 --- a/examples/typewriter/main.go +++ b/examples/typewriter/main.go @@ -227,10 +227,7 @@ func (dl *dotlight) Draw(t pixel.Target, m pixel.Matrix) { dl.imd.Color = pixel.Alpha(0) for i := 0.0; i <= 32; i++ { angle := i * 2 * math.Pi / 32 - dl.imd.Push(dl.pos.Add(pixel.V( - math.Cos(angle)*dl.radius, - math.Sin(angle)*dl.radius, - ))) + dl.imd.Push(dl.pos.Add(pixel.V(dl.radius, 0).Rotated(angle))) } dl.imd.Polygon(0) dl.imd.Draw(t) From 4749e3ee7eda262a3d25d6d4c79fae9e53f2b284 Mon Sep 17 00:00:00 2001 From: faiface Date: Sun, 28 May 2017 18:44:30 +0200 Subject: [PATCH 03/14] add Canvas.Frame method --- pixelgl/canvas.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index 070a382..93065ea 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -218,6 +218,11 @@ func (c *Canvas) Texture() *glhf.Texture { return c.gf.Texture() } +// Frame return 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 // an alpha-premultiplied RGBA sequence of correct length (4 * width * height). func (c *Canvas) SetPixels(pixels []uint8) { From 3706d040ce09e9ac076111ce447fe36e6cd05ebe Mon Sep 17 00:00:00 2001 From: faiface Date: Sun, 28 May 2017 18:50:56 +0200 Subject: [PATCH 04/14] fix typo in doc --- pixelgl/canvas.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index 93065ea..a088350 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -218,7 +218,7 @@ func (c *Canvas) Texture() *glhf.Texture { return c.gf.Texture() } -// Frame return the underlying OpenGL Frame of this Canvas. +// Frame returns the underlying OpenGL Frame of this Canvas. func (c *Canvas) Frame() *glhf.Frame { return c.gf.frame } From c385b247b3a7e614d8a9a735bf24e16f9b75540a Mon Sep 17 00:00:00 2001 From: faiface Date: Tue, 30 May 2017 02:52:33 +0200 Subject: [PATCH 05/14] add 07 guide code --- .../intuitive.ttf | Bin 0 -> 49552 bytes .../07_typing_text_on_the_screen/main.go | 78 ++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 examples/guide/07_typing_text_on_the_screen/intuitive.ttf create mode 100644 examples/guide/07_typing_text_on_the_screen/main.go diff --git a/examples/guide/07_typing_text_on_the_screen/intuitive.ttf b/examples/guide/07_typing_text_on_the_screen/intuitive.ttf new file mode 100644 index 0000000000000000000000000000000000000000..9039d7b3e5e9bd7830b19102b1ba5d1c37599d0b GIT binary patch literal 49552 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 literal 0 HcmV?d00001 diff --git a/examples/guide/07_typing_text_on_the_screen/main.go b/examples/guide/07_typing_text_on_the_screen/main.go new file mode 100644 index 0000000..caaa0da --- /dev/null +++ b/examples/guide/07_typing_text_on_the_screen/main.go @@ -0,0 +1,78 @@ +package main + +import ( + "io/ioutil" + "os" + "time" + + "github.com/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) +} From 781c44f1191d2a481f3e9610302e5503d2707c88 Mon Sep 17 00:00:00 2001 From: faiface Date: Tue, 30 May 2017 02:53:24 +0200 Subject: [PATCH 06/14] add text tutorial link to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index ac347e9..e1553de 100644 --- a/README.md +++ b/README.md @@ -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) - [Drawing efficiently with Batch](https://github.com/faiface/pixel/wiki/Drawing-efficiently-with-Batch) - [Drawing shapes with IMDraw](https://github.com/faiface/pixel/wiki/Drawing-shapes-with-IMDraw) +- [Typing text on the screen](https://github.com/faiface/pixel/wiki/Typing-text-on-the-screen) ## Examples From 4b7553cd73d038f46306a2f6da7a55c5c00a4da3 Mon Sep 17 00:00:00 2001 From: faiface Date: Tue, 30 May 2017 13:30:09 +0200 Subject: [PATCH 07/14] add maze generator community example --- examples/community/maze/LICENSE | 21 ++ examples/community/maze/README.md | 19 ++ examples/community/maze/maze-generator.go | 317 ++++++++++++++++++++++ examples/community/maze/screenshot.png | Bin 0 -> 14155 bytes examples/community/maze/stack/stack.go | 86 ++++++ 5 files changed, 443 insertions(+) create mode 100644 examples/community/maze/LICENSE create mode 100644 examples/community/maze/README.md create mode 100644 examples/community/maze/maze-generator.go create mode 100644 examples/community/maze/screenshot.png create mode 100644 examples/community/maze/stack/stack.go diff --git a/examples/community/maze/LICENSE b/examples/community/maze/LICENSE new file mode 100644 index 0000000..1326e36 --- /dev/null +++ b/examples/community/maze/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 stephen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/examples/community/maze/README.md b/examples/community/maze/README.md new file mode 100644 index 0000000..27b344a --- /dev/null +++ b/examples/community/maze/README.md @@ -0,0 +1,19 @@ +# Maze generator in Go + +Created by [Stephen Chavez](https://github.com/redragonx) + +This uses the game engine: Pixel. Install it here: https://github.com/faiface/pixel + +I made this to improve my understanding of Go and some game concepts with some basic maze generating algorithms. + +Controls: Press 'R' to restart the maze. + +Optional command-line arguments: `go run ./maze-generator.go` + - `-w` sets the maze's width in pixels. + - `-h` sets the maze's height in pixels. + - `-c` sets the maze cell's size in pixels. + +Code based on the Recursive backtracker algorithm. +- https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker + +![Screenshot](screenshot.png) \ No newline at end of file diff --git a/examples/community/maze/maze-generator.go b/examples/community/maze/maze-generator.go new file mode 100644 index 0000000..a8b09f8 --- /dev/null +++ b/examples/community/maze/maze-generator.go @@ -0,0 +1,317 @@ +package main + +// Code based on the Recursive backtracker algorithm. +// https://en.wikipedia.org/wiki/Maze_generation_algorithm#Recursive_backtracker +// See https://youtu.be/HyK_Q5rrcr4 as an example +// YouTube example ported to Go for the Pixel library. + +// Created by Stephen Chavez + +import ( + "crypto/rand" + "errors" + "flag" + "fmt" + "math/big" + "time" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/examples/community/maze/stack" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" + + "github.com/pkg/profile" + "golang.org/x/image/colornames" +) + +var visitedColor = pixel.RGB(0.5, 0, 1).Mul(pixel.Alpha(0.35)) +var hightlightColor = pixel.RGB(0.3, 0, 0).Mul(pixel.Alpha(0.45)) +var debug = false + +type cell struct { + walls [4]bool // Wall order: top, right, bottom, left + + row int + col int + visited bool +} + +func (c *cell) Draw(imd *imdraw.IMDraw, wallSize int) { + drawCol := c.col * wallSize // x + drawRow := c.row * wallSize // y + + imd.Color = colornames.White + if c.walls[0] { + // top line + imd.Push(pixel.V(float64(drawCol), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow))) + imd.Line(3) + } + if c.walls[1] { + // right Line + imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow)), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize))) + imd.Line(3) + } + if c.walls[2] { + // bottom line + imd.Push(pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow+wallSize))) + imd.Line(3) + } + if c.walls[3] { + // left line + imd.Push(pixel.V(float64(drawCol), float64(drawRow+wallSize)), pixel.V(float64(drawCol), float64(drawRow))) + imd.Line(3) + } + imd.EndShape = imdraw.SharpEndShape + + if c.visited { + imd.Color = visitedColor + imd.Push(pixel.V(float64(drawCol), (float64(drawRow))), pixel.V(float64(drawCol+wallSize), float64(drawRow+wallSize))) + imd.Rectangle(0) + } +} + +func (c *cell) GetNeighbors(grid []*cell, cols int, rows int) ([]*cell, error) { + neighbors := []*cell{} + j := c.row + i := c.col + + top, _ := getCellAt(i, j-1, cols, rows, grid) + right, _ := getCellAt(i+1, j, cols, rows, grid) + bottom, _ := getCellAt(i, j+1, cols, rows, grid) + left, _ := getCellAt(i-1, j, cols, rows, grid) + + if top != nil && !top.visited { + neighbors = append(neighbors, top) + } + if right != nil && !right.visited { + neighbors = append(neighbors, right) + } + if bottom != nil && !bottom.visited { + neighbors = append(neighbors, bottom) + } + if left != nil && !left.visited { + neighbors = append(neighbors, left) + } + + if len(neighbors) == 0 { + return nil, errors.New("We checked all cells...") + } + return neighbors, nil +} + +func (c *cell) GetRandomNeighbor(grid []*cell, cols int, rows int) (*cell, error) { + neighbors, err := c.GetNeighbors(grid, cols, rows) + if neighbors == nil { + return nil, err + } + nBig, err := rand.Int(rand.Reader, big.NewInt(int64(len(neighbors)))) + if err != nil { + panic(err) + } + randomIndex := nBig.Int64() + return neighbors[randomIndex], nil +} + +func (c *cell) hightlight(imd *imdraw.IMDraw, wallSize int) { + x := c.col * wallSize + y := c.row * wallSize + + imd.Color = hightlightColor + imd.Push(pixel.V(float64(x), float64(y)), pixel.V(float64(x+wallSize), float64(y+wallSize))) + imd.Rectangle(0) +} + +func newCell(col int, row int) *cell { + newCell := new(cell) + newCell.row = row + newCell.col = col + + for i := range newCell.walls { + newCell.walls[i] = true + } + return newCell +} + +// Creates the inital maze slice for use. +func initGrid(cols, rows int) []*cell { + grid := []*cell{} + for j := 0; j < rows; j++ { + for i := 0; i < cols; i++ { + newCell := newCell(i, j) + grid = append(grid, newCell) + } + } + return grid +} + +func setupMaze(cols, rows int) ([]*cell, *stack.Stack, *cell) { + // Make an empty grid + grid := initGrid(cols, rows) + backTrackStack := stack.NewStack(len(grid)) + currentCell := grid[0] + + return grid, backTrackStack, currentCell +} + +func cellIndex(i, j, cols, rows int) int { + if i < 0 || j < 0 || i > cols-1 || j > rows-1 { + return -1 + } + return i + j*cols +} + +func getCellAt(i int, j int, cols int, rows int, grid []*cell) (*cell, error) { + possibleIndex := cellIndex(i, j, cols, rows) + + if possibleIndex == -1 { + return nil, fmt.Errorf("cellIndex: CellIndex is a negative number %d", possibleIndex) + } + return grid[possibleIndex], nil +} + +func removeWalls(a *cell, b *cell) { + x := a.col - b.col + + if x == 1 { + a.walls[3] = false + b.walls[1] = false + } else if x == -1 { + a.walls[1] = false + b.walls[3] = false + } + + y := a.row - b.row + + if y == 1 { + a.walls[0] = false + b.walls[2] = false + } else if y == -1 { + a.walls[2] = false + b.walls[0] = false + } +} + +func run() { + // unsiged integers, because easier parsing error checks. + // We must convert these to intergers, as done below... + uScreenWidth, uScreenHeight, uWallSize := parseArgs() + + var ( + // In pixels + // Defualt is 800x800x40 = 20x20 wallgrid + screenWidth = int(uScreenWidth) + screenHeight = int(uScreenHeight) + wallSize = int(uWallSize) + + frames = 0 + second = time.Tick(time.Second) + + grid = []*cell{} + cols = screenWidth / wallSize + rows = screenHeight / wallSize + currentCell = new(cell) + backTrackStack = stack.NewStack(1) + ) + + // Set game FPS manually + fps := time.Tick(time.Second / 60) + + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks! - Maze example", + Bounds: pixel.R(0, 0, float64(screenHeight), float64(screenWidth)), + } + + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + grid, backTrackStack, currentCell = setupMaze(cols, rows) + + gridIMDraw := imdraw.New(nil) + + for !win.Closed() { + if win.JustReleased(pixelgl.KeyR) { + fmt.Println("R pressed") + grid, backTrackStack, currentCell = setupMaze(cols, rows) + } + + win.Clear(colornames.Gray) + gridIMDraw.Clear() + + for i := range grid { + grid[i].Draw(gridIMDraw, wallSize) + } + + // step 1 + // Make the initial cell the current cell and mark it as visited + currentCell.visited = true + currentCell.hightlight(gridIMDraw, wallSize) + + // step 2.1 + // If the current cell has any neighbours which have not been visited + // Choose a random unvisited cell + nextCell, _ := currentCell.GetRandomNeighbor(grid, cols, rows) + if nextCell != nil && !nextCell.visited { + // step 2.2 + // Push the current cell to the stack + backTrackStack.Push(currentCell) + + // step 2.3 + // Remove the wall between the current cell and the chosen cell + + removeWalls(currentCell, nextCell) + + // step 2.4 + // Make the chosen cell the current cell and mark it as visited + nextCell.visited = true + currentCell = nextCell + } else if backTrackStack.Len() > 0 { + currentCell = backTrackStack.Pop().(*cell) + } + + gridIMDraw.Draw(win) + win.Update() + <-fps + updateFPSDisplay(win, &cfg, &frames, grid, second) + } +} + +// Parses the maze arguments, all of them are optional. +// Uses uint as implicit error checking :) +func parseArgs() (uint, uint, uint) { + var mazeWidthPtr = flag.Uint("w", 800, "w sets the maze's width in pixels.") + var mazeHeightPtr = flag.Uint("h", 800, "h sets the maze's height in pixels.") + var wallSizePtr = flag.Uint("c", 40, "c sets the maze cell's size in pixels.") + + flag.Parse() + + // If these aren't default values AND if they're not the same values. + // We should warn the user that the maze will look funny. + if *mazeWidthPtr != 800 || *mazeHeightPtr != 800 { + if *mazeWidthPtr != *mazeHeightPtr { + fmt.Printf("WARNING: maze width: %d and maze height: %d don't match. \n", *mazeWidthPtr, *mazeHeightPtr) + fmt.Println("Maze will look funny because the maze size is bond to the window size!") + } + } + + return *mazeWidthPtr, *mazeHeightPtr, *wallSizePtr +} + +func updateFPSDisplay(win *pixelgl.Window, cfg *pixelgl.WindowConfig, frames *int, grid []*cell, second <-chan time.Time) { + *frames++ + select { + case <-second: + win.SetTitle(fmt.Sprintf("%s | FPS: %d with %d Cells", cfg.Title, *frames, len(grid))) + *frames = 0 + default: + } + +} + +func main() { + if debug { + defer profile.Start().Stop() + } + pixelgl.Run(run) +} diff --git a/examples/community/maze/screenshot.png b/examples/community/maze/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ce5bfd2f89336e571b304e1dc224af9f2e231013 GIT binary patch literal 14155 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 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 +} From 0358330d3b3f867f390a773e2705d243a4629ba3 Mon Sep 17 00:00:00 2001 From: Seebs Date: Sun, 4 Jun 2017 12:55:43 -0500 Subject: [PATCH 08/14] The initializer is surprisingly expensive. Removing the call to Alpha(1) and replacing it with an inline definition produces measurable improvements. Replacing each instance of ZV with Vec{} further improves things. We keep an inline RGBA because there are circumstances (mostly when using pictures) where we don't want to have to set colors to get default behavior. For a fairly triangle-heavy thing, this reduces time spent in SetLen from something over 10% of execution time to around 2.5% of execution time. --- data.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data.go b/data.go index 4e6c528..c941241 100644 --- a/data.go +++ b/data.go @@ -45,7 +45,7 @@ func (td *TrianglesData) SetLen(len int) { Color RGBA Picture Vec Intensity float64 - }{ZV, Alpha(1), ZV, 0}) + }{Color: RGBA{1, 1, 1, 1}}) } } if len < td.Len() { From 34cdd8729b0915aa422c5a7c84c29e10ec2c572d Mon Sep 17 00:00:00 2001 From: Seebs Date: Mon, 5 Jun 2017 18:54:53 -0500 Subject: [PATCH 09/14] Simplify Matrix math, use 6-value affine matrixes. It turns out that affine matrices are much simpler than the 3x3 matrices they imply, and we can use this to dramatically streamline some code. For a test program, this was about a 50% gain in frame rate just from the cost of the applyMatrixAndMask calls in imdraw, which were calling matrix.Project() many times. Simplifying matrix.Project, alone, got a nearly 50% frame rate boost! Also modify pixelgl's SetMatrix to copy the six values of a 3x2 Affine into the corresponding locations of a 3x3 matrix. --- geometry.go | 75 ++++++++++++++++++++++++++--------------------- pixelgl/canvas.go | 11 +++++-- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/geometry.go b/geometry.go index f934839..928c9c5 100644 --- a/geometry.go +++ b/geometry.go @@ -3,8 +3,6 @@ package pixel import ( "fmt" "math" - - "github.com/go-gl/mathgl/mgl64" ) // 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. // // 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 // 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. -var IM = Matrix(mgl64.Ident3()) +var IM = Matrix{1, 0, 0, 1, 0, 0} // String returns a string representation of the Matrix. // // 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 { 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], + "Matrix(%v %v %v | %v %v %v)", + m[0], m[2], m[4], + m[1], m[3], m[5], ) } // Moved moves everything by the delta vector. func (m Matrix) Moved(delta Vec) Matrix { - m3 := mgl64.Mat3(m) - m3 = mgl64.Translate2D(delta.XY()).Mul3(m3) - return Matrix(m3) + m[4], m[5] = m[4]+delta.X, m[5]+delta.Y + return m } // ScaledXY scales everything around a given point by the scale factor in each axis respectively. func (m Matrix) ScaledXY(around Vec, scale Vec) Matrix { - m3 := mgl64.Mat3(m) - m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3) - m3 = mgl64.Scale2D(scale.XY()).Mul3(m3) - m3 = mgl64.Translate2D(around.XY()).Mul3(m3) - return Matrix(m3) + m[4], m[5] = m[4]-around.X, m[5]-around.Y + m[0], m[2], m[4] = m[0]*scale.X, m[2]*scale.X, m[4]*scale.X + m[1], m[3], m[5] = m[1]*scale.Y, m[3]*scale.Y, m[5]*scale.Y + m[4], m[5] = m[4]+around.X, m[5]+around.Y + return m } // Scaled scales everything around a given point by the scale factor. @@ -302,36 +303,44 @@ func (m Matrix) Scaled(around Vec, scale float64) Matrix { // Rotated rotates everything around a given point by the given angle in radians. func (m Matrix) Rotated(around Vec, angle float64) Matrix { - m3 := mgl64.Mat3(m) - m3 = mgl64.Translate2D(around.Scaled(-1).XY()).Mul3(m3) - m3 = mgl64.Rotate3DZ(angle).Mul3(m3) - m3 = mgl64.Translate2D(around.XY()).Mul3(m3) - return Matrix(m3) + sint, cost := math.Sincos(angle) + m[4], m[5] = m[4]-around.X, m[5]-around.Y + m = m.Chained(Matrix{cost, sint, -sint, cost, 0, 0}) + m[4], m[5] = m[4]+around.X, m[5]+around.Y + return m } // Chained adds another Matrix to this one. All tranformations by the next Matrix will be applied // after the transformations of this Matrix. func (m Matrix) Chained(next Matrix) Matrix { - m3 := mgl64.Mat3(m) - m3 = mgl64.Mat3(next).Mul3(m3) - return Matrix(m3) + return Matrix{ + m[0]*next[0] + m[2]*next[1], + 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. // // Time complexity is O(1). func (m Matrix) Project(u Vec) Vec { - m3 := mgl64.Mat3(m) - proj := m3.Mul3x1(mgl64.Vec3{u.X, u.Y, 1}) - return V(proj.X(), proj.Y()) + return Vec{X: m[0]*u.X + m[2]*u.Y + m[4], Y: m[1]*u.X + m[3]*u.Y + m[5]} } // 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). func (m Matrix) Unproject(u Vec) Vec { - m3 := mgl64.Mat3(m) - inv := m3.Inv() - unproj := inv.Mul3x1(mgl64.Vec3{u.X, u.Y, 1}) - return V(unproj.X(), unproj.Y()) + d := (m[0] * m[3]) - (m[1] * m[2]) + u.X, u.Y = (u.X-m[4])/d, (u.Y-m[5])/d + return Vec{u.X*m[3] - u.Y*m[1], u.Y*m[0] - u.X*m[2]} } diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index a088350..b7b5cfc 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -90,9 +90,16 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture { } // SetMatrix sets a Matrix that every point will be projected by. +// pixel.Matrix is 3x2 with an implicit 0, 0, 1 row after it. So +// [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 +// func (c *Canvas) SetMatrix(m pixel.Matrix) { - for i := range m { - c.mat[i] = float32(m[i]) + for i, j := range [6]int{ 0, 1, 3, 4, 6, 7} { + c.mat[j] = float32(m[i]) } } From 9a7ab1c6b0d1aa611d73ced78ef10dbb44d6bf80 Mon Sep 17 00:00:00 2001 From: Seebs Date: Mon, 5 Jun 2017 19:37:53 -0500 Subject: [PATCH 10/14] use point pool For internal operations (anything using getAndClearPoints), there's a pretty good chance that the operation will repeatedly invoke something like fillPolygon(), meaning that it needs to push "a few" points and then invoke something that uses those points. So, we add a slice for containing spare slices of points, and on the way out of each such function, shove the current imd.points (as used inside that function) onto a stack, and set imd.points to [0:0] of the thing it was called with. Performance goes from 11-13fps to 17-18fps on my test case. --- imdraw/imdraw.go | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index 626cb2f..2fe1fad 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -52,6 +52,7 @@ type IMDraw struct { EndShape EndShape points []point + pool [][]point matrix pixel.Matrix mask pixel.RGBA @@ -109,7 +110,7 @@ func (imd *IMDraw) Clear() { // // This does not affect matrix and color mask set by SetMatrix and SetColorMask. func (imd *IMDraw) Reset() { - imd.points = nil + imd.points = imd.points[:0] imd.Color = pixel.Alpha(1) imd.Picture = pixel.ZV imd.Intensity = 0 @@ -256,10 +257,22 @@ func (imd *IMDraw) EllipseArc(radius pixel.Vec, low, high, thickness float64) { func (imd *IMDraw) getAndClearPoints() []point { points := imd.points - imd.points = nil + // 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] + imd.pool = imd.pool[0:pos] + } else { + imd.points = nil + } 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) { for i := range (*imd.tri)[off:] { (*imd.tri)[off+i].Position = imd.matrix.Project((*imd.tri)[off+i].Position) @@ -271,6 +284,7 @@ func (imd *IMDraw) fillRectangle() { points := imd.getAndClearPoints() if len(points) < 2 { + imd.restorePoints(points) return } @@ -302,12 +316,14 @@ func (imd *IMDraw) fillRectangle() { imd.applyMatrixAndMask(off) imd.batch.Dirty() + imd.restorePoints(points) } func (imd *IMDraw) outlineRectangle(thickness float64) { points := imd.getAndClearPoints() if len(points) < 2 { + imd.restorePoints(points) return } @@ -323,12 +339,14 @@ func (imd *IMDraw) outlineRectangle(thickness float64) { imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid) imd.polyline(thickness, true) } + imd.restorePoints(points) } func (imd *IMDraw) fillPolygon() { points := imd.getAndClearPoints() if len(points) < 3 { + imd.restorePoints(points) return } @@ -346,6 +364,7 @@ func (imd *IMDraw) fillPolygon() { imd.applyMatrixAndMask(off) imd.batch.Dirty() + imd.restorePoints(points) } func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) { @@ -387,6 +406,7 @@ func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) { imd.applyMatrixAndMask(off) imd.batch.Dirty() } + imd.restorePoints(points) } func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness float64, doEndShape bool) { @@ -485,12 +505,14 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa } } } + imd.restorePoints(points) } func (imd *IMDraw) polyline(thickness float64, closed bool) { points := imd.getAndClearPoints() if len(points) == 0 { + imd.restorePoints(points) return } if len(points) == 1 { @@ -591,4 +613,5 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normal.Angle(), normal.Angle()-math.Pi) } } + imd.restorePoints(points) } From 918031892a487e02b46755913bdc75fb0c5a1776 Mon Sep 17 00:00:00 2001 From: Seebs Date: Mon, 5 Jun 2017 19:46:16 -0500 Subject: [PATCH 11/14] smaller imdraw optimizations For polyline, don't compute each normal twice; when we're going through a line, the "next" normal for segment N is always the "previous" normal for segment N+1, and we can compute fewer of them. --- imdraw/imdraw.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index 2fe1fad..0d40452 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -543,6 +543,8 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { imd.pushPt(points[j].pos.Sub(normal), points[j]) // 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++ { j, k := i+1, i+2 @@ -558,7 +560,6 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { 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) orientation := 1.0 @@ -589,6 +590,8 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) { imd.pushPt(points[j].pos.Add(jkNormal), points[j]) imd.pushPt(points[j].pos.Sub(jkNormal), points[j]) } + // "next" normal becomes previous normal + ijNormal = jkNormal } // last point From fc858bff4d66c35184fe63d179d39927329fa144 Mon Sep 17 00:00:00 2001 From: Seebs Date: Mon, 5 Jun 2017 20:12:35 -0500 Subject: [PATCH 12/14] Reduce copying in fillPolygon A slice of points means copying every point into the slice, then copying every point's data from the slice to TrianglesData. An array of indicies lets the compiler make better choices. --- imdraw/imdraw.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index 0d40452..e5a8c95 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -354,11 +354,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 { - 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 + for k, p := range []int{0, i, i + 1} { + tri := &(*imd.tri)[j+k] + tri.Position = points[p].pos + tri.Color = points[p].col + tri.Picture = points[p].pic + tri.Intensity = points[p].in } } From 7215265523612fdda6a2429b7409f9da76a83f76 Mon Sep 17 00:00:00 2001 From: Seebs Date: Fri, 9 Jun 2017 00:07:08 -0500 Subject: [PATCH 13/14] Don't duplicate computations in gltriangles.go The computation including a call to Stride() can't be optimized away safely because the compiler can't tell that Stride() is effectively constant, but we know it won't change so we can make a slice pointing at that part of the array. CPU time for updateData goes from 26.35% to 18.65% in my test case. --- pixelgl/gltriangles.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pixelgl/gltriangles.go b/pixelgl/gltriangles.go index 64b7355..bf7a895 100644 --- a/pixelgl/gltriangles.go +++ b/pixelgl/gltriangles.go @@ -103,15 +103,17 @@ func (gt *GLTriangles) updateData(t pixel.Triangles) { tx, ty = (*t)[i].Picture.XY() in = (*t)[i].Intensity ) - gt.data[i*gt.vs.Stride()+0] = float32(px) - gt.data[i*gt.vs.Stride()+1] = float32(py) - gt.data[i*gt.vs.Stride()+2] = float32(col.R) - gt.data[i*gt.vs.Stride()+3] = float32(col.G) - gt.data[i*gt.vs.Stride()+4] = float32(col.B) - gt.data[i*gt.vs.Stride()+5] = float32(col.A) - gt.data[i*gt.vs.Stride()+6] = float32(tx) - gt.data[i*gt.vs.Stride()+7] = float32(ty) - gt.data[i*gt.vs.Stride()+8] = float32(in) + s := gt.vs.Stride() + d := gt.data[i*s : i*s+9] + d[0] = float32(px) + d[1] = float32(py) + d[2] = float32(col.R) + d[3] = float32(col.G) + d[4] = float32(col.B) + d[5] = float32(col.A) + d[6] = float32(tx) + d[7] = float32(ty) + d[8] = float32(in) } return } From 6b9ea45e96f5928beb21d03436525cb4ae6a2e2e Mon Sep 17 00:00:00 2001 From: faiface Date: Fri, 9 Jun 2017 18:13:05 +0200 Subject: [PATCH 14/14] minor, mostly stylistic, changes --- geometry.go | 10 ++++------ imdraw/imdraw.go | 14 ++++++++++---- pixelgl/canvas.go | 14 ++++++-------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/geometry.go b/geometry.go index 928c9c5..13a574d 100644 --- a/geometry.go +++ b/geometry.go @@ -263,7 +263,7 @@ func (r Rect) Union(s Rect) Rect { // Layout is: // [0] [2] [4] // [1] [3] [5] -// 0 0 1 [implicit row] +// 0 0 1 (implicit row) type Matrix [6]float64 // IM stands for identity matrix. Does nothing, no transformation. @@ -332,11 +332,9 @@ func (m Matrix) Project(u Vec) Vec { // 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. +// 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). func (m Matrix) Unproject(u Vec) Vec { diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go index e5a8c95..f7ce769 100644 --- a/imdraw/imdraw.go +++ b/imdraw/imdraw.go @@ -260,8 +260,8 @@ func (imd *IMDraw) getAndClearPoints() []point { // 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] - imd.pool = imd.pool[0:pos] + imd.points = imd.pool[pos][:0] + imd.pool = imd.pool[:pos] } else { imd.points = nil } @@ -306,7 +306,7 @@ func (imd *IMDraw) fillRectangle() { 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].Color = p.col (*imd.tri)[j+k].Picture = p.pic @@ -316,6 +316,7 @@ func (imd *IMDraw) fillRectangle() { imd.applyMatrixAndMask(off) imd.batch.Dirty() + imd.restorePoints(points) } @@ -339,6 +340,7 @@ func (imd *IMDraw) outlineRectangle(thickness float64) { imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid) imd.polyline(thickness, true) } + imd.restorePoints(points) } @@ -354,7 +356,7 @@ 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 { - for k, p := range []int{0, i, i + 1} { + for k, p := range [...]int{0, i, i + 1} { tri := &(*imd.tri)[j+k] tri.Position = points[p].pos tri.Color = points[p].col @@ -365,6 +367,7 @@ func (imd *IMDraw) fillPolygon() { imd.applyMatrixAndMask(off) imd.batch.Dirty() + imd.restorePoints(points) } @@ -407,6 +410,7 @@ func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) { imd.applyMatrixAndMask(off) imd.batch.Dirty() } + imd.restorePoints(points) } @@ -506,6 +510,7 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa } } } + imd.restorePoints(points) } @@ -617,5 +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.restorePoints(points) } diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index b7b5cfc..b8e4057 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -90,15 +90,13 @@ func (c *Canvas) MakePicture(p pixel.Picture) pixel.TargetPicture { } // SetMatrix sets a Matrix that every point will be projected by. -// pixel.Matrix is 3x2 with an implicit 0, 0, 1 row after it. So -// [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 -// func (c *Canvas) SetMatrix(m pixel.Matrix) { - for i, j := range [6]int{ 0, 1, 3, 4, 6, 7} { + // pixel.Matrix is 3x2 with an implicit 0, 0, 1 row after it. So + // [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]) } }