├── .gitignore ├── README.md ├── bouncing ├── README.md ├── bouncing-collisions.go ├── bouncing-gradient.go ├── bouncing-particles.go ├── bouncing-triangle.go └── bouncing.go ├── doomfire ├── README.md └── doomfire.go ├── double-pendulum ├── README.md └── double-pendulum.go ├── drawer.go ├── epigenetic └── the-magic-hand-of-chance │ └── the-magic-hand-of-chance.go ├── julia.go ├── metaballs ├── README.md └── metaballs.go ├── minimal.go ├── particles ├── .gitignore ├── README.md ├── attraction-repulsion │ ├── README.md │ └── attraction-repulsion.go ├── boids │ ├── .gitignore │ ├── boids-jerky-movement.go │ ├── boids-liquid-borders.go │ ├── boids-single-color-liquid-borders.go │ ├── boids-walls.go │ ├── steps │ │ ├── .gitignore │ │ ├── step01.go │ │ ├── step02.go │ │ ├── step03.go │ │ ├── step04.go │ │ ├── step05.go │ │ ├── step06.go │ │ ├── step07.go │ │ ├── step08.go │ │ ├── step09.go │ │ ├── step10.go │ │ ├── step11.go │ │ ├── step12.go │ │ ├── step13.go │ │ ├── step14.go │ │ ├── step15.go │ │ ├── step16.go │ │ ├── step17.go │ │ └── step18.go │ └── variations │ │ ├── .gitignore │ │ ├── boids-bouncing-2.go │ │ ├── boids-bouncing.go │ │ ├── boids-crashers.go │ │ ├── boids-groups.go │ │ ├── boids-liquid.go │ │ ├── boids-neighbors.go │ │ └── boids-snakes.go ├── falling-red.go ├── fireworks.go ├── particles.go ├── small-particles.go └── snow.go ├── plasma.go ├── pong ├── README.md ├── pong-with-sound │ ├── README.md │ ├── pong-with-sound.go │ └── sound │ │ ├── .gitignore │ │ ├── beeep.wav │ │ ├── convert.sh │ │ ├── peeeeeep.wav │ │ └── plop.wav └── pong.go ├── popcorn └── popcorn.go ├── raycaster ├── README.md ├── raycaster-textured-floor.go ├── raycaster-textured-walls.go └── raycaster-untextured.go ├── starfield ├── README.md └── starfield.go ├── tree ├── .gitignore ├── README.md ├── lerp-mask-color-states-rgb-gradient.go ├── lerp-mask-color-states.go ├── steps │ └── .gitignore └── tree.go ├── tunnel ├── .gitignore ├── README.md ├── plasma-tunnel.go └── xor-tunnel.go ├── warping ├── README.md ├── cuttlefish.jpg ├── flower.png ├── smokestack.jpg ├── steps │ ├── step01.go │ ├── step02.go │ ├── step03.go │ ├── step04.go │ ├── step05.go │ ├── step06.go │ └── step07.go ├── variations │ ├── good-looking-offset-rendering.go │ └── yet-another-good-looking-offset-rendering-2.go └── warping.go ├── water-ripple ├── README.md └── water-ripple.go └── xor.go /.gitignore: -------------------------------------------------------------------------------- 1 | drawer 2 | julia 3 | minimal 4 | plasma 5 | xor 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments 2 | 3 | These are some of my experiments with the [pixel library](https://github.com/faiface/pixel) 4 | 5 | ## Installation 6 | 7 | Just go get it: 8 | 9 | ``` 10 | go get -u -d github.com/peterhellberg/pixel-experiments 11 | ``` 12 | 13 | ## Experiments 14 | 15 | ### [bouncing](/bouncing) 16 | 17 | ![bouncing](https://user-images.githubusercontent.com/565124/32401910-7cd87fb2-c119-11e7-8121-7fb46e5e11a8.gif) 18 | ![bouncing](https://user-images.githubusercontent.com/565124/32391018-da783e62-c0d0-11e7-9981-07fcbd946432.gif) 19 | 20 | ### [double-pendulum](/double-pendulum) 21 | 22 | ![double-pendulum](https://user-images.githubusercontent.com/565124/36648903-27c31850-1a99-11e8-8538-76fe63368185.gif) 23 | 24 | ### [metaballs](/metaballs) 25 | 26 | ![metaballs](https://user-images.githubusercontent.com/565124/31692148-ec0a23a6-b398-11e7-9d04-6f3b1f01393d.gif) 27 | ![metaballs](https://user-images.githubusercontent.com/565124/31692972-7575c6a6-b39c-11e7-9de1-1d1c9a04e617.gif) 28 | 29 | ### [particles](/particles) 30 | 31 | ![particles](https://user-images.githubusercontent.com/565124/28490620-e47474c4-6ede-11e7-856f-00aa72ca6aa2.gif) 32 | ![particles](https://user-images.githubusercontent.com/565124/28492208-29f66672-6eff-11e7-8222-0b25fa9539e6.gif) 33 | 34 | ### [pong](/pong) 35 | 36 | ![pong](https://user-images.githubusercontent.com/565124/35081700-f416c566-fc15-11e7-83d9-1fe349121994.png) 37 | 38 | ### [raycaster](/raycaster) 39 | 40 | ![raycaster animation](https://user-images.githubusercontent.com/565124/31828029-798e6620-b5b9-11e7-96b7-fda540755745.gif) 41 | 42 | ![raycaster](https://user-images.githubusercontent.com/565124/31826180-a464ef40-b5b4-11e7-9b74-57d2b67b29b4.png) 43 | 44 | ### [starfield](/starfield) 45 | 46 | ![starfield](https://user-images.githubusercontent.com/565124/32411599-a5fcba72-c1df-11e7-8730-a570470a4eee.gif) 47 | 48 | ### [tree](/tree) 49 | 50 | ![tree animation](https://user-images.githubusercontent.com/565124/29730798-1aae495c-89e2-11e7-8071-3359f3c74088.gif) 51 | 52 | ![tree](https://user-images.githubusercontent.com/565124/29733012-84136aa4-89eb-11e7-98a7-7f60b7ba6399.png) 53 | ![tree](https://user-images.githubusercontent.com/565124/29735431-36e99682-89f9-11e7-9027-99a0f06b0ff1.png) 54 | 55 | ### [tunnel](/tunnel) 56 | 57 | ![plasma-tunnel](https://cloud.githubusercontent.com/assets/565124/25528930/48bc195c-2c20-11e7-8db8-d3b01b4a8903.gif) 58 | 59 | ![xor-tunnel](https://cloud.githubusercontent.com/assets/565124/25475885/8f018e6c-2b38-11e7-9a9e-9ca281f99c1b.png) 60 | 61 | ### [warping](/warping) 62 | 63 | ![Warping](https://user-images.githubusercontent.com/565124/30237177-f138eed2-952c-11e7-9a13-02319e8d18aa.png) 64 | 65 | ### [xor](/xor.go) 66 | 67 | ![XOR](https://assets.c7.se/screenshots/xor-20170426-001349.png) 68 | 69 | ### [plasma](/plasma.go) 70 | 71 | ![Plasma animation](http://assets.c7.se/viz/plasma-progress-010.gif) 72 | 73 | ### [julia](/julia.go) 74 | 75 | ![Julia](https://user-images.githubusercontent.com/565124/27253135-194ee39a-536f-11e7-9a0e-23dc3f71f994.gif) 76 | ![Julia](https://user-images.githubusercontent.com/565124/27253145-36513b8c-536f-11e7-9de4-713777101881.png) 77 | ![Julia](https://user-images.githubusercontent.com/565124/27253409-6892a63a-5374-11e7-8887-4d090c18e107.png) 78 | 79 | ## License (MIT) 80 | 81 | Copyright (c) 2017-2018 [Peter Hellberg](https://c7.se/) 82 | 83 | > Permission is hereby granted, free of charge, to any person obtaining 84 | > a copy of this software and associated documentation files (the 85 | > "Software"), to deal in the Software without restriction, including 86 | > without limitation the rights to use, copy, modify, merge, publish, 87 | > distribute, sublicense, and/or sell copies of the Software, and to 88 | > permit persons to whom the Software is furnished to do so, subject to 89 | > the following conditions: 90 | 91 | > The above copyright notice and this permission notice shall be 92 | > included in all copies or substantial portions of the Software. 93 | 94 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 95 | > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 96 | > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 97 | > NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 98 | > LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 99 | > OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 100 | > WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 101 | -------------------------------------------------------------------------------- /bouncing/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/bouncing 2 | 3 | https://gist.github.com/peterhellberg/674f32a15a7d2d249e634ce781f333e8 4 | 5 | ![](https://user-images.githubusercontent.com/565124/32401910-7cd87fb2-c119-11e7-8121-7fb46e5e11a8.gif) 6 | 7 | ![](https://user-images.githubusercontent.com/565124/32391018-da783e62-c0d0-11e7-9981-07fcbd946432.gif) 8 | 9 | ![](https://user-images.githubusercontent.com/565124/32398320-e8d36752-c0ee-11e7-97cf-4c5da91d9727.png) 10 | -------------------------------------------------------------------------------- /bouncing/bouncing-collisions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "image/color/palette" 6 | "math" 7 | "math/rand" 8 | "time" 9 | 10 | "github.com/faiface/pixel" 11 | "github.com/faiface/pixel/imdraw" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | var ( 16 | w, h, scale = float64(640), float64(360), float64(6) 17 | 18 | p, bg = newPalette(palette.Plan9), color.RGBA{255, 228, 225, 255} 19 | 20 | balls = []*ball{ 21 | newRandomBall(30), 22 | newRandomBall(40), 23 | newRandomBall(30), 24 | } 25 | ) 26 | 27 | func run() { 28 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 29 | Bounds: pixel.R(0, 0, w, h), 30 | VSync: true, 31 | Undecorated: true, 32 | }) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | win.SetSmooth(true) 38 | 39 | imd := imdraw.New(nil) 40 | imd.EndShape = imdraw.RoundEndShape 41 | 42 | for !win.Closed() { 43 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 44 | 45 | c0 := balls[0].color 46 | c1 := balls[1].color 47 | c2 := balls[2].color 48 | 49 | lc := color.RGBA{(c1.R / 3) * 2, (c1.G / 3) * 2, (c1.B / 3) * 2, 255} 50 | 51 | imd.Clear() 52 | 53 | imd.Color = lc 54 | imd.Push(balls[0].pos, balls[1].pos, balls[2].pos) 55 | imd.Line(scale * 3) 56 | 57 | imd.Push(balls[1].pos) 58 | imd.Circle(balls[1].radius, scale*4) 59 | 60 | imd.Color = color.RGBA{ 61 | uint8((int(c0.R) + int(c2.R) + int(c1.R)) / 4), 62 | uint8((int(c0.G) + int(c2.G) + int(c1.G)) / 4), 63 | uint8((int(c0.B) + int(c2.B) + int(c1.B)) / 4), 64 | 128, 65 | } 66 | 67 | imd.Push(balls[0].pos, balls[2].pos) 68 | imd.Line(balls[0].radius * 2.5) 69 | 70 | for _, ball := range []*ball{balls[0], balls[2]} { 71 | imd.Color = ball.color 72 | imd.Push(ball.pos) 73 | imd.Circle(ball.radius, 0) 74 | } 75 | 76 | imd.Color = balls[1].color 77 | imd.Push(balls[1].pos) 78 | imd.Circle(balls[1].radius, 0) 79 | 80 | win.Clear(bg) 81 | 82 | imd.Draw(win) 83 | 84 | win.Update() 85 | } 86 | } 87 | 88 | func main() { 89 | rand.Seed(4) 90 | 91 | go func() { 92 | for range time.Tick(32 * time.Millisecond) { 93 | for _, ball := range balls { 94 | ball.update() 95 | } 96 | } 97 | }() 98 | 99 | pixelgl.Run(run) 100 | } 101 | 102 | func newRandomBall(radius float64) *ball { 103 | bp := p.clone() 104 | 105 | mass := math.Pi * (radius * radius) 106 | 107 | return &ball{ 108 | pixel.V(w/2, h/2), 109 | pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale), 110 | mass, radius, bp.random(), bp, 111 | } 112 | } 113 | 114 | type ball struct { 115 | pos pixel.Vec 116 | vel pixel.Vec 117 | mass float64 118 | radius float64 119 | color color.RGBA 120 | palette *Palette 121 | } 122 | 123 | func (b *ball) update() { 124 | b.pos = b.pos.Add(b.vel) 125 | 126 | if b.pos.Y <= b.radius+scale || b.pos.Y >= h-(b.radius+scale) { 127 | b.vel.Y *= -1.0 128 | b.color = b.palette.next() 129 | } 130 | 131 | if b.pos.X <= b.radius+scale || b.pos.X >= w-(b.radius+scale) { 132 | b.vel.X *= -1.0 133 | b.color = b.palette.next() 134 | } 135 | 136 | for _, a := range balls { 137 | if a != b { 138 | d := a.pos.Sub(b.pos) 139 | 140 | if d.Len() > a.radius+b.radius { 141 | continue 142 | } 143 | 144 | pen := d.Unit().Scaled(a.radius + b.radius - d.Len()) 145 | 146 | a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass))) 147 | b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass))) 148 | 149 | u := d.Unit() 150 | v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass) 151 | 152 | a.vel = a.vel.Sub(u.Scaled(v * a.mass)) 153 | b.vel = b.vel.Add(u.Scaled(v * b.mass)) 154 | 155 | a.color = a.palette.next() 156 | b.color = b.palette.next() 157 | } 158 | } 159 | } 160 | 161 | func newPalette(cc []color.Color) *Palette { 162 | colors := []color.RGBA{} 163 | 164 | for _, v := range cc { 165 | if c, ok := v.(color.RGBA); ok { 166 | colors = append(colors, c) 167 | } 168 | } 169 | 170 | return &Palette{colors, len(colors), 0} 171 | } 172 | 173 | type Palette struct { 174 | colors []color.RGBA 175 | size int 176 | index int 177 | } 178 | 179 | func (p *Palette) clone() *Palette { 180 | return &Palette{p.colors, p.size, p.index} 181 | } 182 | 183 | func (p *Palette) next() color.RGBA { 184 | p.index++ 185 | 186 | if p.index+1 >= p.size { 187 | p.index = 0 188 | } 189 | 190 | return p.colors[p.index] 191 | } 192 | 193 | func (p *Palette) color() color.RGBA { 194 | return p.colors[p.index] 195 | } 196 | 197 | func (p *Palette) random() color.RGBA { 198 | p.index = rand.Intn(p.size) 199 | 200 | return p.colors[p.index] 201 | } 202 | -------------------------------------------------------------------------------- /bouncing/bouncing-gradient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/imdraw" 11 | "github.com/faiface/pixel/pixelgl" 12 | ) 13 | 14 | var ( 15 | w, h, scale = float64(640), float64(360), float64(48) 16 | 17 | p, bg = newPalette(Colors), color.RGBA{255, 228, 225, 255} 18 | 19 | balls = []*ball{ 20 | newRandomBall(scale / 2), 21 | newRandomBall(scale / 2), 22 | newRandomBall(scale / 2), 23 | newRandomBall(scale / 2), 24 | newRandomBall(scale / 2), 25 | } 26 | ) 27 | 28 | func run() { 29 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 30 | Bounds: pixel.R(0, 0, w, h), 31 | VSync: true, 32 | Undecorated: true, 33 | }) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | imd := imdraw.New(nil) 39 | imd.EndShape = imdraw.SharpEndShape 40 | 41 | for !win.Closed() { 42 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 43 | 44 | if win.JustPressed(pixelgl.KeySpace) { 45 | for _, ball := range balls { 46 | ball.color = ball.palette.next() 47 | } 48 | } 49 | 50 | if win.JustPressed(pixelgl.KeyEnter) { 51 | for _, ball := range balls { 52 | ball.pos = center() 53 | ball.vel = randomVelocity() 54 | } 55 | } 56 | 57 | imd.Clear() 58 | 59 | for _, ball := range balls { 60 | imd.Color = ball.color 61 | imd.Push(ball.pos) 62 | } 63 | 64 | imd.Polygon(scale) 65 | 66 | for _, ball := range balls { 67 | imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128} 68 | imd.Push(ball.pos) 69 | } 70 | 71 | imd.Polygon(scale * 0.3) 72 | 73 | win.Clear(bg) 74 | imd.Draw(win) 75 | win.Update() 76 | } 77 | } 78 | 79 | func main() { 80 | rand.Seed(4) 81 | 82 | go func() { 83 | for range time.Tick(32 * time.Millisecond) { 84 | for _, ball := range balls { 85 | ball.update() 86 | } 87 | } 88 | }() 89 | 90 | pixelgl.Run(run) 91 | } 92 | 93 | func newRandomBall(radius float64) *ball { 94 | return &ball{ 95 | center(), randomVelocity(), 96 | math.Pi * (radius * radius), 97 | radius, p.random(), p, 98 | } 99 | } 100 | 101 | func center() pixel.Vec { 102 | return pixel.V(w/2, h/2) 103 | } 104 | 105 | func randomVelocity() pixel.Vec { 106 | return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 5) 107 | } 108 | 109 | type ball struct { 110 | pos pixel.Vec 111 | vel pixel.Vec 112 | mass float64 113 | radius float64 114 | color color.RGBA 115 | palette *Palette 116 | } 117 | 118 | func (b *ball) update() { 119 | b.pos = b.pos.Add(b.vel) 120 | 121 | switch { 122 | case b.pos.Y <= b.radius || b.pos.Y >= h-(b.radius): 123 | b.vel.Y *= -1.0 124 | b.color = p.next() 125 | 126 | if b.pos.Y < b.radius { 127 | b.pos.Y = b.radius 128 | } 129 | case b.pos.X <= b.radius || b.pos.X >= w-(b.radius): 130 | b.vel.X *= -1.0 131 | b.color = p.next() 132 | 133 | if b.pos.X < b.radius { 134 | b.pos.X = b.radius 135 | } 136 | } 137 | 138 | for _, a := range balls { 139 | if a != b { 140 | d := a.pos.Sub(b.pos) 141 | 142 | if d.Len() > a.radius+b.radius { 143 | continue 144 | } 145 | 146 | pen := d.Unit().Scaled(a.radius + b.radius - d.Len()) 147 | 148 | a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass))) 149 | b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass))) 150 | 151 | u := d.Unit() 152 | v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass) 153 | 154 | a.vel = a.vel.Sub(u.Scaled(v * a.mass)) 155 | b.vel = b.vel.Add(u.Scaled(v * b.mass)) 156 | } 157 | } 158 | } 159 | 160 | func newPalette(cc []color.Color) *Palette { 161 | colors := []color.RGBA{} 162 | 163 | for _, v := range cc { 164 | if c, ok := v.(color.RGBA); ok { 165 | colors = append(colors, c) 166 | } 167 | } 168 | 169 | return &Palette{colors, len(colors), 0} 170 | } 171 | 172 | type Palette struct { 173 | colors []color.RGBA 174 | size int 175 | index int 176 | } 177 | 178 | func (p *Palette) clone() *Palette { 179 | return &Palette{p.colors, p.size, p.index} 180 | } 181 | 182 | func (p *Palette) next() color.RGBA { 183 | if p.index++; p.index >= p.size { 184 | p.index = 0 185 | } 186 | 187 | return p.colors[p.index] 188 | } 189 | 190 | func (p *Palette) color() color.RGBA { 191 | return p.colors[p.index] 192 | } 193 | 194 | func (p *Palette) random() color.RGBA { 195 | p.index = rand.Intn(p.size) 196 | 197 | return p.colors[p.index] 198 | } 199 | 200 | var Colors = []color.Color{ 201 | color.RGBA{190, 38, 51, 255}, 202 | color.RGBA{224, 111, 139, 255}, 203 | color.RGBA{73, 60, 43, 255}, 204 | color.RGBA{164, 100, 34, 255}, 205 | color.RGBA{235, 137, 49, 255}, 206 | color.RGBA{247, 226, 107, 255}, 207 | color.RGBA{47, 72, 78, 255}, 208 | color.RGBA{68, 137, 26, 255}, 209 | color.RGBA{163, 206, 39, 255}, 210 | color.RGBA{0, 87, 132, 255}, 211 | color.RGBA{49, 162, 242, 255}, 212 | color.RGBA{178, 220, 239, 255}, 213 | } 214 | -------------------------------------------------------------------------------- /bouncing/bouncing-particles.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/imdraw" 11 | "github.com/faiface/pixel/pixelgl" 12 | ) 13 | 14 | var ( 15 | w, h, s, scale = float64(640), float64(360), float64(2.3), float64(32) 16 | 17 | p, bg = newPalette(Colors), color.RGBA{32, p.color().G, 32, 255} 18 | 19 | balls = []*ball{ 20 | newRandomBall(scale), 21 | newRandomBall(scale), 22 | } 23 | ) 24 | 25 | func run() { 26 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 27 | Bounds: pixel.R(0, 0, w, h), 28 | VSync: true, 29 | Undecorated: true, 30 | }) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | imd := imdraw.New(nil) 36 | 37 | imd.EndShape = imdraw.RoundEndShape 38 | imd.Precision = 3 39 | 40 | go func() { 41 | start := time.Now() 42 | 43 | for range time.Tick(16 * time.Millisecond) { 44 | bg = color.RGBA{32 + (p.color().R/128)*4, 32 + (p.color().G/128)*4, 32 + (p.color().B/128)*4, 255} 45 | s = pixel.V(math.Sin(time.Since(start).Seconds())*0.8, 0).Len()*2 - 1 46 | scale = 64 + 15*s 47 | imd.Intensity = 1.2 * s 48 | } 49 | }() 50 | 51 | for !win.Closed() { 52 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 53 | 54 | if win.JustPressed(pixelgl.KeySpace) { 55 | for _, ball := range balls { 56 | ball.color = ball.palette.next() 57 | } 58 | } 59 | 60 | if win.JustPressed(pixelgl.KeyEnter) { 61 | for _, ball := range balls { 62 | ball.pos = center() 63 | ball.vel = randomVelocity() 64 | } 65 | } 66 | 67 | imd.Clear() 68 | 69 | for _, ball := range balls { 70 | imd.Color = ball.color 71 | imd.Push(ball.pos) 72 | } 73 | 74 | imd.Polygon(scale) 75 | 76 | for _, ball := range balls { 77 | imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128 - uint8(128*s)} 78 | imd.Push(ball.pos) 79 | } 80 | 81 | imd.Polygon(scale * s) 82 | 83 | for _, ball := range balls { 84 | aliveParticles := []*particle{} 85 | 86 | for _, particle := range ball.particles { 87 | if particle.life > 0 { 88 | aliveParticles = append(aliveParticles, particle) 89 | } 90 | } 91 | 92 | for _, particle := range aliveParticles { 93 | imd.Color = particle.color 94 | imd.Push(particle.pos) 95 | imd.Circle(16*particle.life, 0) 96 | } 97 | } 98 | 99 | win.Clear(bg) 100 | imd.Draw(win) 101 | win.Update() 102 | } 103 | } 104 | 105 | func main() { 106 | rand.Seed(4) 107 | 108 | go func() { 109 | for range time.Tick(32 * time.Millisecond) { 110 | for _, ball := range balls { 111 | go ball.update() 112 | 113 | for _, particle := range ball.particles { 114 | go particle.update() 115 | } 116 | } 117 | } 118 | }() 119 | 120 | pixelgl.Run(run) 121 | } 122 | 123 | func newParticleAt(pos, vel pixel.Vec) *particle { 124 | c := p.color() 125 | c.A = 5 126 | 127 | return &particle{pos, vel, c, rand.Float64() * 1.5} 128 | } 129 | 130 | func newRandomBall(radius float64) *ball { 131 | return &ball{ 132 | center(), randomVelocity(), 133 | math.Pi * (radius * radius), 134 | radius, p.random(), p, []*particle{}, 135 | } 136 | } 137 | 138 | func center() pixel.Vec { 139 | return pixel.V(w/2, h/2) 140 | } 141 | 142 | func randomVelocity() pixel.Vec { 143 | return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 4) 144 | } 145 | 146 | type particle struct { 147 | pos pixel.Vec 148 | vel pixel.Vec 149 | color color.RGBA 150 | life float64 151 | } 152 | 153 | func (p *particle) update() { 154 | p.pos = p.pos.Add(p.vel) 155 | p.life -= 0.03 156 | 157 | switch { 158 | case p.pos.Y < 0 || p.pos.Y >= h: 159 | p.vel.Y *= -1.0 160 | case p.pos.X < 0 || p.pos.X >= w: 161 | p.vel.X *= -1.0 162 | } 163 | } 164 | 165 | type ball struct { 166 | pos pixel.Vec 167 | vel pixel.Vec 168 | mass float64 169 | radius float64 170 | color color.RGBA 171 | palette *Palette 172 | particles []*particle 173 | } 174 | 175 | func (b *ball) update() { 176 | b.pos = b.pos.Add(b.vel) 177 | 178 | var bounced bool 179 | 180 | switch { 181 | case b.pos.Y <= b.radius || b.pos.Y >= h-b.radius: 182 | b.vel.Y *= -1.0 183 | bounced = true 184 | 185 | if b.pos.Y < b.radius { 186 | b.pos.Y = b.radius 187 | } else { 188 | b.pos.Y = h - b.radius 189 | } 190 | case b.pos.X <= b.radius || b.pos.X >= w-b.radius: 191 | b.vel.X *= -1.0 192 | bounced = true 193 | 194 | if b.pos.X < b.radius { 195 | b.pos.X = b.radius 196 | } else { 197 | b.pos.X = w - b.radius 198 | } 199 | } 200 | 201 | for _, a := range balls { 202 | if a != b { 203 | d := a.pos.Sub(b.pos) 204 | 205 | if d.Len() > a.radius+b.radius { 206 | continue 207 | } 208 | 209 | pen := d.Unit().Scaled(a.radius + b.radius - d.Len()) 210 | 211 | a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass))) 212 | b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass))) 213 | 214 | u := d.Unit() 215 | v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass) 216 | 217 | a.vel = a.vel.Sub(u.Scaled(v * b.mass)) 218 | b.vel = b.vel.Add(u.Scaled(v * a.mass)) 219 | 220 | bounced = true 221 | } 222 | } 223 | 224 | if bounced { 225 | b.color = p.next() 226 | b.particles = append(b.particles, 227 | newParticleAt(b.pos, b.vel.Rotated(1).Scaled(rand.Float64())), 228 | newParticleAt(b.pos, b.vel.Rotated(2).Scaled(rand.Float64())), 229 | newParticleAt(b.pos, b.vel.Rotated(3).Scaled(rand.Float64())), 230 | newParticleAt(b.pos, b.vel.Rotated(4).Scaled(rand.Float64())), 231 | newParticleAt(b.pos, b.vel.Rotated(5).Scaled(rand.Float64())), 232 | newParticleAt(b.pos, b.vel.Rotated(6).Scaled(rand.Float64())), 233 | newParticleAt(b.pos, b.vel.Rotated(7).Scaled(rand.Float64())), 234 | newParticleAt(b.pos, b.vel.Rotated(8).Scaled(rand.Float64())), 235 | newParticleAt(b.pos, b.vel.Rotated(9).Scaled(rand.Float64())), 236 | 237 | newParticleAt(b.pos, b.vel.Rotated(10).Scaled(rand.Float64()+1)), 238 | newParticleAt(b.pos, b.vel.Rotated(20).Scaled(rand.Float64()+1)), 239 | newParticleAt(b.pos, b.vel.Rotated(30).Scaled(rand.Float64()+1)), 240 | newParticleAt(b.pos, b.vel.Rotated(40).Scaled(rand.Float64()+1)), 241 | newParticleAt(b.pos, b.vel.Rotated(50).Scaled(rand.Float64()+1)), 242 | newParticleAt(b.pos, b.vel.Rotated(60).Scaled(rand.Float64()+1)), 243 | newParticleAt(b.pos, b.vel.Rotated(70).Scaled(rand.Float64()+1)), 244 | newParticleAt(b.pos, b.vel.Rotated(80).Scaled(rand.Float64()+1)), 245 | newParticleAt(b.pos, b.vel.Rotated(90).Scaled(rand.Float64()+1)), 246 | ) 247 | } 248 | } 249 | 250 | func newPalette(cc []color.Color) *Palette { 251 | colors := []color.RGBA{} 252 | 253 | for _, v := range cc { 254 | if c, ok := v.(color.RGBA); ok { 255 | colors = append(colors, c) 256 | } 257 | } 258 | 259 | return &Palette{colors, len(colors), 0} 260 | } 261 | 262 | type Palette struct { 263 | colors []color.RGBA 264 | size int 265 | index int 266 | } 267 | 268 | func (p *Palette) clone() *Palette { 269 | return &Palette{p.colors, p.size, p.index} 270 | } 271 | 272 | func (p *Palette) next() color.RGBA { 273 | if p.index++; p.index >= p.size { 274 | p.index = 0 275 | } 276 | 277 | return p.colors[p.index] 278 | } 279 | 280 | func (p *Palette) color() color.RGBA { 281 | return p.colors[p.index] 282 | } 283 | 284 | func (p *Palette) random() color.RGBA { 285 | p.index = rand.Intn(p.size) 286 | 287 | return p.colors[p.index] 288 | } 289 | 290 | var Colors = []color.Color{ 291 | color.RGBA{190, 38, 51, 255}, 292 | color.RGBA{224, 111, 139, 255}, 293 | color.RGBA{73, 60, 43, 255}, 294 | color.RGBA{164, 100, 34, 255}, 295 | color.RGBA{235, 137, 49, 255}, 296 | color.RGBA{247, 226, 107, 255}, 297 | color.RGBA{47, 72, 78, 255}, 298 | color.RGBA{68, 137, 26, 255}, 299 | color.RGBA{163, 206, 39, 255}, 300 | color.RGBA{0, 87, 132, 255}, 301 | color.RGBA{49, 162, 242, 255}, 302 | color.RGBA{178, 220, 239, 255}, 303 | } 304 | -------------------------------------------------------------------------------- /bouncing/bouncing-triangle.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/imdraw" 11 | "github.com/faiface/pixel/pixelgl" 12 | ) 13 | 14 | var ( 15 | w, h, s, scale = float64(640), float64(360), float64(0.3), float64(48) 16 | 17 | p, bg = newPalette(Colors), color.RGBA{255, 228, 225, 255} 18 | 19 | balls = []*ball{ 20 | newRandomBall(scale / 2), 21 | newRandomBall(scale / 2), 22 | newRandomBall(scale / 2), 23 | //newRandomBall(scale / 2), 24 | //newRandomBall(scale / 2), 25 | } 26 | ) 27 | 28 | func run() { 29 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 30 | Bounds: pixel.R(0, 0, w, h), 31 | VSync: true, 32 | Undecorated: true, 33 | }) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | imd := imdraw.New(nil) 39 | imd.EndShape = imdraw.RoundEndShape 40 | 41 | go func() { 42 | start := time.Now() 43 | 44 | for range time.Tick(32 * time.Millisecond) { 45 | s = (pixel.V(math.Sin(time.Since(start).Seconds())*0.6, 0).Len() * 2) - 1 46 | scale = 40 + 30*s 47 | } 48 | }() 49 | 50 | for !win.Closed() { 51 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 52 | 53 | if win.JustPressed(pixelgl.KeySpace) { 54 | for _, ball := range balls { 55 | ball.color = ball.palette.next() 56 | } 57 | } 58 | 59 | if win.JustPressed(pixelgl.KeyEnter) { 60 | for _, ball := range balls { 61 | ball.pos = center() 62 | ball.vel = randomVelocity() 63 | } 64 | } 65 | 66 | imd.Clear() 67 | 68 | for _, ball := range balls { 69 | imd.Color = ball.color 70 | imd.Push(ball.pos) 71 | } 72 | 73 | imd.Polygon(scale) 74 | 75 | for _, ball := range balls { 76 | imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128 - uint8(128*s)} 77 | imd.Push(ball.pos) 78 | } 79 | 80 | imd.Polygon(scale * s) 81 | 82 | win.Clear(bg) 83 | imd.Draw(win) 84 | win.Update() 85 | } 86 | } 87 | 88 | func main() { 89 | rand.Seed(4) 90 | 91 | go func() { 92 | for range time.Tick(32 * time.Millisecond) { 93 | for _, ball := range balls { 94 | ball.update() 95 | } 96 | } 97 | }() 98 | 99 | pixelgl.Run(run) 100 | } 101 | 102 | func newRandomBall(radius float64) *ball { 103 | return &ball{ 104 | center(), randomVelocity(), 105 | math.Pi * (radius * radius), 106 | radius, p.random(), p, 107 | } 108 | } 109 | 110 | func center() pixel.Vec { 111 | return pixel.V(w/2, h/2) 112 | } 113 | 114 | func randomVelocity() pixel.Vec { 115 | return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 5) 116 | } 117 | 118 | type ball struct { 119 | pos pixel.Vec 120 | vel pixel.Vec 121 | mass float64 122 | radius float64 123 | color color.RGBA 124 | palette *Palette 125 | } 126 | 127 | func (b *ball) update() { 128 | b.pos = b.pos.Add(b.vel) 129 | 130 | var bounced bool 131 | 132 | switch { 133 | case b.pos.Y <= b.radius || b.pos.Y >= h-(b.radius): 134 | b.vel.Y *= -1.0 135 | bounced = true 136 | 137 | if b.pos.Y < b.radius { 138 | b.pos.Y = b.radius 139 | } 140 | case b.pos.X <= b.radius || b.pos.X >= w-(b.radius): 141 | b.vel.X *= -1.0 142 | bounced = true 143 | 144 | if b.pos.X < b.radius { 145 | b.pos.X = b.radius 146 | } 147 | } 148 | 149 | for _, a := range balls { 150 | if a != b { 151 | d := a.pos.Sub(b.pos) 152 | 153 | if d.Len() > a.radius+b.radius { 154 | continue 155 | } 156 | 157 | pen := d.Unit().Scaled(a.radius + b.radius - d.Len()) 158 | 159 | a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass))) 160 | b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass))) 161 | 162 | u := d.Unit() 163 | v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass) 164 | 165 | a.vel = a.vel.Sub(u.Scaled(v * b.mass)) 166 | b.vel = b.vel.Add(u.Scaled(v * a.mass)) 167 | } 168 | } 169 | 170 | if bounced { 171 | b.color = p.next() 172 | } 173 | } 174 | 175 | func newPalette(cc []color.Color) *Palette { 176 | colors := []color.RGBA{} 177 | 178 | for _, v := range cc { 179 | if c, ok := v.(color.RGBA); ok { 180 | colors = append(colors, c) 181 | } 182 | } 183 | 184 | return &Palette{colors, len(colors), 0} 185 | } 186 | 187 | type Palette struct { 188 | colors []color.RGBA 189 | size int 190 | index int 191 | } 192 | 193 | func (p *Palette) clone() *Palette { 194 | return &Palette{p.colors, p.size, p.index} 195 | } 196 | 197 | func (p *Palette) next() color.RGBA { 198 | if p.index++; p.index >= p.size { 199 | p.index = 0 200 | } 201 | 202 | return p.colors[p.index] 203 | } 204 | 205 | func (p *Palette) color() color.RGBA { 206 | return p.colors[p.index] 207 | } 208 | 209 | func (p *Palette) random() color.RGBA { 210 | p.index = rand.Intn(p.size) 211 | 212 | return p.colors[p.index] 213 | } 214 | 215 | var Colors = []color.Color{ 216 | color.RGBA{190, 38, 51, 255}, 217 | color.RGBA{224, 111, 139, 255}, 218 | color.RGBA{73, 60, 43, 255}, 219 | color.RGBA{164, 100, 34, 255}, 220 | color.RGBA{235, 137, 49, 255}, 221 | color.RGBA{247, 226, 107, 255}, 222 | color.RGBA{47, 72, 78, 255}, 223 | color.RGBA{68, 137, 26, 255}, 224 | color.RGBA{163, 206, 39, 255}, 225 | color.RGBA{0, 87, 132, 255}, 226 | color.RGBA{49, 162, 242, 255}, 227 | color.RGBA{178, 220, 239, 255}, 228 | } 229 | -------------------------------------------------------------------------------- /bouncing/bouncing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/imdraw" 10 | "github.com/faiface/pixel/pixelgl" 11 | ) 12 | 13 | var ( 14 | w, h, scale = float64(640), float64(360), float64(6) 15 | 16 | p = newPalette(PinkColors) 17 | 18 | bg = color.RGBA{7, 9, 9, 255} 19 | 20 | lc, pc color.RGBA 21 | 22 | v = rand.Float64() * scale 23 | 24 | balls = []*ball{ 25 | newRandomBall(4 + v), 26 | newRandomBall(6 + v), 27 | newRandomBall(10 + v), 28 | newRandomBall(14 + v), 29 | newRandomBall(18 + v), 30 | } 31 | ) 32 | 33 | func run() { 34 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 35 | Bounds: pixel.R(0, 0, w, h), 36 | VSync: true, 37 | Undecorated: true, 38 | }) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | imd := imdraw.New(nil) 44 | 45 | imd.Precision = 3 46 | 47 | go func() { 48 | var step int 49 | 50 | for range time.Tick(256 * time.Millisecond) { 51 | switch imd.Precision { 52 | case 3: 53 | step = 1 54 | case 9: 55 | step = -1 56 | } 57 | 58 | imd.Precision += step 59 | } 60 | }() 61 | 62 | for !win.Closed() { 63 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 64 | 65 | var positions = []pixel.Vec{} 66 | 67 | for _, ball := range balls { 68 | positions = append(positions, ball.pos) 69 | } 70 | 71 | imd.Clear() 72 | 73 | imd.Color = lc 74 | imd.Push(positions...) 75 | imd.Push(positions[0]) 76 | imd.Line(scale) 77 | 78 | imd.Color = pc 79 | imd.Push(positions...) 80 | imd.Polygon(0) 81 | 82 | for _, ball := range balls { 83 | imd.Color = ball.color 84 | imd.Push(ball.pos) 85 | imd.Circle(ball.radius, 0) 86 | } 87 | 88 | win.Clear(bg) 89 | 90 | imd.Draw(win) 91 | 92 | win.Update() 93 | } 94 | } 95 | 96 | func main() { 97 | rand.Seed(42) 98 | 99 | go func() { 100 | for range time.Tick(16 * time.Millisecond) { 101 | for _, ball := range balls { 102 | ball.update() 103 | } 104 | } 105 | }() 106 | 107 | updatePolygonColor(p.next()) 108 | 109 | pixelgl.Run(run) 110 | } 111 | 112 | func updatePolygonColor(c color.RGBA) { 113 | lc = color.RGBA{c.R / 4, c.G / 4, c.B / 4, 192} 114 | pc = color.RGBA{c.R / 6, c.G / 6, c.B / 6, 64} 115 | } 116 | 117 | func newRandomBall(radius float64) *ball { 118 | pos := pixel.V(w/4+(w/4*3)*rand.Float64(), h/4+(h/4*3)*rand.Float64()) 119 | dir := pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(32 / radius) 120 | 121 | return &ball{pos, dir, radius, p.color(), p.clone()} 122 | } 123 | 124 | type ball struct { 125 | pos pixel.Vec 126 | dir pixel.Vec 127 | radius float64 128 | color color.RGBA 129 | palette *palette 130 | } 131 | 132 | func (b *ball) update() { 133 | b.pos.X += b.dir.X 134 | b.pos.Y += b.dir.Y 135 | 136 | if b.pos.Y <= b.radius || b.pos.Y >= h-b.radius { 137 | b.dir.Y *= -1 138 | b.color = b.palette.next() 139 | updatePolygonColor(p.next()) 140 | } 141 | 142 | if b.pos.X <= b.radius || b.pos.X >= w-b.radius { 143 | b.dir.X *= -1 144 | b.color = b.palette.next() 145 | updatePolygonColor(p.next()) 146 | } 147 | } 148 | 149 | func newPalette(colors []color.RGBA) *palette { 150 | return &palette{colors, len(colors), 0} 151 | } 152 | 153 | type palette struct { 154 | colors []color.RGBA 155 | size int 156 | index int 157 | } 158 | 159 | func (p *palette) clone() *palette { 160 | return &palette{p.colors, len(p.colors), p.index} 161 | } 162 | 163 | func (p *palette) next() color.RGBA { 164 | p.index++ 165 | 166 | if p.index+1 >= p.size { 167 | p.index = 0 168 | } 169 | 170 | return p.colors[p.index] 171 | } 172 | 173 | func (p *palette) color() color.RGBA { 174 | return p.colors[p.index] 175 | } 176 | 177 | func (p *palette) random() color.RGBA { 178 | p.index = rand.Intn(p.size) 179 | 180 | return p.colors[p.index] 181 | } 182 | 183 | var ( 184 | Pink = color.RGBA{255, 192, 203, 255} 185 | LightPink = color.RGBA{255, 182, 193, 255} 186 | HotPink = color.RGBA{255, 105, 180, 255} 187 | DeepPink = color.RGBA{255, 20, 147, 255} 188 | PaleVioletRed = color.RGBA{219, 112, 147, 255} 189 | MediumVioletRed = color.RGBA{199, 21, 133, 255} 190 | ) 191 | 192 | var PinkColors = []color.RGBA{ 193 | Pink, 194 | LightPink, 195 | HotPink, 196 | DeepPink, 197 | PaleVioletRed, 198 | MediumVioletRed, 199 | } 200 | -------------------------------------------------------------------------------- /doomfire/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/doomfire 2 | 3 | https://gist.github.com/peterhellberg/d36cfeffbf24116eebf7e6cd7646c40f 4 | 5 | ![doomfire](https://user-images.githubusercontent.com/565124/50575428-b79f4200-0dff-11e9-9884-812ca93c92da.png) 6 | 7 | ![animation](https://user-images.githubusercontent.com/565124/50575245-09919900-0dfb-11e9-9ebe-2fe1e51afe92.gif) 8 | 9 | Based on which might have been inspired by 10 | -------------------------------------------------------------------------------- /doomfire/doomfire.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/pixelgl" 11 | ) 12 | 13 | const ( 14 | width = 160 15 | height = 96 16 | scale = 6 17 | delay = 32 * time.Millisecond 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, float64(width)*scale, float64(height)*scale), 23 | VSync: true, 24 | Undecorated: true, 25 | Resizable: false, 26 | }) 27 | if err != nil { 28 | panic(err) 29 | } 30 | 31 | i := NewInferno(width, height) 32 | c := win.Bounds().Center() 33 | 34 | ticker := time.NewTicker(delay) 35 | 36 | go func() { 37 | for range ticker.C { 38 | i.Spread() 39 | i.Render() 40 | } 41 | }() 42 | 43 | for !win.Closed() { 44 | p := pixel.PictureDataFromImage(i.buffer) 45 | 46 | pixel.NewSprite(p, p.Bounds()). 47 | Draw(win, pixel.IM.Moved(c).Scaled(c, scale*1.1)) 48 | 49 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 50 | return 51 | } 52 | 53 | win.Update() 54 | } 55 | } 56 | 57 | func main() { 58 | pixelgl.Run(run) 59 | } 60 | 61 | type Inferno struct { 62 | width int 63 | height int 64 | grid []int8 65 | buffer *image.RGBA 66 | } 67 | 68 | func NewInferno(width, height int) *Inferno { 69 | i := &Inferno{width: width, height: height} 70 | 71 | i.init() 72 | 73 | return i 74 | } 75 | 76 | func (i *Inferno) init() { 77 | i.grid = make([]int8, i.width*i.height) 78 | 79 | for j := 0; j < i.width; j++ { 80 | i.grid[((i.height-1)*i.width)+j] = 42 81 | } 82 | 83 | i.buffer = image.NewRGBA(image.Rect(0, 0, i.width, i.height)) 84 | } 85 | 86 | func (i *Inferno) Render() { 87 | for y := 0; y < i.height; y += 2 { 88 | for x := 0; x < i.width; x++ { 89 | if pos := (y * i.width) + x; pos > 0 && i.grid[pos] != i.grid[pos-1] { 90 | i.buffer.Set(x, y, mapColor(i.grid[pos])) 91 | } 92 | 93 | if pos := ((y + 1) * i.width) + x; i.grid[pos] != i.grid[pos-1] { 94 | i.buffer.Set(x, y+1, mapColor(i.grid[pos])) 95 | } 96 | } 97 | } 98 | } 99 | 100 | func (i *Inferno) Spread() { 101 | for y := i.height - 1; y > 0; y-- { 102 | for x := 0; x < i.width; x++ { 103 | src := (y * i.width) + x 104 | dst := (src - i.width) + rand.Intn(5) - 2 105 | 106 | if dst < 0 { 107 | dst = 0 108 | } 109 | 110 | if end := (i.width * i.height) - 1; dst > end { 111 | dst = end 112 | } 113 | 114 | i.grid[dst] = i.grid[src] - int8(rand.Intn(6)-1) 115 | 116 | if i.grid[dst] > 32 { 117 | i.grid[dst] = 32 118 | } 119 | 120 | if i.grid[dst] < 0 { 121 | i.grid[dst] = 0 122 | } 123 | } 124 | } 125 | } 126 | 127 | var cmap = []color.RGBA{ 128 | {0x07, 0x07, 0x07, 0xdc}, {0x1f, 0x07, 0x07, 0xdc}, 129 | {0x2f, 0x0f, 0x07, 0xdc}, {0x47, 0x0f, 0x07, 0xdc}, 130 | {0x57, 0x17, 0x07, 0xdc}, {0x67, 0x1f, 0x07, 0xdc}, 131 | {0x77, 0x1f, 0x07, 0xdc}, {0x8f, 0x27, 0x07, 0xdc}, 132 | {0x9f, 0x2f, 0x07, 0xdc}, {0xaf, 0x3f, 0x07, 0xdc}, 133 | {0xbf, 0x47, 0x07, 0xdc}, {0xc7, 0x47, 0x07, 0xdc}, 134 | {0xdf, 0x4f, 0x07, 0xdc}, {0xdf, 0x57, 0x07, 0xdc}, 135 | {0xdf, 0x57, 0x07, 0xdc}, {0xd7, 0x5f, 0x07, 0xdc}, 136 | {0xd7, 0x67, 0x0f, 0xdc}, {0xcf, 0x6f, 0x0f, 0xdc}, 137 | {0xcf, 0x77, 0x0f, 0xdc}, {0xcf, 0x7f, 0x0f, 0xdc}, 138 | {0xcf, 0x87, 0x17, 0xdc}, {0xc7, 0x87, 0x17, 0xdc}, 139 | {0xc7, 0x8f, 0x17, 0xdc}, {0xc7, 0x97, 0x1f, 0xdc}, 140 | {0xbf, 0x9f, 0x1f, 0xdc}, {0xbf, 0x9f, 0x1f, 0xdc}, 141 | {0xbf, 0xa7, 0x27, 0xdc}, {0xbf, 0xa7, 0x27, 0xdc}, 142 | {0xbf, 0xaf, 0x2f, 0xdc}, {0xb7, 0xaf, 0x2f, 0xdc}, 143 | {0xb7, 0xb7, 0x2f, 0xdc}, {0xb7, 0xb7, 0x37, 0xdc}, 144 | {0xcf, 0xcf, 0x6f, 0xdc}, {0xdf, 0xdf, 0x9f, 0xdc}, 145 | {0xef, 0xef, 0xc7, 0xdc}, {0xff, 0xff, 0xff, 0xdc}, 146 | } 147 | 148 | func mapColor(v int8) color.RGBA { 149 | if v < 0 || int(v) >= len(cmap) { 150 | return color.RGBA{0, 0, 0, 255} 151 | } 152 | 153 | return cmap[v] 154 | } 155 | -------------------------------------------------------------------------------- /double-pendulum/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/double-pendulum 2 | 3 | https://gist.github.com/peterhellberg/c8b31f3bb7cc11adae589fa261f80d0c 4 | 5 | ![double-pendulum](https://user-images.githubusercontent.com/565124/36648903-27c31850-1a99-11e8-8538-76fe63368185.gif) 6 | -------------------------------------------------------------------------------- /double-pendulum/double-pendulum.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/imdraw" 9 | "github.com/faiface/pixel/pixelgl" 10 | ) 11 | 12 | var ( 13 | g = 0.1 14 | r1 = 180.0 15 | r2 = 90.0 16 | m1 = 32.0 17 | m2 = 16.0 18 | a1v = 0.0 19 | a2v = 0.0 20 | a1, a2 = a1a2DefaultValues() 21 | ) 22 | 23 | func run() { 24 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 25 | Bounds: pixel.R(0, 0, 600, 310), 26 | VSync: true, 27 | Undecorated: true, 28 | }) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | win.SetSmooth(true) 34 | win.SetMatrix(pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)).Moved(pixel.V(300, 300))) 35 | 36 | for !win.Closed() { 37 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 38 | 39 | if win.JustPressed(pixelgl.KeySpace) { 40 | a1, a2 = a1a2DefaultValues() 41 | } 42 | 43 | win.Clear(color.NRGBA{44, 44, 84, 255}) 44 | 45 | a, b := update() 46 | 47 | imd := imdraw.New(nil) 48 | 49 | imd.Color = color.NRGBA{64, 64, 122, 255} 50 | imd.Push(pixel.ZV, a, b) 51 | imd.Line(3) 52 | 53 | imd.Color = color.NRGBA{51, 217, 178, 255} 54 | imd.Push(a) 55 | imd.Circle(m1/2, 0) 56 | 57 | imd.Color = color.NRGBA{52, 172, 224, 255} 58 | imd.Push(b) 59 | imd.Circle(m2/2, 0) 60 | 61 | imd.Draw(win) 62 | win.Update() 63 | } 64 | } 65 | 66 | func update() (pixel.Vec, pixel.Vec) { 67 | a1a := a1aCalculation() 68 | a2a := a2aCalculation() 69 | 70 | a1v += a1a 71 | a2v += a2a 72 | 73 | a1 += a1v 74 | a2 += a2v 75 | 76 | a1v *= 0.9996 77 | a2v *= 0.9996 78 | 79 | a := pixel.V(r1*math.Sin(a1), r1*math.Cos(a1)) 80 | b := pixel.V(a.X+r2*math.Sin(a2), a.Y+r2*math.Cos(a2)) 81 | 82 | return a, b 83 | } 84 | 85 | func main() { 86 | pixelgl.Run(run) 87 | } 88 | 89 | func a1a2DefaultValues() (float64, float64) { 90 | return math.Pi / 2, math.Pi / 3 91 | } 92 | 93 | func a1aCalculation() float64 { 94 | num1 := -g * (2*m1 + m2) * math.Sin(a1) 95 | num2 := -m2 * g * math.Sin(a1-2*a2) 96 | num3 := -2 * math.Sin(a1-a2) * m2 97 | num4 := a2v*a2v*r2 + a1v*a1v*r1*math.Cos(a1-a2) 98 | den := r1 * (2*m1 + m2 - m2*math.Cos(2*a1-2*a2)) 99 | 100 | return (num1 + num2 + num3*num4) / den 101 | } 102 | 103 | func a2aCalculation() float64 { 104 | num1 := 2 * math.Sin(a1-a2) 105 | num2 := (a1v * a1v * r1 * (m1 + m2)) 106 | num3 := g * (m1 + m2) * math.Cos(a1) 107 | num4 := a2v * a2v * r2 * m2 * math.Cos(a1-a2) 108 | den := r2 * (2*m1 + m2 - m2*math.Cos(2*a2-2*a2)) 109 | 110 | return (num1 * (num2 + num3 + num4)) / den 111 | } 112 | -------------------------------------------------------------------------------- /drawer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/pixelgl" 9 | ) 10 | 11 | const ( 12 | w, h = 512, 512 13 | fw, fh = float64(w), float64(h) 14 | ) 15 | 16 | func run() { 17 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 18 | Bounds: pixel.R(0, 0, fw, fh), 19 | Undecorated: true, 20 | }) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | canvas := pixelgl.NewCanvas(win.Bounds()) 26 | 27 | for !win.Closed() { 28 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 29 | 30 | drawFrame(win, canvas) 31 | 32 | win.Update() 33 | } 34 | } 35 | 36 | func drawFrame(win *pixelgl.Window, canvas *pixelgl.Canvas) { 37 | fx, fy := win.MousePosition().XY() 38 | x, y := int(fx), int(fy) 39 | 40 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 41 | 42 | buffer.Set(x, y, color.RGBA{255, uint8(y % 255), uint8(x % 255), 255}) 43 | 44 | canvas.SetPixels(buffer.Pix) 45 | 46 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 47 | } 48 | 49 | func main() { 50 | pixelgl.Run(run) 51 | } 52 | -------------------------------------------------------------------------------- /epigenetic/the-magic-hand-of-chance/the-magic-hand-of-chance.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/pixelgl" 11 | "github.com/faiface/pixel/text" 12 | ) 13 | 14 | var ( 15 | left, up = float64(55), float64(-65) 16 | 17 | texts = [6][6]*text.Text{ 18 | {newText(0, 125), newText(25, 125), newText(50, 125), newText(75, 125), newText(100, 125), newText(125, 125)}, 19 | {newText(0, 100), newText(25, 100), newText(50, 100), newText(75, 100), newText(100, 100), newText(125, 100)}, 20 | {newText(0, 75), newText(25, 75), newText(50, 75), newText(75, 75), newText(100, 75), newText(125, 75)}, 21 | {newText(0, 50), newText(25, 50), newText(50, 50), newText(75, 50), newText(100, 50), newText(125, 50)}, 22 | {newText(0, 25), newText(25, 25), newText(50, 25), newText(75, 25), newText(100, 25), newText(125, 25)}, 23 | {newText(0, 0), newText(25, 0), newText(50, 0), newText(75, 0), newText(100, 0), newText(125, 0)}, 24 | } 25 | 26 | colors = []color.RGBA{ 27 | color.RGBA{255, 32, 32, 200}, // RED 28 | color.RGBA{32, 255, 32, 200}, // GREEN 29 | color.RGBA{32, 32, 255, 200}, // BLUE 30 | color.RGBA{32, 255, 255, 200}, // CYAN 31 | color.RGBA{255, 32, 255, 200}, // MAGENTA 32 | } 33 | ) 34 | 35 | func run() { 36 | start := time.Now() 37 | 38 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 39 | Bounds: pixel.R(0, 0, float64(880), float64(400)), 40 | VSync: true, 41 | Undecorated: true, 42 | }) 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | win.SetSmooth(false) 48 | win.SetMatrix(pixel.IM.Moved(win.Bounds().Center()).Scaled(win.Bounds().Center(), 2)) 49 | 50 | s := new(state) 51 | 52 | go func() { 53 | for range time.Tick(96 * time.Millisecond) { 54 | s.Update(time.Since(start)) 55 | } 56 | }() 57 | 58 | txt := text.New(pixel.V(-250+left, 50+up), text.Atlas7x13) 59 | txt.WriteString("THE MAGIC HAND OF") 60 | 61 | for !win.Closed() { 62 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 63 | win.Clear(color.RGBA{24, 24, 24, 255}) 64 | 65 | txt.Draw(win, pixel.IM.ScaledXY(txt.Orig, pixel.V(2, 1))) 66 | 67 | for y := 0; y < 6; y++ { 68 | for x := 0; x < 6; x++ { 69 | t := texts[y][x] 70 | t.Clear() 71 | t.WriteString(string(s.field[y][x])) 72 | t.Draw(win, pixel.IM.ScaledXY(t.Orig, pixel.V(2, 1))) 73 | } 74 | } 75 | 76 | if win.JustPressed(pixelgl.KeySpace) { 77 | start = time.Now() 78 | } 79 | 80 | win.Update() 81 | } 82 | } 83 | 84 | func main() { 85 | rand.Seed(time.Now().UTC().UnixNano()) 86 | pixelgl.Run(run) 87 | } 88 | 89 | type field [6]row 90 | 91 | type row [6]rune 92 | 93 | func (r row) String() string { 94 | return fmt.Sprintf("%s %s %s %s %s %s", 95 | string(r[0]), 96 | string(r[1]), 97 | string(r[2]), 98 | string(r[3]), 99 | string(r[4]), 100 | string(r[5]), 101 | ) 102 | } 103 | 104 | type state struct { 105 | field field 106 | } 107 | 108 | func (s *state) Update(d time.Duration) bool { 109 | var letters string 110 | 111 | switch { 112 | case d < 1*time.Second: 113 | letters = "#" 114 | case d < 2*time.Second: 115 | letters = "ACEHN####" 116 | case d < 3*time.Second: 117 | letters = "ACEHN###" 118 | default: 119 | letters = "ACEHN##" 120 | 121 | if s.field[3].String() == "C H A N C E" { 122 | return true 123 | } 124 | } 125 | 126 | for y, row := range s.field { 127 | for x, _ := range row { 128 | if d > 3*time.Second && y == 3 { 129 | switch { 130 | case 131 | x == 0 && row[0] == 'C', 132 | x == 1 && row[0] == 'C' && row[1] == 'H', 133 | x == 2 && row[0] == 'C' && row[1] == 'H' && row[2] == 'A', 134 | x == 3 && row[0] == 'C' && row[1] == 'H' && row[2] == 'A' && row[3] == 'N', 135 | x == 4 && row[0] == 'C' && row[1] == 'H' && row[2] == 'A' && row[3] == 'N' && row[4] == 'C': 136 | continue 137 | } 138 | } 139 | 140 | s.field[y][x] = rune(letters[rand.Intn(len(letters))]) 141 | 142 | if rand.Intn(3) == 0 { 143 | texts[y][x].Color = colors[rand.Intn(len(colors))] 144 | } 145 | } 146 | } 147 | 148 | return false 149 | } 150 | 151 | func (s *state) String() string { 152 | var f string 153 | 154 | for _, row := range s.field { 155 | f += fmt.Sprintf("%s %s %s %s %s %s\n", 156 | string(row[0]), 157 | string(row[1]), 158 | string(row[2]), 159 | string(row[3]), 160 | string(row[4]), 161 | string(row[5]), 162 | ) 163 | } 164 | 165 | return f 166 | } 167 | 168 | func newText(x, y float64) *text.Text { 169 | return text.New(pixel.V(left+x, up+y), text.Atlas7x13) 170 | } 171 | -------------------------------------------------------------------------------- /julia.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math/cmplx" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/pixelgl" 10 | 11 | "github.com/peterhellberg/plasma/palette" 12 | ) 13 | 14 | const ( 15 | width, height = 1280, 720 16 | maxIterations = 1024 17 | juliaConstant = complex(-0.7, 0.27015) 18 | //juliaConstant = complex(0.285, 0.01) 19 | ) 20 | 21 | var z = 1.0 22 | 23 | func main() { 24 | pixelgl.Run(run) 25 | } 26 | 27 | func run() { 28 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 29 | Bounds: pixel.R(0, 0, float64(width), float64(height)), 30 | VSync: true, 31 | Undecorated: true, 32 | }) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | buffer := image.NewRGBA(image.Rect(0, 0, width, height)) 38 | canvas := pixelgl.NewCanvas(win.Bounds()) 39 | 40 | go draw(buffer) 41 | 42 | c := win.Bounds().Center() 43 | 44 | for !win.Closed() { 45 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 46 | 47 | if win.Pressed(pixelgl.KeyUp) { 48 | z += 0.01 49 | } 50 | 51 | if win.Pressed(pixelgl.KeyDown) { 52 | z -= 0.01 53 | } 54 | 55 | win.Clear(color.Black) 56 | 57 | canvas.SetPixels(buffer.Pix) 58 | canvas.Draw(win, pixel.IM.Moved(c)) 59 | 60 | win.Update() 61 | } 62 | } 63 | 64 | func draw(buffer *image.RGBA) { 65 | for iterations := 0; iterations < maxIterations; iterations++ { 66 | i := &imageIterator{bounds: buffer.Bounds()} 67 | 68 | for i.next() { 69 | center := i.centered() 70 | 71 | value := calculateValue( 72 | complex(1.5*real(center)/(0.45*width*z), imag(center)/(0.45*height*z)), 73 | iterations, 74 | ) 75 | 76 | if value >= 18 { 77 | r, g, b := palette.MaterialDesign500[value%255].RGB255() 78 | 79 | buffer.Set(i.X, i.Y, color.RGBA{r, g, b, 255}) 80 | } else { 81 | buffer.Set(i.X, i.Y, color.RGBA{0, 0, 0, 255}) 82 | } 83 | } 84 | } 85 | 86 | } 87 | 88 | func calculateValue(value complex128, iterations int) (i int) { 89 | for i = 0; i < iterations; i++ { 90 | value = (value * value) + juliaConstant 91 | 92 | if cmplx.Abs(value) > 4.0 { 93 | return i 94 | } 95 | } 96 | 97 | return i 98 | } 99 | 100 | type imageIterator struct { 101 | image.Point 102 | bounds image.Rectangle 103 | } 104 | 105 | func (i *imageIterator) centered() complex128 { 106 | return complex( 107 | float64(i.X)-float64(i.bounds.Max.X)/2.0, 108 | float64(i.Y)-float64(i.bounds.Max.Y)/2.0, 109 | ) 110 | } 111 | 112 | func (i *imageIterator) next() bool { 113 | if i.X < i.bounds.Max.X { 114 | i.X++ 115 | 116 | return true 117 | } 118 | 119 | if i.Y < i.bounds.Max.Y { 120 | i.X = 0 121 | i.Y++ 122 | 123 | return true 124 | } 125 | 126 | return false 127 | } 128 | -------------------------------------------------------------------------------- /metaballs/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/metaballs 2 | 3 | https://gist.github.com/peterhellberg/79034ab4c4746b55ebc9b9f04c1eccfc 4 | 5 | ![metaballs](https://user-images.githubusercontent.com/565124/31692148-ec0a23a6-b398-11e7-9d04-6f3b1f01393d.gif) 6 | ![metaballs](https://user-images.githubusercontent.com/565124/31692972-7575c6a6-b39c-11e7-9de1-1d1c9a04e617.gif) 7 | ![metaballs](https://assets.c7.se/screenshots/metaballs-20171018-000313.png) 8 | ![metaballs](https://assets.c7.se/screenshots/metaballs-20171018-000454.png) 9 | ![metaballs](https://assets.c7.se/screenshots/metaballs-20171018-000044.png) 10 | -------------------------------------------------------------------------------- /metaballs/metaballs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | width = 600 17 | height = 600 18 | seed = 6502 19 | numCircles = 4 20 | ) 21 | 22 | var ( 23 | threshold = 0.6 24 | pxSize = 3 25 | ) 26 | 27 | type circle struct { 28 | x int 29 | y int 30 | r int 31 | vx int 32 | vy int 33 | color color.RGBA 34 | } 35 | 36 | var circles = []*circle{} 37 | 38 | func init() { 39 | rand.Seed(seed) 40 | 41 | for i := 0; i < numCircles; i++ { 42 | c := color.RGBA{ 43 | uint8(random(100, 255)), 44 | uint8(random(20, 255)), 45 | uint8(random(50, 255)), 46 | 255, 47 | } 48 | 49 | circles = append(circles, &circle{ 50 | x: random(width/3, width), 51 | y: random(height/3, height), 52 | r: random(64, 128), 53 | vx: random(-3, 3), 54 | vy: random(-3, 3), 55 | color: c, 56 | }) 57 | } 58 | } 59 | 60 | func drawPicture() *pixel.PictureData { 61 | m := image.NewRGBA(image.Rect(0, 0, width, height)) 62 | 63 | draw.Draw(m, m.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src) 64 | 65 | for x := 0; x < width; x += pxSize { 66 | for y := 0; y < height; y += pxSize { 67 | sum, closestD2 := 0.0, math.MaxFloat64 68 | 69 | var closestColor color.RGBA 70 | 71 | for _, c := range circles { 72 | dx := float64(x - c.x) 73 | dy := float64(y - c.y) 74 | d2 := dx*dx + dy*dy 75 | 76 | if d2 >= 0 { 77 | sum += float64(c.r) * float64(c.r) / d2 78 | } 79 | 80 | if d2 < closestD2 { 81 | closestD2 = d2 82 | closestColor = c.color 83 | } 84 | } 85 | 86 | if sum > threshold { 87 | rect := image.Rect(x, y, x+pxSize, y+pxSize) 88 | draw.Draw(m, rect, &image.Uniform{closestColor}, image.ZP, draw.Src) 89 | } else { 90 | m.Set(x, y, color.RGBA{150, 150, 250, 255}) 91 | } 92 | } 93 | } 94 | 95 | return pixel.PictureDataFromImage(m) 96 | } 97 | 98 | func update() { 99 | for _, c := range circles { 100 | c.x += c.vx 101 | c.y += c.vy 102 | 103 | if c.x-c.r < 0+c.r { 104 | c.vx = +abs(c.vx) 105 | } 106 | 107 | if c.x+c.r > width-c.r { 108 | c.vx = -abs(c.vx) 109 | } 110 | 111 | if c.y-c.r < 0+c.r { 112 | c.vy = +abs(c.vy) 113 | } 114 | 115 | if c.y+c.r > height-c.r { 116 | c.vy = -abs(c.vy) 117 | } 118 | } 119 | } 120 | 121 | func run() { 122 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 123 | Bounds: pixel.R(0, 0, float64(width), float64(height)), 124 | VSync: true, 125 | Undecorated: true, 126 | Resizable: false, 127 | }) 128 | if err != nil { 129 | panic(err) 130 | } 131 | 132 | c := win.Bounds().Center() 133 | 134 | go func() { 135 | for { 136 | update() 137 | time.Sleep(60 * time.Millisecond) 138 | } 139 | }() 140 | 141 | for !win.Closed() { 142 | win.Update() 143 | 144 | p := drawPicture() 145 | 146 | s := pixel.NewSprite(p, p.Bounds()) 147 | s.Draw(win, pixel.IM.Moved(c).Scaled(c, 1)) 148 | 149 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 150 | return 151 | } 152 | 153 | if win.Pressed(pixelgl.KeyRight) { 154 | threshold += 0.01 155 | } 156 | 157 | if win.Pressed(pixelgl.KeyLeft) { 158 | threshold -= 0.01 159 | } 160 | 161 | if win.Pressed(pixelgl.KeyUp) { 162 | pxSize++ 163 | } 164 | 165 | if win.Pressed(pixelgl.KeyDown) { 166 | if pxSize > 1 { 167 | pxSize-- 168 | } 169 | } 170 | 171 | if win.JustPressed(pixelgl.KeyR) { 172 | for _, circle := range circles { 173 | circle.color = color.RGBA{ 174 | uint8(random(100, 255)), 175 | uint8(random(20, 255)), 176 | uint8(random(50, 255)), 177 | 155, 178 | } 179 | } 180 | } 181 | 182 | if win.JustPressed(pixelgl.KeyS) { 183 | c := color.RGBA{ 184 | uint8(random(100, 255)), 185 | uint8(random(20, 255)), 186 | uint8(random(50, 255)), 187 | 155, 188 | } 189 | 190 | for _, circle := range circles { 191 | circle.color = c 192 | } 193 | } 194 | } 195 | } 196 | 197 | func main() { 198 | pixelgl.Run(run) 199 | } 200 | 201 | func random(min, max int) int { 202 | return min - rand.Intn(max-min) 203 | } 204 | 205 | func abs(n int) int { 206 | if n < 0 { 207 | return -n 208 | } 209 | 210 | return n 211 | } 212 | -------------------------------------------------------------------------------- /minimal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | 6 | "github.com/faiface/pixel" 7 | "github.com/faiface/pixel/pixelgl" 8 | ) 9 | 10 | func run() { 11 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 12 | Bounds: pixel.R(0, 0, float64(512), float64(512)), 13 | VSync: true, 14 | Undecorated: true, 15 | }) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | win.Clear(color.RGBA{255, 255, 0, 255}) 21 | 22 | for !win.Closed() { 23 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 24 | win.Update() 25 | } 26 | } 27 | 28 | func main() { 29 | pixelgl.Run(run) 30 | } 31 | -------------------------------------------------------------------------------- /particles/.gitignore: -------------------------------------------------------------------------------- 1 | falling-red 2 | fireworks 3 | particles 4 | small-particles 5 | snow 6 | -------------------------------------------------------------------------------- /particles/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/particles 2 | 3 | https://gist.github.com/peterhellberg/b63c0afa93cfbcc02cdab96be87f1de0 4 | 5 | ## Experiments 6 | 7 | ### [particles](/particles/particles.go) 8 | ![](https://user-images.githubusercontent.com/565124/28490620-e47474c4-6ede-11e7-856f-00aa72ca6aa2.gif) 9 | ![](https://user-images.githubusercontent.com/565124/28492208-29f66672-6eff-11e7-8222-0b25fa9539e6.gif) 10 | 11 | ### [small-particles](/particles/small-particles.go) 12 | 13 | ### [falling-red](/particles/falling-red.go) 14 | ![](https://camo.githubusercontent.com/08399bbb390546b768b14613ed22dfba098b03f6/68747470733a2f2f6173736574732e63372e73652f73637265656e73686f74732f706978656c2d7061727469636c65732d66616c6c696e672d7265642d32303137303830322d3135353230392e706e67) 15 | 16 | ### [fireworks](/particles/fireworks.go) 17 | 18 | ### [snow](/particles/snow.go) 19 | ![](https://user-images.githubusercontent.com/565124/28492836-e70c6210-6f0a-11e7-8e30-246a33a566d1.gif) 20 | -------------------------------------------------------------------------------- /particles/attraction-repulsion/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/particles/attraction-repulsion 2 | 3 | https://gist.github.com/peterhellberg/8ba8dd0e55d537f9a9582bb321732dc9 4 | 5 | Attraction and Repulsion Forces based on [The Coding Train - Coding Challenge #56](https://www.youtube.com/watch?v=OAcXnzRNiCY) 6 | 7 | | Key | Description | 8 | |------------ |---------------------------------| 9 | | C | Clear particles and attractors | 10 | | A | Add attractor at mouse position | 11 | | P | Add particle at mouse position | 12 | | Up | Increase gravity | 13 | | Down | Decrease gravity | 14 | | Mouse Left | Add particles | 15 | | Mouse Right | Add attractor | 16 | 17 | ![](https://user-images.githubusercontent.com/565124/34926298-a841177c-f9ae-11e7-9a80-ba8e276506fd.png) 18 | 19 | ![](https://user-images.githubusercontent.com/565124/34997896-ec9eb1ce-fadd-11e7-91fc-a40d942678c4.png) 20 | 21 | ![](https://user-images.githubusercontent.com/565124/34950598-df4d2688-fa13-11e7-8f25-3e669f5b9045.png) 22 | ![](https://user-images.githubusercontent.com/565124/34950611-ed187204-fa13-11e7-81af-2d72e4c40ecf.png) 23 | 24 | ## Links 25 | 26 | - https://github.com/CodingTrain/Rainbow-Code/tree/master/CodingChallenges/CC_56_attraction_repulsion 27 | - https://www.youtube.com/watch?v=OAcXnzRNiCY 28 | -------------------------------------------------------------------------------- /particles/attraction-repulsion/attraction-repulsion.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image/color" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/imdraw" 11 | "github.com/faiface/pixel/pixelgl" 12 | ) 13 | 14 | var w, h int 15 | 16 | var G float64 17 | 18 | func run() { 19 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 20 | Title: "Attraction Repulsion", 21 | Bounds: pixel.R(0, 0, float64(w), float64(h)), 22 | VSync: true, 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | var ( 30 | last = time.Now() 31 | particles = []*particle{} 32 | attractors = []pixel.Vec{} 33 | ) 34 | 35 | for !win.Closed() { 36 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 37 | 38 | dt := time.Since(last).Seconds() 39 | last = time.Now() 40 | 41 | imd := imdraw.New(nil) 42 | imd.Precision = 6 43 | 44 | for _, p := range particles { 45 | p.update(dt, attractors) 46 | 47 | if win.Bounds().Contains(p.pos) { 48 | imd.Color = color.NRGBA{45, 47, 49, 200} 49 | imd.Push(p.pos) 50 | imd.Circle(p.mass, 0) 51 | 52 | imd.Color = color.NRGBA{45, 47, 49, 40} 53 | imd.Push(p.pos) 54 | imd.Circle(4, 1.2) 55 | 56 | imd.Color = color.NRGBA{0, 0, 0, 16} 57 | imd.Push(p.pos, p.pos.Add(p.vel.Unit().Scaled(16))) 58 | imd.Line(3) 59 | } 60 | } 61 | 62 | for _, a := range attractors { 63 | for _, p := range particles { 64 | if l := a.Sub(p.pos).Len(); l < 112 { 65 | imd.Color = color.NRGBA{54, 57, 59, 0} 66 | imd.Push(p.pos) 67 | imd.Color = color.NRGBA{119, 187, 17, 112 - uint8(l)} 68 | imd.Push(a) 69 | imd.Line(3) 70 | 71 | switch { 72 | case l < 80: 73 | imd.Push(p.pos) 74 | imd.Circle(3, 0) 75 | case l < 96: 76 | imd.Push(p.pos) 77 | imd.Circle(2.5, 0) 78 | case l < 112: 79 | imd.Push(p.pos) 80 | imd.Circle(2, 0) 81 | } 82 | } 83 | } 84 | } 85 | 86 | for _, a := range attractors { 87 | imd.Color = color.NRGBA{119, 187, 17, 255} 88 | imd.Push(a) 89 | imd.Circle(12, 0) 90 | 91 | imd.Color = color.NRGBA{199, 244, 100, 255} 92 | imd.Push(a) 93 | imd.Circle(6, 0) 94 | } 95 | 96 | win.Clear(color.NRGBA{54, 57, 59, 255}) 97 | imd.Draw(win) 98 | 99 | if win.JustPressed(pixelgl.KeyC) { 100 | particles = []*particle{} 101 | attractors = []pixel.Vec{} 102 | } 103 | 104 | if win.JustPressed(pixelgl.MouseButtonRight) || win.JustPressed(pixelgl.KeyA) { 105 | attractors = append(attractors, win.MousePosition()) 106 | } 107 | 108 | if win.Pressed(pixelgl.KeyUp) { 109 | G += 0.01 110 | } 111 | 112 | if win.Pressed(pixelgl.KeyDown) { 113 | G -= 0.01 114 | } 115 | 116 | if win.Pressed(pixelgl.MouseButtonLeft) || win.JustPressed(pixelgl.KeyP) { 117 | particles = append(particles, &particle{ 118 | pos: win.MousePosition(), 119 | vel: pixel.V(rand.Float64()-0.5, rand.Float64()-0.5).Scaled(0.15), 120 | mass: 4 + rand.Float64()*6, 121 | }) 122 | } 123 | 124 | win.Update() 125 | } 126 | } 127 | 128 | func main() { 129 | flag.IntVar(&w, "w", 1024, "width") 130 | flag.IntVar(&h, "h", 576, "width") 131 | flag.Float64Var(&G, "G", 0.6673, "gravity") 132 | flag.Parse() 133 | 134 | rand.Seed(42) 135 | 136 | pixelgl.Run(run) 137 | } 138 | 139 | type particle struct { 140 | vel pixel.Vec 141 | pos pixel.Vec 142 | acc pixel.Vec 143 | 144 | mass float64 145 | } 146 | 147 | func (p *particle) update(dt float64, attractors []pixel.Vec) { 148 | for _, a := range attractors { 149 | f := a.Sub(p.pos) 150 | d := f.Len() 151 | 152 | if d > 128 { 153 | d = 128 154 | } 155 | 156 | s := ((G * ((p.mass + 1) * 3)) / (d * d)) 157 | 158 | f = f.Unit().Scaled(s) 159 | 160 | if d < 24 { 161 | p.acc = a.To(p.pos).Unit() 162 | } 163 | 164 | p.acc = p.acc.Add(f) 165 | } 166 | 167 | p.pos = p.pos.Add(p.vel) 168 | p.vel = p.vel.Add(p.acc).Scaled(1 / p.vel.Len() * (p.mass / 12)) // Dampening by speed 169 | p.acc = p.acc.Scaled(0) 170 | } 171 | -------------------------------------------------------------------------------- /particles/boids/.gitignore: -------------------------------------------------------------------------------- 1 | boids-jerky-movement 2 | boids-liquid-borders 3 | boids-single-color-liquid-borders 4 | boids-walls 5 | -------------------------------------------------------------------------------- /particles/boids/steps/.gitignore: -------------------------------------------------------------------------------- 1 | step01 2 | step02 3 | step03 4 | step04 5 | step05 6 | step06 7 | step07 8 | step08 9 | step09 10 | step10 11 | step11 12 | step12 13 | step13 14 | step14 15 | step15 16 | step16 17 | step17 18 | step18 19 | -------------------------------------------------------------------------------- /particles/boids/steps/step01.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 768, 432 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, fw, fh), 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | canvas := pixelgl.NewCanvas(win.Bounds()) 32 | 33 | particles := []*particle{} 34 | obstacles := []*particle{} 35 | 36 | last := time.Now() 37 | 38 | for !win.Closed() { 39 | dt := time.Since(last).Seconds() 40 | last = time.Now() 41 | 42 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 43 | 44 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 45 | 46 | if win.JustPressed(pixelgl.KeyC) { 47 | particles = nil 48 | obstacles = nil 49 | 50 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 51 | } 52 | 53 | fx, fy := win.MousePosition().XY() 54 | x := int(fx) 55 | y := int(fy) 56 | 57 | if win.JustPressed(pixelgl.MouseButtonRight) { 58 | obstacles = append(obstacles, 59 | newParticle(fx, fy, 0, 0, 100, color.RGBA{255, 255, 255, 255}), 60 | ) 61 | } 62 | 63 | if win.JustPressed(pixelgl.MouseButtonLeft) { 64 | c := color.RGBA{255, uint8(y % 255), uint8(x % 255), 255} 65 | 66 | for i := 0; i < 10; i++ { 67 | angle := 90.0 + rand.Float64()*180.0 68 | speed := 40.0 + dt + (20.0 * rand.Float64()) 69 | life := 0.2 + (2.0 * rand.Float64()) 70 | 71 | particles = append(particles, 72 | newParticle(fx, fy, angle, speed, life, c), 73 | ) 74 | } 75 | } 76 | 77 | for _, p := range particles { 78 | p.update(dt) 79 | 80 | buffer.Set(int(p.position.X), int(p.position.Y), p.color) 81 | } 82 | 83 | for _, o := range obstacles { 84 | buffer.Set(int(o.position.X), int(o.position.Y), o.color) 85 | } 86 | 87 | win.Clear(color.RGBA{0, 0, 0, 255}) 88 | 89 | canvas.SetPixels(buffer.Pix) 90 | 91 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 92 | 93 | win.Update() 94 | } 95 | } 96 | 97 | func main() { 98 | pixelgl.Run(run) 99 | } 100 | 101 | type particle struct { 102 | position pixel.Vec 103 | velocity pixel.Vec 104 | color color.RGBA 105 | life float64 106 | } 107 | 108 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 109 | angleInRadians := angle * math.Pi / 180 110 | 111 | return &particle{ 112 | position: pixel.Vec{x, y}, 113 | velocity: pixel.Vec{ 114 | X: speed * math.Cos(angleInRadians), 115 | Y: -speed * math.Sin(angleInRadians), 116 | }, 117 | life: life, 118 | color: c, 119 | } 120 | } 121 | 122 | func (p *particle) update(dt float64) { 123 | p.life -= dt 124 | 125 | if p.life > 0 { 126 | p.position.X += p.velocity.X * dt 127 | p.position.Y += p.velocity.Y * dt 128 | 129 | if p.life > 1.5 { 130 | if rand.Float64() < 0.4+dt { 131 | p.color.R -= 1 132 | p.color.G -= 1 133 | p.color.B -= 1 134 | } 135 | } else { 136 | p.color.R -= 1 137 | p.color.G -= 1 138 | p.color.B -= 1 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /particles/boids/steps/step02.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 768, 432 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, fw, fh), 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | canvas := pixelgl.NewCanvas(win.Bounds()) 32 | 33 | particles := []*particle{} 34 | obstacles := []*particle{} 35 | 36 | last := time.Now() 37 | 38 | white := color.RGBA{255, 255, 255, 255} 39 | 40 | for !win.Closed() { 41 | dt := time.Since(last).Seconds() 42 | last = time.Now() 43 | 44 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 45 | 46 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 47 | 48 | if win.JustPressed(pixelgl.KeyC) { 49 | particles = nil 50 | obstacles = nil 51 | 52 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 53 | } 54 | 55 | fx, fy := win.MousePosition().XY() 56 | 57 | if win.JustPressed(pixelgl.KeyO) { 58 | obstacles = append(obstacles, newParticle(fx, fy, 0, 0, 100, white)) 59 | } 60 | 61 | if win.JustPressed(pixelgl.MouseButtonLeft) { 62 | particles = append(particles, newParticle(fx, fy, 0, 0, 100, 63 | color.RGBA{255, uint8(int(fy) % 255), uint8(int(fx) % 255), 255}, 64 | )) 65 | } 66 | 67 | for _, p := range particles { 68 | p.update(dt) 69 | buffer.Set(int(p.position.X), int(p.position.Y), p.color) 70 | } 71 | 72 | for _, o := range obstacles { 73 | o.update(dt) 74 | buffer.Set(int(o.position.X), int(o.position.Y), o.color) 75 | } 76 | 77 | win.Clear(color.RGBA{0, 0, 0, 255}) 78 | 79 | canvas.SetPixels(buffer.Pix) 80 | 81 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 82 | 83 | win.Update() 84 | } 85 | } 86 | 87 | func main() { 88 | pixelgl.Run(run) 89 | } 90 | 91 | type particle struct { 92 | position pixel.Vec 93 | velocity pixel.Vec 94 | color color.RGBA 95 | life float64 96 | } 97 | 98 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 99 | angleInRadians := angle * math.Pi / 180 100 | 101 | return &particle{ 102 | position: pixel.Vec{x, y}, 103 | velocity: pixel.Vec{ 104 | X: speed * math.Cos(angleInRadians), 105 | Y: -speed * math.Sin(angleInRadians), 106 | }, 107 | life: life, 108 | color: c, 109 | } 110 | } 111 | 112 | func (p *particle) update(dt float64) { 113 | p.life -= dt 114 | 115 | if p.life > 0 { 116 | p.position.X += p.velocity.X * dt 117 | p.position.Y += p.velocity.Y * dt 118 | 119 | if p.life > 1.5 { 120 | if rand.Float64() < 0.4+dt { 121 | p.color.R -= 1 122 | p.color.G -= 1 123 | p.color.B -= 1 124 | } 125 | } else { 126 | p.color.R -= 1 127 | p.color.G -= 1 128 | p.color.B -= 1 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /particles/boids/steps/step03.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 768, 432 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func init() { 21 | rand.Seed(time.Now().UnixNano()) 22 | } 23 | 24 | func run() { 25 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 26 | Bounds: pixel.R(0, 0, fw, fh), 27 | Undecorated: true, 28 | }) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | canvas := pixelgl.NewCanvas(win.Bounds()) 34 | 35 | boids := []*boid{} 36 | avoids := []*avoid{} 37 | 38 | last := time.Now() 39 | 40 | white := color.RGBA{255, 255, 255, 255} 41 | 42 | for !win.Closed() { 43 | dt := time.Since(last).Seconds() 44 | last = time.Now() 45 | 46 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 47 | 48 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 49 | 50 | if win.JustPressed(pixelgl.KeyC) { 51 | boids = nil 52 | avoids = nil 53 | 54 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 55 | } 56 | 57 | fx, fy := win.MousePosition().XY() 58 | 59 | if win.JustPressed(pixelgl.KeyO) { 60 | avoids = append(avoids, newAvoid(fx, fy, 10, white)) 61 | } 62 | 63 | if win.JustPressed(pixelgl.MouseButtonLeft) { 64 | angle := 90.0 + rand.Float64()*180.0*flip() 65 | speed := 40.0 + dt + (20.0 * rand.Float64()) 66 | 67 | boids = append(boids, newBoid(fx, fy, angle, speed, 68 | color.RGBA{255, uint8(int(fy) % 255), uint8(int(fx) % 255), 255}, 69 | )) 70 | } 71 | 72 | for _, p := range boids { 73 | p.update(dt) 74 | buffer.Set(int(p.position.X), int(p.position.Y), p.color) 75 | } 76 | 77 | for _, a := range avoids { 78 | buffer.Set(int(a.position.X), int(a.position.Y), a.color) 79 | } 80 | 81 | win.Clear(color.RGBA{0, 0, 0, 255}) 82 | 83 | canvas.SetPixels(buffer.Pix) 84 | 85 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 86 | 87 | win.Update() 88 | } 89 | } 90 | 91 | func main() { 92 | pixelgl.Run(run) 93 | } 94 | 95 | func newAvoid(x, y, s float64, c color.RGBA) *avoid { 96 | p := pixel.Vec{x, y} 97 | 98 | return &avoid{position: p, size: s, color: c} 99 | } 100 | 101 | type avoid struct { 102 | position pixel.Vec 103 | size float64 104 | color color.RGBA 105 | } 106 | 107 | type boid struct { 108 | position pixel.Vec 109 | velocity pixel.Vec 110 | color color.RGBA 111 | } 112 | 113 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 114 | angleInRadians := angle * math.Pi / 180 115 | 116 | return &boid{ 117 | position: pixel.Vec{x, y}, 118 | velocity: pixel.Vec{ 119 | X: speed * math.Cos(angleInRadians), 120 | Y: -speed * math.Sin(angleInRadians), 121 | }, 122 | color: c, 123 | } 124 | } 125 | 126 | func (p *boid) update(dt float64) { 127 | p.position.X += p.velocity.X * dt 128 | p.position.Y += p.velocity.Y * dt 129 | 130 | if rand.Float64() < 0.4+dt { 131 | p.color.R -= 1 132 | p.color.G -= 1 133 | p.color.B -= 1 134 | } 135 | } 136 | 137 | func flip() float64 { 138 | if rand.Float64() > 0.5 { 139 | return 1.0 140 | } 141 | 142 | return -1.0 143 | } 144 | -------------------------------------------------------------------------------- /particles/boids/steps/step04.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 768, 432 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | var ( 21 | boids = []*boid{} 22 | avoids = []*avoid{} 23 | 24 | white = color.RGBA{255, 255, 255, 255} 25 | ) 26 | 27 | func init() { 28 | rand.Seed(time.Now().UnixNano()) 29 | 30 | for x := 0; x < w; x += 15 { 31 | avoids = append(avoids, newAvoid(float64(x), float64(10), 10, white)) 32 | avoids = append(avoids, newAvoid(float64(x), fh-10, 10, white)) 33 | } 34 | } 35 | 36 | func run() { 37 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 38 | Bounds: pixel.R(0, 0, fw, fh), 39 | Undecorated: true, 40 | }) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | canvas := pixelgl.NewCanvas(win.Bounds()) 46 | 47 | last := time.Now() 48 | 49 | for !win.Closed() { 50 | dt := time.Since(last).Seconds() 51 | last = time.Now() 52 | 53 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 54 | 55 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 56 | 57 | if win.JustPressed(pixelgl.KeyC) { 58 | boids = nil 59 | avoids = nil 60 | 61 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 62 | } 63 | 64 | fx, fy := win.MousePosition().XY() 65 | 66 | if win.JustPressed(pixelgl.KeyO) { 67 | avoids = append(avoids, newAvoid(fx, fy, 10, white)) 68 | } 69 | 70 | if win.JustPressed(pixelgl.MouseButtonLeft) { 71 | angle := 90.0 + rand.Float64()*180.0*flip() 72 | speed := 20.0 + dt + (10.0 * rand.Float64()) 73 | 74 | boids = append(boids, newBoid(fx, fy, angle, speed, 75 | color.RGBA{255, uint8(int(fy) % 255), uint8(int(fx) % 255), 255}, 76 | )) 77 | } 78 | 79 | for _, p := range boids { 80 | p.update(dt) 81 | buffer.Set(int(p.position.X), int(p.position.Y), p.color) 82 | } 83 | 84 | for _, a := range avoids { 85 | buffer.Set(int(a.position.X), int(a.position.Y), a.color) 86 | } 87 | 88 | win.Clear(color.RGBA{0, 0, 0, 255}) 89 | 90 | canvas.SetPixels(buffer.Pix) 91 | 92 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 93 | 94 | win.Update() 95 | } 96 | } 97 | 98 | func main() { 99 | pixelgl.Run(run) 100 | } 101 | 102 | func newAvoid(x, y, s float64, c color.RGBA) *avoid { 103 | return &avoid{position: pixel.Vec{x, y}, size: s, color: c} 104 | } 105 | 106 | type avoid struct { 107 | position pixel.Vec 108 | size float64 109 | color color.RGBA 110 | } 111 | 112 | type boid struct { 113 | position pixel.Vec 114 | velocity pixel.Vec 115 | color color.RGBA 116 | } 117 | 118 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 119 | angleInRadians := angle * math.Pi / 180 120 | 121 | return &boid{ 122 | position: pixel.Vec{x, y}, 123 | velocity: pixel.Vec{ 124 | X: speed * math.Cos(angleInRadians), 125 | Y: -speed * math.Sin(angleInRadians), 126 | }, 127 | color: c, 128 | } 129 | } 130 | 131 | func (b *boid) update(dt float64) { 132 | b.updatePosition(dt) 133 | b.updateColor(dt) 134 | } 135 | 136 | func (b *boid) updatePosition(dt float64) { 137 | b.position.X += b.velocity.X * dt 138 | b.position.Y += b.velocity.Y * dt 139 | 140 | if b.position.X < 0 { 141 | b.position.X = fw 142 | } 143 | 144 | if b.position.X > fw { 145 | b.position.X = 0 146 | } 147 | 148 | if b.position.Y < 0 { 149 | b.position.Y = fh 150 | } 151 | 152 | if b.position.Y > fh { 153 | b.position.Y = 0 154 | } 155 | } 156 | 157 | func (b *boid) updateColor(dt float64) { 158 | if rand.Float64() < 0.4+dt { 159 | b.color.R -= 1 160 | b.color.G -= 1 161 | b.color.B -= 1 162 | } 163 | } 164 | 165 | func flip() float64 { 166 | if rand.Float64() > 0.5 { 167 | return 1.0 168 | } 169 | 170 | return -1.0 171 | } 172 | -------------------------------------------------------------------------------- /particles/boids/steps/step05.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | //w, h = 768, 432 17 | w, h = 192, 108 18 | fw, fh = float64(w), float64(h) 19 | 20 | globalScale = 0.12 21 | ) 22 | 23 | var ( 24 | maxSpeed = 2.1 * globalScale 25 | friendRadius = 60 * globalScale 26 | crowdRadius = friendRadius / 1.3 27 | avoidRadius = 90 * globalScale 28 | coheseRadius = friendRadius 29 | 30 | boids = []*boid{} 31 | avoids = []*avoid{} 32 | 33 | red = color.RGBA{255, 55, 55, 255} 34 | gray = color.RGBA{105, 105, 105, 255} 35 | ) 36 | 37 | func init() { 38 | rand.Seed(time.Now().UnixNano()) 39 | 40 | setup() 41 | } 42 | 43 | func setup() { 44 | for x := 0; x < w; x += 10 { 45 | avoids = append(avoids, newAvoid(pixel.V(float64(x), 10), 10, red)) 46 | avoids = append(avoids, newAvoid(pixel.V(float64(x), fh-10), 10, red)) 47 | } 48 | } 49 | 50 | func run() { 51 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 52 | Bounds: pixel.R(0, 0, fw, fh), 53 | Undecorated: true, 54 | VSync: true, 55 | }) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | canvas := pixelgl.NewCanvas(win.Bounds()) 61 | 62 | last := time.Now() 63 | 64 | for !win.Closed() { 65 | dt := time.Since(last).Seconds() 66 | last = time.Now() 67 | 68 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 69 | 70 | if win.JustPressed(pixelgl.KeyC) { 71 | boids, avoids = nil, nil 72 | setup() 73 | } 74 | 75 | pos := win.MousePosition() 76 | 77 | if win.JustPressed(pixelgl.KeyO) { 78 | avoids = append(avoids, newAvoid(pos, 10, red)) 79 | } 80 | 81 | if win.JustPressed(pixelgl.MouseButtonLeft) { 82 | boids = append(boids, randomColorBoidAt(pos, dt)) 83 | } 84 | 85 | win.Clear(color.RGBA{0, 0, 0, 255}) 86 | 87 | drawFrame(canvas, dt) 88 | 89 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 90 | 91 | win.Update() 92 | } 93 | } 94 | 95 | func drawFrame(canvas *pixelgl.Canvas, dt float64) { 96 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 97 | 98 | for _, b := range boids { 99 | b.move(dt) 100 | b.draw(buffer) 101 | } 102 | 103 | for _, a := range avoids { 104 | a.draw(buffer) 105 | } 106 | 107 | canvas.SetPixels(buffer.Pix) 108 | } 109 | 110 | func randomColorBoidAt(p pixel.Vec, dt float64) *boid { 111 | angle := 90.0 + rand.Float64()*180.0*flip() 112 | speed := 20.0 + dt + (10.0 * rand.Float64()) 113 | 114 | return newBoid(p.X, p.Y, angle, speed, 115 | color.RGBA{255, uint8(int(p.Y) % 255), uint8(int(p.X) % 255), 255}, 116 | ) 117 | } 118 | 119 | func main() { 120 | pixelgl.Run(run) 121 | } 122 | 123 | func newAvoid(p pixel.Vec, s float64, c color.RGBA) *avoid { 124 | return &avoid{position: p, size: s, color: c} 125 | } 126 | 127 | type avoid struct { 128 | position pixel.Vec 129 | size float64 130 | color color.RGBA 131 | } 132 | 133 | func (a *avoid) draw(buffer *image.RGBA) { 134 | buffer.Set(int(a.position.X), int(a.position.Y), a.color) 135 | buffer.Set(int(a.position.X+1), int(a.position.Y), a.color) 136 | buffer.Set(int(a.position.X+1), int(a.position.Y+1), a.color) 137 | } 138 | 139 | type boid struct { 140 | angle float64 141 | speed float64 142 | position pixel.Vec 143 | color color.RGBA 144 | friends []*boid 145 | } 146 | 147 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 148 | return &boid{ 149 | angle: angle, 150 | speed: speed, 151 | position: pixel.Vec{x, y}, 152 | color: c, 153 | friends: nil, 154 | } 155 | } 156 | 157 | func (b *boid) velocity() pixel.Vec { 158 | angleInRadians := b.angle * math.Pi / 180 159 | 160 | return pixel.Vec{ 161 | X: b.speed * math.Cos(angleInRadians), 162 | Y: -b.speed * math.Sin(angleInRadians), 163 | } 164 | } 165 | 166 | func (b *boid) move(dt float64) { 167 | b.updatePosition(dt) 168 | b.updateColor(dt) 169 | b.updateFriends() 170 | } 171 | 172 | func (b *boid) updatePosition(dt float64) { 173 | v := b.velocity() 174 | 175 | b.position.X += v.X * dt 176 | b.position.Y += v.Y * dt 177 | 178 | if b.position.X < 0 { 179 | b.position.X = fw 180 | } 181 | 182 | if b.position.X > fw { 183 | b.position.X = 0 184 | } 185 | 186 | if b.position.Y < 0 { 187 | b.position.Y = fh 188 | } 189 | 190 | if b.position.Y > fh { 191 | b.position.Y = 0 192 | } 193 | } 194 | 195 | func (b *boid) updateColor(dt float64) { 196 | b.color.R += 1 197 | b.color.G -= 1 198 | b.color.B -= 1 199 | 200 | b.color.R += 10 201 | b.color.G += 20 202 | b.color.B += 10 203 | } 204 | 205 | func (b *boid) updateFriends() { 206 | var nearby []*boid 207 | 208 | for _, t := range boids { 209 | if t != b { 210 | if math.Abs(t.position.X-b.position.X) < friendRadius && 211 | math.Abs(t.position.Y-b.position.Y) < friendRadius { 212 | 213 | fmt.Println("found nearby friend", t.angle) 214 | 215 | t.angle = -t.angle 216 | 217 | nearby = append(nearby, t) 218 | } 219 | } 220 | } 221 | 222 | b.friends = nearby 223 | } 224 | 225 | func (b *boid) draw(buffer *image.RGBA) { 226 | buffer.Set(int(b.position.X-1), int(b.position.Y-1), gray) 227 | buffer.Set(int(b.position.X+1), int(b.position.Y-1), gray) 228 | 229 | buffer.Set(int(b.position.X), int(b.position.Y), b.color) 230 | 231 | buffer.Set(int(b.position.X-1), int(b.position.Y+1), gray) 232 | buffer.Set(int(b.position.X+1), int(b.position.Y+1), gray) 233 | } 234 | 235 | func flip() float64 { 236 | if rand.Float64() > 0.5 { 237 | return 1.0 238 | } 239 | 240 | return -1.0 241 | } 242 | -------------------------------------------------------------------------------- /particles/boids/steps/step06.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "math" 9 | "math/rand" 10 | "time" 11 | 12 | "github.com/faiface/pixel" 13 | "github.com/faiface/pixel/pixelgl" 14 | ) 15 | 16 | const ( 17 | w, h = 768, 432 18 | //w, h = 192, 108 19 | fw, fh = float64(w), float64(h) 20 | 21 | globalScale = 1.0 22 | ) 23 | 24 | var ( 25 | maxSpeed = 2.1 * globalScale 26 | friendRadius = 60 * globalScale 27 | crowdRadius = friendRadius / 1.3 28 | avoidRadius = 90 * globalScale 29 | coheseRadius = friendRadius 30 | 31 | boids = Boids{} 32 | avoids = Avoids{} 33 | 34 | gray = color.RGBA{55, 55, 55, 255} 35 | ) 36 | 37 | func init() { 38 | rand.Seed(time.Now().UnixNano()) 39 | 40 | setup() 41 | } 42 | 43 | func setup() { 44 | for x := 0; x < w; x += 10 { 45 | avoids = append(avoids, newAvoid(pixel.V(float64(x), 10), 10, gray)) 46 | avoids = append(avoids, newAvoid(pixel.V(float64(x), fh-10), 10, gray)) 47 | } 48 | } 49 | 50 | func run() { 51 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 52 | Bounds: pixel.R(0, 0, fw, fh), 53 | Undecorated: true, 54 | VSync: true, 55 | }) 56 | if err != nil { 57 | panic(err) 58 | } 59 | 60 | canvas := pixelgl.NewCanvas(win.Bounds()) 61 | 62 | last := time.Now() 63 | 64 | for !win.Closed() { 65 | dt := time.Since(last).Seconds() 66 | last = time.Now() 67 | 68 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 69 | 70 | if win.JustPressed(pixelgl.KeyC) { 71 | boids, avoids = nil, nil 72 | setup() 73 | } 74 | 75 | pos := win.MousePosition() 76 | 77 | if win.JustPressed(pixelgl.KeyO) { 78 | avoids = append(avoids, newAvoid(pos, 10, gray)) 79 | } 80 | 81 | if win.JustPressed(pixelgl.MouseButtonLeft) { 82 | boids = append(boids, randomColorBoidAt(pos, dt)) 83 | } 84 | 85 | win.Clear(color.RGBA{0, 0, 0, 255}) 86 | 87 | drawFrame(canvas, dt) 88 | 89 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 90 | 91 | win.Update() 92 | } 93 | } 94 | 95 | func drawFrame(canvas *pixelgl.Canvas, dt float64) { 96 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 97 | 98 | for _, b := range boids { 99 | b.move(dt) 100 | b.draw(buffer) 101 | } 102 | 103 | for _, a := range avoids { 104 | a.draw(buffer) 105 | } 106 | 107 | canvas.SetPixels(buffer.Pix) 108 | } 109 | 110 | func randomColorBoidAt(p pixel.Vec, dt float64) *boid { 111 | angle := 90.0 + rand.Float64()*180.0*flip() 112 | speed := 20.0 + dt + (10.0 * rand.Float64()) 113 | 114 | return newBoid(p.X, p.Y, angle, speed, 115 | color.RGBA{ 116 | uint8(rand.Intn(200)), 117 | uint8(rand.Intn(200) + 55), 118 | uint8(rand.Intn(200) + 55), 119 | 255, 120 | }, 121 | ) 122 | } 123 | 124 | func main() { 125 | pixelgl.Run(run) 126 | } 127 | 128 | type Avoids []*avoid 129 | 130 | func newAvoid(p pixel.Vec, s float64, c color.RGBA) *avoid { 131 | return &avoid{position: p, size: s, color: c} 132 | } 133 | 134 | type avoid struct { 135 | position pixel.Vec 136 | size float64 137 | color color.RGBA 138 | } 139 | 140 | func (a *avoid) draw(m *image.RGBA) { 141 | x, y := int(a.position.X), int(a.position.Y) 142 | 143 | r := image.Rect(x-3, y-3, x+3, y+3) 144 | 145 | draw.Draw(m, r, &image.Uniform{a.color}, image.ZP, draw.Src) 146 | } 147 | 148 | type boid struct { 149 | angle float64 150 | speed float64 151 | position pixel.Vec 152 | color color.RGBA 153 | originalColor color.RGBA 154 | friends []*boid 155 | } 156 | 157 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 158 | return &boid{ 159 | angle: angle, 160 | speed: speed, 161 | position: pixel.Vec{x, y}, 162 | color: c, 163 | originalColor: c, 164 | friends: nil, 165 | } 166 | } 167 | 168 | func (b *boid) velocity() pixel.Vec { 169 | angleInRadians := b.angle * math.Pi / 180 170 | 171 | return pixel.Vec{ 172 | X: b.speed * math.Cos(angleInRadians), 173 | Y: -b.speed * math.Sin(angleInRadians), 174 | } 175 | } 176 | 177 | func (b *boid) move(dt float64) { 178 | b.updatePosition(dt) 179 | b.updateFriends() 180 | } 181 | 182 | func (b *boid) updatePosition(dt float64) { 183 | v := b.velocity() 184 | 185 | b.position.X += v.X * dt 186 | b.position.Y += v.Y * dt 187 | 188 | if b.position.X < 0 { 189 | b.position.X = fw 190 | } 191 | 192 | if b.position.X > fw { 193 | b.position.X = 0 194 | } 195 | 196 | if b.position.Y < 0 { 197 | b.position.Y = fh 198 | } 199 | 200 | if b.position.Y > fh { 201 | b.position.Y = 0 202 | } 203 | } 204 | 205 | type Boids []*boid 206 | 207 | func (b *boid) updateFriends() { 208 | var nearby []*boid 209 | 210 | for _, t := range boids { 211 | if t != b { 212 | if math.Abs(t.position.X-b.position.X) < friendRadius && 213 | math.Abs(t.position.Y-b.position.Y) < friendRadius { 214 | 215 | // change speed 216 | //if t.speed > 0 { t.speed -= 0.1 } 217 | 218 | // flip angle 219 | //t.angle = -t.angle 220 | 221 | nearby = append(nearby, t) 222 | } 223 | } 224 | } 225 | 226 | if len(nearby) > 0 { 227 | fmt.Println(b, "found", len(nearby), "nearby friends") 228 | 229 | for _, n := range nearby { 230 | n.color = color.RGBA{255, 0, 0, 255} 231 | } 232 | 233 | } else { 234 | b.color = b.originalColor 235 | } 236 | 237 | b.friends = nearby 238 | } 239 | 240 | func (b *boid) draw(m *image.RGBA) { 241 | x, y := int(b.position.X), int(b.position.Y) 242 | 243 | r := image.Rect(x-3, y-3, x+3, y+3) 244 | 245 | draw.Draw(m, r, &image.Uniform{b.color}, image.ZP, draw.Src) 246 | } 247 | 248 | func flip() float64 { 249 | if rand.Float64() > 0.5 { 250 | return 1.0 251 | } 252 | 253 | return -1.0 254 | } 255 | -------------------------------------------------------------------------------- /particles/boids/steps/step07.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "math" 9 | "math/rand" 10 | "time" 11 | 12 | "github.com/faiface/pixel" 13 | "github.com/faiface/pixel/pixelgl" 14 | ) 15 | 16 | const ( 17 | w, h = 192 * 3, 108 * 3 18 | fw, fh = float64(w), float64(h) 19 | 20 | globalScale = 0.5 21 | ) 22 | 23 | var ( 24 | maxSpeed = 2.1 * globalScale 25 | friendRadius = 60 * globalScale 26 | //crowdRadius = friendRadius / 1.3 27 | //avoidRadius = 90 * globalScale 28 | //coheseRadius = friendRadius 29 | 30 | boids = Boids{} 31 | avoids = Avoids{} 32 | 33 | gray = color.RGBA{55, 55, 55, 255} 34 | ) 35 | 36 | func init() { 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | setup() 40 | } 41 | 42 | func setup() { 43 | for x := 0; x < w+10; x += 10 { 44 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), 10), 0, gray)) 45 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), fh-10), 0, gray)) 46 | } 47 | } 48 | 49 | func run() { 50 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 51 | Bounds: pixel.R(0, 0, fw, fh), 52 | Undecorated: true, 53 | VSync: true, 54 | }) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | canvas := pixelgl.NewCanvas(win.Bounds()) 60 | 61 | last := time.Now() 62 | 63 | for !win.Closed() { 64 | dt := time.Since(last).Seconds() 65 | last = time.Now() 66 | 67 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 68 | 69 | if win.JustPressed(pixelgl.KeyC) { 70 | boids, avoids = nil, nil 71 | setup() 72 | } 73 | 74 | pos := win.MousePosition() 75 | 76 | if win.JustPressed(pixelgl.KeyO) { 77 | avoids = append(avoids, newAvoid(pos, 10, gray)) 78 | } 79 | 80 | if win.JustPressed(pixelgl.MouseButtonLeft) { 81 | boids = append(boids, randomColorBoidAt(pos, dt)) 82 | } 83 | 84 | win.Clear(color.RGBA{0, 0, 0, 255}) 85 | 86 | drawFrame(canvas, dt) 87 | 88 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 89 | 90 | win.Update() 91 | } 92 | } 93 | 94 | func drawFrame(canvas *pixelgl.Canvas, dt float64) { 95 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 96 | 97 | for _, a := range avoids { 98 | a.draw(buffer) 99 | } 100 | 101 | for _, b := range boids { 102 | b.move(dt) 103 | b.draw(buffer) 104 | } 105 | 106 | canvas.SetPixels(buffer.Pix) 107 | } 108 | 109 | func randomColor() color.RGBA { 110 | return color.RGBA{ 111 | uint8(rand.Intn(200)), 112 | uint8(rand.Intn(200) + 55), 113 | uint8(rand.Intn(200) + 55), 114 | 255, 115 | } 116 | } 117 | 118 | func randomColorBoidAt(p pixel.Vec, dt float64) *boid { 119 | angle := 90.0 + rand.Float64()*180.0*flip() 120 | speed := 20.0 + dt + (10.0 * rand.Float64()) 121 | 122 | return newBoid(p.X, p.Y, angle, speed, randomColor()) 123 | } 124 | 125 | type boid struct { 126 | angle float64 127 | speed float64 128 | position pixel.Vec 129 | velocity pixel.Vec 130 | color color.RGBA 131 | originalColor color.RGBA 132 | friends []*boid 133 | } 134 | 135 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 136 | angleInRadians := angle * math.Pi / 180 137 | 138 | return &boid{ 139 | angle: angle, 140 | speed: speed, 141 | position: pixel.Vec{x, y}, 142 | velocity: pixel.Vec{ 143 | X: speed * math.Cos(angleInRadians), 144 | Y: -speed * math.Sin(angleInRadians), 145 | }, 146 | color: c, 147 | originalColor: c, 148 | friends: nil, 149 | } 150 | } 151 | 152 | func (b *boid) move(dt float64) { 153 | b.updateFriends() 154 | b.flock() 155 | b.updatePosition(dt) 156 | } 157 | 158 | func (b *boid) flock() { 159 | 160 | } 161 | 162 | func (b *boid) updatePosition(dt float64) { 163 | b.position.X += b.velocity.X * dt 164 | b.position.Y += b.velocity.Y * dt 165 | 166 | if b.position.X < 0 { 167 | b.position.X = fw 168 | } 169 | 170 | if b.position.X > fw { 171 | b.position.X = 0 172 | } 173 | 174 | if b.position.Y < 0 { 175 | b.position.Y = fh 176 | } 177 | 178 | if b.position.Y > fh { 179 | b.position.Y = 0 180 | } 181 | } 182 | 183 | type Boids []*boid 184 | 185 | func (b *boid) updateFriends() { 186 | var nearby []*boid 187 | 188 | for _, t := range boids { 189 | if t != b { 190 | if math.Abs(t.position.X-b.position.X) < friendRadius && 191 | math.Abs(t.position.Y-b.position.Y) < friendRadius { 192 | 193 | // change speed 194 | //if t.speed > 0 { t.speed -= 0.1 } 195 | 196 | // flip angle 197 | t.angle = -t.angle 198 | 199 | nearby = append(nearby, t) 200 | } 201 | } 202 | } 203 | 204 | if len(nearby) > 0 { 205 | fmt.Println(b, "found", len(nearby), "nearby friends") 206 | 207 | //b.color = color.RGBA{0, 255, 0, 255} 208 | 209 | for _, n := range nearby { 210 | n.color = color.RGBA{200, 55, 55, 255} 211 | } 212 | } else { 213 | b.color = b.originalColor 214 | } 215 | 216 | b.friends = nearby 217 | } 218 | 219 | func (b *boid) draw(m *image.RGBA) { 220 | x, y := int(b.position.X), int(b.position.Y) 221 | 222 | r := image.Rect(x-3, y-3, x+3, y+3) 223 | 224 | draw.Draw(m, r, &image.Uniform{b.color}, image.ZP, draw.Src) 225 | } 226 | 227 | type Avoids []*avoid 228 | 229 | func newAvoid(p pixel.Vec, s float64, c color.RGBA) *avoid { 230 | return &avoid{position: p, size: s, color: c} 231 | } 232 | 233 | type avoid struct { 234 | position pixel.Vec 235 | size float64 236 | color color.RGBA 237 | } 238 | 239 | func (a *avoid) draw(m *image.RGBA) { 240 | x, y := int(a.position.X), int(a.position.Y) 241 | 242 | r := image.Rect(x-2, y-4, x+2, y+4) 243 | 244 | draw.Draw(m, r, &image.Uniform{a.color}, image.ZP, draw.Src) 245 | } 246 | 247 | func flip() float64 { 248 | if rand.Float64() > 0.5 { 249 | return 1.0 250 | } 251 | 252 | return -1.0 253 | } 254 | 255 | func main() { 256 | pixelgl.Run(run) 257 | } 258 | -------------------------------------------------------------------------------- /particles/boids/steps/step08.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "math" 9 | "math/rand" 10 | "time" 11 | 12 | "github.com/faiface/pixel" 13 | "github.com/faiface/pixel/pixelgl" 14 | ) 15 | 16 | const ( 17 | w, h = 192 * 3, 108 * 3 18 | fw, fh = float64(w), float64(h) 19 | 20 | globalScale = 1.0 21 | ) 22 | 23 | var ( 24 | maxSpeed = 5.1 * globalScale 25 | friendRadius = 60 * globalScale 26 | //crowdRadius = friendRadius / 1.3 27 | //avoidRadius = 90 * globalScale 28 | coheseRadius = friendRadius 29 | 30 | boids = Boids{} 31 | avoids = Avoids{} 32 | 33 | gray = color.RGBA{55, 55, 55, 255} 34 | ) 35 | 36 | func init() { 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | setup() 40 | } 41 | 42 | func setup() { 43 | for x := 0; x < w+10; x += 10 { 44 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), 10), 0, gray)) 45 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), fh-10), 0, gray)) 46 | } 47 | } 48 | 49 | func run() { 50 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 51 | Bounds: pixel.R(0, 0, fw, fh), 52 | Undecorated: true, 53 | VSync: true, 54 | }) 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | canvas := pixelgl.NewCanvas(win.Bounds()) 60 | 61 | last := time.Now() 62 | 63 | for !win.Closed() { 64 | dt := time.Since(last).Seconds() 65 | last = time.Now() 66 | 67 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 68 | 69 | if win.JustPressed(pixelgl.KeyC) { 70 | boids, avoids = nil, nil 71 | setup() 72 | } 73 | 74 | pos := win.MousePosition() 75 | 76 | if win.JustPressed(pixelgl.KeyO) { 77 | avoids = append(avoids, newAvoid(pos, 10, gray)) 78 | } 79 | 80 | if win.JustPressed(pixelgl.MouseButtonLeft) { 81 | boids = append(boids, randomColorBoidAt(pos, dt)) 82 | } 83 | 84 | win.Clear(color.RGBA{0, 0, 0, 255}) 85 | 86 | drawFrame(canvas, dt) 87 | 88 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 89 | 90 | win.Update() 91 | } 92 | } 93 | 94 | func drawFrame(canvas *pixelgl.Canvas, dt float64) { 95 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 96 | 97 | for _, a := range avoids { 98 | a.draw(buffer) 99 | } 100 | 101 | for _, b := range boids { 102 | b.updateFriends() 103 | b.flock() 104 | b.updatePosition(dt) 105 | b.draw(buffer) 106 | } 107 | 108 | canvas.SetPixels(buffer.Pix) 109 | } 110 | 111 | func randomColor() color.RGBA { 112 | return color.RGBA{ 113 | uint8(rand.Intn(200)), 114 | uint8(rand.Intn(200) + 55), 115 | uint8(rand.Intn(200) + 55), 116 | 255, 117 | } 118 | } 119 | 120 | func randomColorBoidAt(p pixel.Vec, dt float64) *boid { 121 | angle := 90.0 + rand.Float64()*180.0*flip() 122 | speed := 20.0 + dt + (10.0 * rand.Float64()) 123 | 124 | return newBoid(p.X, p.Y, angle, speed, randomColor()) 125 | } 126 | 127 | type boid struct { 128 | position pixel.Vec 129 | velocity pixel.Vec 130 | color color.RGBA 131 | originalColor color.RGBA 132 | friends []*boid 133 | } 134 | 135 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 136 | angleInRadians := angle * math.Pi / 180 137 | 138 | return &boid{ 139 | position: pixel.Vec{x, y}, 140 | velocity: pixel.Vec{ 141 | X: speed * math.Cos(angleInRadians), 142 | Y: -speed * math.Sin(angleInRadians), 143 | }, 144 | color: c, 145 | originalColor: c, 146 | friends: nil, 147 | } 148 | } 149 | 150 | func (b *boid) updatePosition(dt float64) { 151 | b.position.X += b.velocity.X * dt 152 | b.position.Y += b.velocity.Y * dt 153 | 154 | if b.position.X < 0 { 155 | b.position.X = fw 156 | } 157 | 158 | if b.position.X > fw { 159 | b.position.X = 0 160 | } 161 | 162 | if b.position.Y < 0 { 163 | b.position.Y = fh 164 | } 165 | 166 | if b.position.Y > fh { 167 | b.position.Y = 0 168 | } 169 | } 170 | 171 | type Boids []*boid 172 | 173 | func (b *boid) updateFriends() { 174 | var nearby []*boid 175 | 176 | for _, t := range boids { 177 | if t != b { 178 | if math.Abs(t.position.X-b.position.X) < friendRadius && 179 | math.Abs(t.position.Y-b.position.Y) < friendRadius { 180 | nearby = append(nearby, t) 181 | } 182 | } 183 | } 184 | 185 | b.friends = nearby 186 | } 187 | 188 | func (b *boid) getAverageColor() color.RGBA { 189 | if len(b.friends) == 0 { 190 | return b.originalColor 191 | } 192 | 193 | if true { 194 | return color.RGBA{255, 0, 0, 255} 195 | } 196 | 197 | c := len(b.friends) 198 | 199 | tr, tg, tb := 0, 0, 0 200 | br, bg, bb := int(b.originalColor.R), int(b.originalColor.G), int(b.originalColor.B) 201 | 202 | for _, f := range b.friends { 203 | fr, fg, fb := int(f.originalColor.R), int(f.originalColor.G), int(f.originalColor.B) 204 | 205 | if fr-br < -128 { 206 | tr += fr + 255 - br 207 | } else if fr-br > 128 { 208 | tr += fr - 255 - br 209 | } else { 210 | tr += fr - br 211 | } 212 | 213 | if fg-bg < -128 { 214 | tg += fg + 255 - bg 215 | } else if fg-bg > 128 { 216 | tg += fg - 255 - bg 217 | } else { 218 | tg += fg - bg 219 | } 220 | 221 | if fb-bb < -128 { 222 | tb += fb + 255 - bb 223 | } else if fb-bb > 128 { 224 | tb += fb - 255 - bb 225 | } else { 226 | tb += fb - bb 227 | } 228 | } 229 | 230 | return color.RGBA{ 231 | uint8(float64(tr) / float64(c)), 232 | uint8(float64(tg) / float64(c)), 233 | uint8(float64(tb) / float64(c)), 234 | 255, 235 | } 236 | } 237 | 238 | func dist(a, b pixel.Vec) float64 { 239 | return math.Sqrt(a.Sub(b).Dot(a)) 240 | } 241 | 242 | func div(v pixel.Vec, d float64) pixel.Vec { 243 | v.X /= d 244 | v.Y /= d 245 | 246 | return v 247 | } 248 | 249 | func (b *boid) getAverageDir() pixel.Vec { 250 | sum := pixel.V(0, 0) 251 | 252 | for _, f := range b.friends { 253 | d := dist(b.position, f.position) 254 | 255 | fmt.Println(d) 256 | 257 | if d > 0 && d < friendRadius { 258 | 259 | copy := div(f.velocity.Unit(), d) 260 | 261 | sum.Add(copy) 262 | } 263 | } 264 | 265 | return sum 266 | } 267 | 268 | func (b *boid) getCohesion() pixel.Vec { 269 | //neighborDist := 50 270 | 271 | sum := pixel.V(0, 0) 272 | 273 | count := 0 274 | 275 | for _, other := range b.friends { 276 | d := dist(b.position, other.position) 277 | 278 | if d > 0 && d < coheseRadius { 279 | sum.Add(other.position) 280 | count++ 281 | } 282 | } 283 | 284 | if count > 0 { 285 | sum = div(sum, float64(count)) 286 | 287 | desired := sum.Sub(b.position) 288 | 289 | sd := desired.Unit().Scaled(0.05) 290 | 291 | return sd 292 | } 293 | 294 | return pixel.V(0, 0) 295 | } 296 | 297 | func (b *boid) flock() { 298 | align := b.getAverageDir().Scaled(1) 299 | cohesion := b.getCohesion().Scaled(1) 300 | 301 | b.velocity.Add(align) 302 | b.velocity.Add(cohesion) 303 | 304 | b.velocity = b.velocity.Unit().Scaled(maxSpeed * 5) 305 | 306 | b.color = b.getAverageColor() 307 | } 308 | 309 | func (b *boid) draw(m *image.RGBA) { 310 | x, y := int(b.position.X), int(b.position.Y) 311 | 312 | r := image.Rect(x-3, y-3, x+3, y+3) 313 | 314 | draw.Draw(m, r, &image.Uniform{b.color}, image.ZP, draw.Src) 315 | } 316 | 317 | type Avoids []*avoid 318 | 319 | func newAvoid(p pixel.Vec, s float64, c color.RGBA) *avoid { 320 | return &avoid{position: p, size: s, color: c} 321 | } 322 | 323 | type avoid struct { 324 | position pixel.Vec 325 | size float64 326 | color color.RGBA 327 | } 328 | 329 | func (a *avoid) draw(m *image.RGBA) { 330 | x, y := int(a.position.X), int(a.position.Y) 331 | 332 | r := image.Rect(x-2, y-4, x+2, y+4) 333 | 334 | draw.Draw(m, r, &image.Uniform{a.color}, image.ZP, draw.Src) 335 | } 336 | 337 | func flip() float64 { 338 | if rand.Float64() > 0.5 { 339 | return 1.0 340 | } 341 | 342 | return -1.0 343 | } 344 | 345 | func main() { 346 | pixelgl.Run(run) 347 | } 348 | -------------------------------------------------------------------------------- /particles/boids/variations/.gitignore: -------------------------------------------------------------------------------- 1 | boids-bouncing 2 | boids-bouncing-2 3 | boids-crashers 4 | boids-groups 5 | boids-liquid 6 | boids-neighbors 7 | boids-snakes 8 | -------------------------------------------------------------------------------- /particles/boids/variations/boids-neighbors.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 192, 108 17 | fw, fh = float64(w), float64(h) 18 | 19 | globalScale = 0.5 20 | ) 21 | 22 | var ( 23 | maxSpeed = 2.1 * globalScale 24 | friendRadius = 60 * globalScale 25 | 26 | boids = Boids{} 27 | avoids = Avoids{} 28 | 29 | gray = color.RGBA{55, 55, 55, 255} 30 | ) 31 | 32 | func init() { 33 | rand.Seed(time.Now().UnixNano()) 34 | 35 | setup() 36 | } 37 | 38 | func setup() { 39 | for x := 0; x < w+10; x += 10 { 40 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), 10), 0, gray)) 41 | avoids = append(avoids, newAvoid(pixel.V(float64(x+5), fh-10), 0, gray)) 42 | } 43 | } 44 | 45 | func run() { 46 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 47 | Bounds: pixel.R(0, 0, fw, fh), 48 | Undecorated: true, 49 | VSync: true, 50 | }) 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | canvas := pixelgl.NewCanvas(win.Bounds()) 56 | 57 | last := time.Now() 58 | 59 | for !win.Closed() { 60 | dt := time.Since(last).Seconds() 61 | last = time.Now() 62 | 63 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 64 | 65 | if win.JustPressed(pixelgl.KeyC) { 66 | boids, avoids = nil, nil 67 | setup() 68 | } 69 | 70 | pos := win.MousePosition() 71 | 72 | if win.JustPressed(pixelgl.KeyO) { 73 | avoids = append(avoids, newAvoid(pos, 10, gray)) 74 | } 75 | 76 | if win.JustPressed(pixelgl.MouseButtonLeft) { 77 | boids = append(boids, randomColorBoidAt(pos, dt)) 78 | } 79 | 80 | win.Clear(color.RGBA{0, 0, 0, 255}) 81 | 82 | drawFrame(canvas, dt) 83 | 84 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 85 | 86 | win.Update() 87 | } 88 | } 89 | 90 | func drawFrame(canvas *pixelgl.Canvas, dt float64) { 91 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 92 | 93 | for _, a := range avoids { 94 | a.draw(buffer) 95 | } 96 | 97 | for _, b := range boids { 98 | b.move(dt) 99 | b.draw(buffer) 100 | } 101 | 102 | canvas.SetPixels(buffer.Pix) 103 | } 104 | 105 | func randomColorBoidAt(p pixel.Vec, dt float64) *boid { 106 | angle := 90.0 + rand.Float64()*180.0*flip() 107 | speed := 20.0 + dt + (10.0 * rand.Float64()) 108 | 109 | return newBoid(p.X, p.Y, angle, speed, 110 | color.RGBA{ 111 | uint8(rand.Intn(200)), 112 | uint8(rand.Intn(200) + 55), 113 | uint8(rand.Intn(200) + 55), 114 | 255, 115 | }, 116 | ) 117 | } 118 | 119 | func main() { 120 | pixelgl.Run(run) 121 | } 122 | 123 | type Avoids []*avoid 124 | 125 | func newAvoid(p pixel.Vec, s float64, c color.RGBA) *avoid { 126 | return &avoid{position: p, size: s, color: c} 127 | } 128 | 129 | type avoid struct { 130 | position pixel.Vec 131 | size float64 132 | color color.RGBA 133 | } 134 | 135 | func (a *avoid) draw(m *image.RGBA) { 136 | x, y := int(a.position.X), int(a.position.Y) 137 | 138 | r := image.Rect(x-2, y-4, x+2, y+4) 139 | 140 | draw.Draw(m, r, &image.Uniform{a.color}, image.ZP, draw.Src) 141 | } 142 | 143 | type boid struct { 144 | angle float64 145 | speed float64 146 | position pixel.Vec 147 | color color.RGBA 148 | originalColor color.RGBA 149 | friends []*boid 150 | } 151 | 152 | func newBoid(x, y, angle, speed float64, c color.RGBA) *boid { 153 | return &boid{ 154 | angle: angle, 155 | speed: speed, 156 | position: pixel.Vec{x, y}, 157 | color: c, 158 | originalColor: c, 159 | friends: nil, 160 | } 161 | } 162 | 163 | func (b *boid) velocity() pixel.Vec { 164 | angleInRadians := b.angle * math.Pi / 180 165 | 166 | return pixel.Vec{ 167 | X: b.speed * math.Cos(angleInRadians), 168 | Y: -b.speed * math.Sin(angleInRadians), 169 | } 170 | } 171 | 172 | func (b *boid) move(dt float64) { 173 | b.updatePosition(dt) 174 | b.updateFriends() 175 | } 176 | 177 | func (b *boid) updatePosition(dt float64) { 178 | v := b.velocity() 179 | 180 | b.position.X += v.X * dt 181 | b.position.Y += v.Y * dt 182 | 183 | if b.position.X < 0 { 184 | b.position.X = fw 185 | } 186 | 187 | if b.position.X > fw { 188 | b.position.X = 0 189 | } 190 | 191 | if b.position.Y < 0 { 192 | b.position.Y = fh 193 | } 194 | 195 | if b.position.Y > fh { 196 | b.position.Y = 0 197 | } 198 | } 199 | 200 | type Boids []*boid 201 | 202 | func (b *boid) updateFriends() { 203 | var nearby []*boid 204 | 205 | for _, t := range boids { 206 | if t != b { 207 | if math.Abs(t.position.X-b.position.X) < friendRadius && 208 | math.Abs(t.position.Y-b.position.Y) < friendRadius { 209 | 210 | // change speed 211 | //if t.speed > 0 { t.speed -= 0.1 } 212 | 213 | // flip angle 214 | //t.angle = -t.angle 215 | 216 | nearby = append(nearby, t) 217 | } 218 | } 219 | } 220 | 221 | if len(nearby) > 0 { 222 | for _, n := range nearby { 223 | n.color = color.RGBA{255, 0, 0, 255} 224 | } 225 | } else { 226 | b.color = b.originalColor 227 | } 228 | 229 | b.friends = nearby 230 | } 231 | 232 | func (b *boid) draw(m *image.RGBA) { 233 | x, y := int(b.position.X), int(b.position.Y) 234 | 235 | r := image.Rect(x-3, y-3, x+3, y+3) 236 | 237 | draw.Draw(m, r, &image.Uniform{b.color}, image.ZP, draw.Src) 238 | } 239 | 240 | func flip() float64 { 241 | if rand.Float64() > 0.5 { 242 | return 1.0 243 | } 244 | 245 | return -1.0 246 | } 247 | -------------------------------------------------------------------------------- /particles/falling-red.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 512, 512 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, fw, fh), 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | canvas := pixelgl.NewCanvas(win.Bounds()) 32 | 33 | particles := []*particle{} 34 | 35 | last := time.Now() 36 | 37 | var total float64 38 | 39 | angle := 90.0 40 | 41 | white := color.RGBA{255, 255, 255, 255} 42 | 43 | fall := func(fx, fy float64) { 44 | speed := 25.0 + (25.0 * rand.Float64()) 45 | life := 10.1 + (1.3 * rand.Float64()) 46 | 47 | particles = append(particles, 48 | newParticle(fx, fy, angle, speed, life, white), 49 | ) 50 | } 51 | 52 | go func() { 53 | for { 54 | time.Sleep(100 * time.Millisecond) 55 | fall(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2)) 56 | } 57 | }() 58 | 59 | for !win.Closed() { 60 | dt := time.Since(last).Seconds() 61 | last = time.Now() 62 | 63 | total += dt 64 | 65 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 66 | 67 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 68 | 69 | if win.JustPressed(pixelgl.KeyC) { 70 | particles = nil 71 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 72 | } 73 | 74 | if win.Pressed(pixelgl.MouseButtonLeft) { 75 | fall(win.MousePosition().XY()) 76 | } 77 | 78 | if win.JustPressed(pixelgl.KeyS) { 79 | fall(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2)) 80 | } 81 | 82 | for _, p := range particles { 83 | p.update(dt) 84 | 85 | x := int(p.position.X) 86 | y := int(p.position.Y) 87 | 88 | buffer.Set(x, y, p.color) 89 | buffer.Set(x-1, y, p.color) 90 | buffer.Set(x+1, y, p.color) 91 | buffer.Set(x, y-1, p.color) 92 | buffer.Set(x, y+1, p.color) 93 | } 94 | 95 | aliveParticles := []*particle{} 96 | 97 | for _, p := range particles { 98 | if p.life > 0 { 99 | aliveParticles = append(aliveParticles, p) 100 | } 101 | } 102 | 103 | particles = aliveParticles 104 | 105 | win.Clear(color.RGBA{0, 0, 0, 255}) 106 | 107 | canvas.SetPixels(buffer.Pix) 108 | 109 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 110 | 111 | win.Update() 112 | } 113 | } 114 | 115 | func main() { 116 | pixelgl.Run(run) 117 | } 118 | 119 | type particle struct { 120 | angle float64 121 | speed float64 122 | position pixel.Vec 123 | velocity pixel.Vec 124 | color color.RGBA 125 | life float64 126 | } 127 | 128 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 129 | angleInRadians := angle * math.Pi / 180 130 | 131 | return &particle{ 132 | angle: angle, 133 | speed: speed, 134 | position: pixel.Vec{x, y}, 135 | velocity: pixel.Vec{ 136 | X: speed * math.Cos(angleInRadians), 137 | Y: -speed * math.Sin(angleInRadians), 138 | }, 139 | life: life, 140 | color: c, 141 | } 142 | } 143 | 144 | func (p *particle) update(dt float64) { 145 | p.life -= dt 146 | 147 | if p.life > 0 { 148 | p.position.X += p.velocity.X * dt 149 | p.position.Y += p.velocity.Y * dt 150 | 151 | if p.life > 1.0 { 152 | if rand.Float64() < 0.6 { 153 | p.color.G -= 1 154 | p.color.B -= 1 155 | } 156 | } else { 157 | p.color.R -= 2 158 | p.color.G -= 1 159 | p.color.B -= 1 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /particles/fireworks.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 512, 512 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func flip() float64 { 21 | if rand.Float64() > 0.5 { 22 | return 1.0 23 | } 24 | 25 | return -1.0 26 | } 27 | 28 | func run() { 29 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 30 | Bounds: pixel.R(0, 0, fw, fh), 31 | Undecorated: true, 32 | }) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | canvas := pixelgl.NewCanvas(win.Bounds()) 40 | 41 | particles := []*particle{} 42 | 43 | last := time.Now() 44 | 45 | var total float64 46 | 47 | explode := func(fx, fy float64) { 48 | if len(particles) > 500 { 49 | return 50 | } 51 | 52 | x := int(fx) 53 | y := int(fy) 54 | 55 | n := 30 + (6 * rand.Intn(10)) 56 | 57 | c := color.RGBA{255, uint8(int(total) + y%255), uint8((int(total) ^ x) % 255), 0} 58 | 59 | for i := 0; i < n; i++ { 60 | angle := 90.0 + rand.Float64()*180.0*flip() 61 | speed := (2.0 * float64(n)) + (float64(i) * rand.Float64()) 62 | life := 0.1 + (1.3 * rand.Float64()) 63 | 64 | particles = append(particles, 65 | newParticle(fx, fy, angle, speed, life, c), 66 | ) 67 | } 68 | } 69 | 70 | go func() { 71 | for { 72 | time.Sleep(2700 * time.Millisecond) 73 | explode(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2)) 74 | } 75 | }() 76 | 77 | for !win.Closed() { 78 | dt := time.Since(last).Seconds() 79 | last = time.Now() 80 | 81 | total += dt 82 | 83 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 84 | 85 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 86 | 87 | if win.JustPressed(pixelgl.KeyC) { 88 | particles = nil 89 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 90 | } 91 | 92 | if win.Pressed(pixelgl.MouseButtonLeft) { 93 | explode(win.MousePosition().XY()) 94 | } 95 | 96 | if win.JustPressed(pixelgl.KeyS) { 97 | explode(rand.Float64()*fw, (rand.Float64()*fh)/2+(fh/2)) 98 | } 99 | 100 | for _, p := range particles { 101 | p.update(dt) 102 | 103 | x := int(p.position.X) 104 | y := int(p.position.Y) 105 | 106 | buffer.Set(x, y, p.color) 107 | buffer.Set(x-1, y, p.color) 108 | buffer.Set(x+1, y, p.color) 109 | buffer.Set(x, y-1, p.color) 110 | buffer.Set(x, y+1, p.color) 111 | } 112 | 113 | aliveParticles := []*particle{} 114 | 115 | for _, p := range particles { 116 | if p.life > 0 { 117 | aliveParticles = append(aliveParticles, p) 118 | } 119 | } 120 | 121 | particles = aliveParticles 122 | 123 | win.Clear(color.RGBA{0, 0, 0, 255}) 124 | 125 | canvas.SetPixels(buffer.Pix) 126 | 127 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 128 | 129 | win.Update() 130 | } 131 | } 132 | 133 | func main() { 134 | pixelgl.Run(run) 135 | } 136 | 137 | type particle struct { 138 | angle float64 139 | speed float64 140 | position pixel.Vec 141 | velocity pixel.Vec 142 | color color.RGBA 143 | life float64 144 | } 145 | 146 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 147 | angleInRadians := angle * math.Pi / 180 148 | 149 | return &particle{ 150 | angle: angle, 151 | speed: speed, 152 | position: pixel.Vec{x, y}, 153 | velocity: pixel.Vec{ 154 | X: speed * math.Cos(angleInRadians), 155 | Y: -speed * math.Sin(angleInRadians), 156 | }, 157 | life: life, 158 | color: c, 159 | } 160 | } 161 | 162 | func (p *particle) update(dt float64) { 163 | p.life -= dt 164 | 165 | if p.life > 0 { 166 | p.position.X += p.velocity.X * dt 167 | p.position.Y += p.velocity.Y * dt 168 | 169 | if p.life > 1.0 { 170 | if rand.Float64() < 0.6 { 171 | p.color.G -= 1 172 | p.color.B -= 1 173 | } 174 | } else { 175 | p.color.R -= 2 176 | p.color.G -= 1 177 | p.color.B -= 1 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /particles/particles.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 512, 512 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func flip() float64 { 21 | if rand.Float64() > 0.5 { 22 | return 1.0 23 | } 24 | 25 | return -1.0 26 | } 27 | 28 | func run() { 29 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 30 | Bounds: pixel.R(0, 0, fw, fh), 31 | Undecorated: true, 32 | }) 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | rand.Seed(time.Now().UnixNano()) 38 | 39 | canvas := pixelgl.NewCanvas(win.Bounds()) 40 | 41 | particles := []*particle{} 42 | 43 | last := time.Now() 44 | 45 | for !win.Closed() { 46 | dt := time.Since(last).Seconds() 47 | last = time.Now() 48 | 49 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 50 | 51 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 52 | 53 | if win.JustPressed(pixelgl.KeyC) { 54 | particles = nil 55 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 56 | } 57 | 58 | fx, fy := win.MousePosition().XY() 59 | x := int(fx) 60 | y := int(fy) 61 | 62 | if win.Pressed(pixelgl.MouseButtonLeft) { 63 | c := color.RGBA{255, uint8(y % 255), uint8(x % 255), 255} 64 | 65 | for i := 0; i < 10; i++ { 66 | angle := 90.0 + rand.Float64()*180.0*flip() 67 | speed := 40.0 + dt + (20.0 * rand.Float64()) 68 | life := 0.2 + (2.0 * rand.Float64()) 69 | 70 | particles = append(particles, 71 | newParticle(fx, fy, angle, speed, life, c), 72 | ) 73 | } 74 | } 75 | 76 | for _, p := range particles { 77 | p.update(dt) 78 | 79 | x := int(p.position.X) 80 | y := int(p.position.Y) 81 | 82 | buffer.Set(x, y, p.color) 83 | buffer.Set(x-1, y, p.color) 84 | buffer.Set(x+1, y, p.color) 85 | buffer.Set(x, y-1, p.color) 86 | buffer.Set(x, y+1, p.color) 87 | } 88 | 89 | aliveParticles := []*particle{} 90 | 91 | for _, p := range particles { 92 | if p.life > 0 { 93 | aliveParticles = append(aliveParticles, p) 94 | } 95 | } 96 | 97 | particles = aliveParticles 98 | 99 | win.Clear(color.RGBA{0, 0, 0, 255}) 100 | 101 | canvas.SetPixels(buffer.Pix) 102 | 103 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 104 | 105 | win.Update() 106 | } 107 | } 108 | 109 | func main() { 110 | pixelgl.Run(run) 111 | } 112 | 113 | type particle struct { 114 | position pixel.Vec 115 | velocity pixel.Vec 116 | color color.RGBA 117 | life float64 118 | } 119 | 120 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 121 | angleInRadians := angle * math.Pi / 180 122 | 123 | return &particle{ 124 | position: pixel.Vec{x, y}, 125 | velocity: pixel.Vec{ 126 | X: speed * math.Cos(angleInRadians), 127 | Y: -speed * math.Sin(angleInRadians), 128 | }, 129 | life: life, 130 | color: c, 131 | } 132 | } 133 | 134 | func (p *particle) update(dt float64) { 135 | p.life -= dt 136 | 137 | if p.life > 0 { 138 | p.position.X += p.velocity.X * dt 139 | p.position.Y += p.velocity.Y * dt 140 | 141 | if p.life > 1.5 { 142 | if rand.Float64() < 0.4+dt { 143 | p.color.R -= 1 144 | p.color.G -= 1 145 | p.color.B -= 1 146 | } 147 | } else { 148 | p.color.R -= 1 149 | p.color.G -= 1 150 | p.color.B -= 1 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /particles/small-particles.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 768, 432 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, fw, fh), 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | canvas := pixelgl.NewCanvas(win.Bounds()) 32 | 33 | particles := []*particle{} 34 | 35 | last := time.Now() 36 | 37 | for !win.Closed() { 38 | dt := time.Since(last).Seconds() 39 | last = time.Now() 40 | 41 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 42 | 43 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 44 | 45 | if win.JustPressed(pixelgl.KeyC) { 46 | particles = nil 47 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 48 | } 49 | 50 | fx, fy := win.MousePosition().XY() 51 | x := int(fx) 52 | y := int(fy) 53 | 54 | if win.Pressed(pixelgl.MouseButtonLeft) { 55 | c := color.RGBA{255, uint8(y % 255), uint8(x % 255), 255} 56 | 57 | for i := 0; i < 10; i++ { 58 | angle := 90.0 + rand.Float64()*180.0 59 | speed := 40.0 + dt + (20.0 * rand.Float64()) 60 | life := 0.2 + (2.0 * rand.Float64()) 61 | 62 | particles = append(particles, 63 | newParticle(fx, fy, angle, speed, life, c), 64 | ) 65 | } 66 | } 67 | 68 | for _, p := range particles { 69 | p.update(dt) 70 | 71 | buffer.Set(int(p.position.X), int(p.position.Y), p.color) 72 | } 73 | 74 | aliveParticles := []*particle{} 75 | 76 | for _, p := range particles { 77 | if p.life > 0 { 78 | aliveParticles = append(aliveParticles, p) 79 | } 80 | } 81 | 82 | particles = aliveParticles 83 | 84 | win.Clear(color.RGBA{0, 0, 0, 255}) 85 | 86 | canvas.SetPixels(buffer.Pix) 87 | 88 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 89 | 90 | win.Update() 91 | } 92 | } 93 | 94 | func main() { 95 | pixelgl.Run(run) 96 | } 97 | 98 | type particle struct { 99 | position pixel.Vec 100 | velocity pixel.Vec 101 | color color.RGBA 102 | life float64 103 | } 104 | 105 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 106 | angleInRadians := angle * math.Pi / 180 107 | 108 | return &particle{ 109 | position: pixel.Vec{x, y}, 110 | velocity: pixel.Vec{ 111 | X: speed * math.Cos(angleInRadians), 112 | Y: -speed * math.Sin(angleInRadians), 113 | }, 114 | life: life, 115 | color: c, 116 | } 117 | } 118 | 119 | func (p *particle) update(dt float64) { 120 | p.life -= dt 121 | 122 | if p.life > 0 { 123 | p.position.X += p.velocity.X * dt 124 | p.position.Y += p.velocity.Y * dt 125 | 126 | if p.life > 1.5 { 127 | if rand.Float64() < 0.4+dt { 128 | p.color.R -= 1 129 | p.color.G -= 1 130 | p.color.B -= 1 131 | } 132 | } else { 133 | p.color.R -= 1 134 | p.color.G -= 1 135 | p.color.B -= 1 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /particles/snow.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/draw" 7 | "math" 8 | "math/rand" 9 | "time" 10 | 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | ) 14 | 15 | const ( 16 | w, h = 512, 256 17 | fw, fh = float64(w), float64(h) 18 | ) 19 | 20 | func run() { 21 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 22 | Bounds: pixel.R(0, 0, fw, fh), 23 | Undecorated: true, 24 | }) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | rand.Seed(time.Now().UnixNano()) 30 | 31 | canvas := pixelgl.NewCanvas(win.Bounds()) 32 | 33 | particles := []*particle{} 34 | 35 | last := time.Now() 36 | 37 | var total float64 38 | 39 | angle := 90.0 40 | 41 | white := color.RGBA{255, 255, 255, 255} 42 | 43 | fall := func(fx, fy float64) { 44 | speed := 32.0 + (32.0 * rand.Float64()) 45 | life := 16.0 + (1.6 * rand.Float64()) 46 | 47 | particles = append(particles, 48 | newParticle(fx, fy, angle, speed, life, white), 49 | ) 50 | } 51 | 52 | go func() { 53 | for { 54 | time.Sleep(40 * time.Millisecond) 55 | fall(rand.Float64()*fw, fh-(rand.Float64()*fh)/2+(fh/2)) 56 | } 57 | }() 58 | 59 | for !win.Closed() { 60 | dt := time.Since(last).Seconds() 61 | last = time.Now() 62 | 63 | total += dt 64 | 65 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 66 | 67 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 68 | 69 | if win.JustPressed(pixelgl.KeyC) { 70 | particles = nil 71 | draw.Draw(buffer, buffer.Bounds(), image.Transparent, image.ZP, draw.Src) 72 | } 73 | 74 | if win.Pressed(pixelgl.KeyS) { 75 | fall(rand.Float64()*fw, fh-(rand.Float64()*fh)/2+(fh/2)) 76 | } 77 | 78 | if win.JustPressed(pixelgl.KeyLeft) { 79 | angle += 4 80 | } 81 | 82 | if win.JustPressed(pixelgl.KeyRight) { 83 | angle -= 4 84 | } 85 | 86 | for _, p := range particles { 87 | p.update(dt) 88 | 89 | x := int(p.position.X) 90 | y := int(p.position.Y) 91 | 92 | buffer.Set(x, y, p.color) 93 | buffer.Set(x-1, y, p.color) 94 | buffer.Set(x+1, y, p.color) 95 | buffer.Set(x, y-1, p.color) 96 | buffer.Set(x, y+1, p.color) 97 | } 98 | 99 | aliveParticles := []*particle{} 100 | 101 | for _, p := range particles { 102 | if p.life > 0 { 103 | aliveParticles = append(aliveParticles, p) 104 | } 105 | } 106 | 107 | particles = aliveParticles 108 | 109 | win.Clear(color.RGBA{0, 0, 0, 255}) 110 | 111 | canvas.SetPixels(buffer.Pix) 112 | 113 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 114 | 115 | win.Update() 116 | } 117 | } 118 | 119 | func main() { 120 | pixelgl.Run(run) 121 | } 122 | 123 | type particle struct { 124 | position pixel.Vec 125 | velocity pixel.Vec 126 | color color.RGBA 127 | life float64 128 | } 129 | 130 | func newParticle(x, y, angle, speed, life float64, c color.RGBA) *particle { 131 | angleInRadians := angle * math.Pi / 180 132 | 133 | return &particle{ 134 | position: pixel.Vec{x, y}, 135 | velocity: pixel.Vec{ 136 | X: speed * math.Cos(angleInRadians), 137 | Y: -speed * math.Sin(angleInRadians), 138 | }, 139 | life: life, 140 | color: c, 141 | } 142 | } 143 | 144 | func (p *particle) update(dt float64) { 145 | p.life -= dt * 3 146 | 147 | if p.life > 0 { 148 | p.position.X += p.velocity.X * dt 149 | p.position.Y += p.velocity.Y * dt 150 | 151 | if p.life < 10 && p.color.R > 0 { 152 | if rand.Float64() < 0.3 { 153 | p.color.R -= 1 154 | p.color.G -= 1 155 | p.color.B -= 1 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /plasma.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "time" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/pixelgl" 9 | 10 | "github.com/peterhellberg/plasma" 11 | "github.com/peterhellberg/plasma/palette" 12 | ) 13 | 14 | const ( 15 | width = 768 16 | height = 768 17 | size = 256 18 | ) 19 | 20 | func run() { 21 | scale := float64(height) / float64(size) 22 | 23 | cfg := pixelgl.WindowConfig{ 24 | Bounds: pixel.R(0, 0, 1024, 512), 25 | VSync: true, 26 | Resizable: false, 27 | Undecorated: true, 28 | } 29 | 30 | win, err := pixelgl.NewWindow(cfg) 31 | if err != nil { 32 | panic(err) 33 | } 34 | 35 | win.SetSmooth(false) 36 | 37 | var s *pixel.Sprite 38 | 39 | size := 12.0 40 | 41 | p := plasmaPicture(256, 128, size, 0) 42 | s = pixel.NewSprite(p, p.Bounds()) 43 | 44 | go func() { 45 | c := time.Tick(32 * time.Millisecond) 46 | 47 | var i int 48 | 49 | for range c { 50 | i++ 51 | 52 | p := plasmaPicture(256, 128, size, i) 53 | 54 | s.Set(p, p.Bounds()) 55 | } 56 | }() 57 | 58 | win.Clear(color.Black) 59 | 60 | c := win.Bounds().Center() 61 | 62 | for !win.Closed() { 63 | win.Update() 64 | 65 | s.Draw(win, pixel.IM.Moved(c).Scaled(c, scale)) 66 | 67 | if win.Pressed(pixelgl.KeyUp) { 68 | size += 0.2 69 | } 70 | 71 | if win.Pressed(pixelgl.KeyDown) { 72 | size -= 0.2 73 | } 74 | 75 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 76 | return 77 | } 78 | } 79 | } 80 | 81 | func plasmaPicture(w, h int, s float64, i int) *pixel.PictureData { 82 | return pixel.PictureDataFromImage(plasma.New(w, h, s). 83 | Image(w, h, i, palette.DefaultGradient)) 84 | } 85 | 86 | func main() { 87 | pixelgl.Run(run) 88 | } 89 | -------------------------------------------------------------------------------- /pong/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/pong 2 | 3 | https://gist.github.com/peterhellberg/6b7915e9c51c44b8d7e21112cbdf8d0f 4 | 5 | ![](https://user-images.githubusercontent.com/565124/35081700-f416c566-fc15-11e7-83d9-1fe349121994.png) 6 | -------------------------------------------------------------------------------- /pong/pong-with-sound/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/pong/pong-with-sound 2 | 3 | http://cs.au.dk/~dsound/DigitalAudio.dir/Greenfoot/Pong.dir/Pong.html 4 | 5 | ## Cross compile from macOS to Windows 6 | 7 | `CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 CGO_ENABLED=1 go build pong-with-sound.go` 8 | 9 | ![](https://user-images.githubusercontent.com/565124/35081700-f416c566-fc15-11e7-83d9-1fe349121994.png) 10 | -------------------------------------------------------------------------------- /pong/pong-with-sound/sound/.gitignore: -------------------------------------------------------------------------------- 1 | *.mp3 2 | *.go 3 | -------------------------------------------------------------------------------- /pong/pong-with-sound/sound/beeep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/pong/pong-with-sound/sound/beeep.wav -------------------------------------------------------------------------------- /pong/pong-with-sound/sound/convert.sh: -------------------------------------------------------------------------------- 1 | go get gopkg.in/slimsag/file2go.v1 2 | 3 | lame -b 32 --resample 32 -a beeep.wav beeep.mp3 4 | file2go.v1 -f -i beeep.mp3 -o beeep.go -package main -var beeep 5 | 6 | lame -b 32 --resample 32 -a peeeeeep.wav peeeeeep.mp3 7 | file2go.v1 -f -i peeeeeep.mp3 -o peeeeeep.go -package main -var peeeeeep 8 | 9 | lame -b 32 --resample 32 -a plop.wav plop.mp3 10 | file2go.v1 -f -i plop.mp3 -o plop.go -package main -var plop 11 | -------------------------------------------------------------------------------- /pong/pong-with-sound/sound/peeeeeep.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/pong/pong-with-sound/sound/peeeeeep.wav -------------------------------------------------------------------------------- /pong/pong-with-sound/sound/plop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/pong/pong-with-sound/sound/plop.wav -------------------------------------------------------------------------------- /pong/pong.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image/color" 6 | "math/rand" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/imdraw" 10 | "github.com/faiface/pixel/pixelgl" 11 | "github.com/faiface/pixel/text" 12 | ) 13 | 14 | func main() { 15 | pixelgl.Run(func() { 16 | p := newPong(pixel.R(0, 0, 858, 525)) 17 | 18 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 19 | Bounds: p.Rect, 20 | Undecorated: true, 21 | VSync: true, 22 | }) 23 | 24 | if err == nil { 25 | win.SetSmooth(false) 26 | 27 | for !win.Closed() { 28 | p.input(win) 29 | p.draw(win) 30 | p.update(win) 31 | } 32 | } 33 | }) 34 | } 35 | 36 | type pong struct { 37 | pixel.Rect 38 | ball pixel.Vec 39 | velocity pixel.Vec 40 | left *player 41 | right *player 42 | } 43 | 44 | func newPong(r pixel.Rect) *pong { 45 | return &pong{ 46 | r, r.Center(), pixel.V(5, (rand.Float64()*2)-1*3), 47 | &player{ 48 | pos: pixel.V(16, r.Max.Y/2), 49 | txt: text.New(pixel.V(r.Max.X/2-320, r.Max.Y-112), text.Atlas7x13), 50 | }, 51 | &player{ 52 | pos: pixel.V(r.Max.X-16, r.Max.Y/2), 53 | txt: text.New(pixel.V(r.Max.X/2+128, r.Max.Y-112), text.Atlas7x13), 54 | }, 55 | } 56 | } 57 | 58 | func (p *pong) input(win *pixelgl.Window) { 59 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 60 | 61 | if win.Pressed(pixelgl.KeyW) && p.left.pos.Y < p.Max.Y-50 { 62 | p.left.pos.Y += 10 63 | } 64 | if win.Pressed(pixelgl.KeyS) && p.left.pos.Y > 50 { 65 | p.left.pos.Y -= 10 66 | } 67 | if win.Pressed(pixelgl.KeyUp) && p.right.pos.Y < p.Max.Y-50 { 68 | p.right.pos.Y += 10 69 | } 70 | if win.Pressed(pixelgl.KeyDown) && p.right.pos.Y > 50 { 71 | p.right.pos.Y -= 10 72 | } 73 | if win.Pressed(pixelgl.KeyR) { 74 | p.ball, p.left.score, p.right.score = p.Center(), 0, 0 75 | } 76 | } 77 | 78 | func (p *pong) draw(win *pixelgl.Window) { 79 | win.Clear(color.NRGBA{16, 16, 16, 15}) 80 | 81 | imd := imdraw.New(nil) 82 | 83 | imd.Color = color.NRGBA{18, 18, 18, 255} 84 | imd.Push(pixel.V(p.Max.X/2, 0), pixel.V(p.Max.X/2, p.Max.Y)) 85 | imd.Line(16) 86 | 87 | imd.Color = color.White 88 | imd.Push(p.ball.Add(pixel.V(-8, -8)), p.ball.Add(pixel.V(8, 8))) 89 | imd.Rectangle(0) 90 | 91 | p.right.draw(imd, win) 92 | p.left.draw(imd, win) 93 | 94 | imd.Draw(win) 95 | } 96 | 97 | func (p *pong) update(win *pixelgl.Window) { 98 | switch { 99 | case p.left.Contains(p.ball): 100 | p.velocity.X = -p.velocity.X 101 | p.ball.X = 24 102 | case p.right.Contains(p.ball): 103 | p.velocity.X = -p.velocity.X 104 | p.ball.X = p.Max.X - 24 105 | case p.ball.Y < 16, p.ball.Y > p.Max.Y-16: 106 | p.velocity.Y = -p.velocity.Y 107 | case p.ball.X < 8 || p.ball.X > p.Max.X+8: 108 | if p.ball.X < 8 { 109 | p.right.score++ 110 | } else { 111 | p.left.score++ 112 | } 113 | 114 | p.velocity = pixel.V(-8, (rand.Float64()-0.5)*16) 115 | 116 | if rand.Float64() > 0.5 { 117 | p.velocity.X = 8 118 | } 119 | 120 | p.ball = p.Center() 121 | } 122 | 123 | p.ball = p.ball.Add(p.velocity) 124 | 125 | p.left.update() 126 | p.right.update() 127 | 128 | win.Update() 129 | } 130 | 131 | type player struct { 132 | pixel.Rect 133 | pos pixel.Vec 134 | txt *text.Text 135 | score int 136 | } 137 | 138 | func (p *player) draw(imd *imdraw.IMDraw, t pixel.Target) { 139 | imd.Color = color.White 140 | imd.Push(p.Min, p.Max) 141 | imd.Rectangle(0) 142 | p.txt.Draw(t, pixel.IM.Scaled(p.txt.Orig, 8)) 143 | } 144 | 145 | func (p *player) update() { 146 | p.txt.Clear() 147 | fmt.Fprintf(p.txt, "% 3d", p.score) 148 | p.Rect = pixel.Rect{p.pos.Add(pixel.V(-8, -50)), p.pos.Add(pixel.V(8, 50))} 149 | } 150 | -------------------------------------------------------------------------------- /popcorn/popcorn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "math" 9 | 10 | "github.com/faiface/pixel" 11 | "github.com/faiface/pixel/pixelgl" 12 | ) 13 | 14 | var ( 15 | width int 16 | height int 17 | number int 18 | density int 19 | scale float64 20 | hconst float64 21 | ) 22 | 23 | func init() { 24 | flag.IntVar(&width, "w", 250, "Image width") 25 | flag.IntVar(&height, "h", 250, "Image width") 26 | flag.IntVar(&number, "n", 20, "Number of points to draw") 27 | flag.IntVar(&density, "m", 3, "Density of sampling the image as initial conditions") 28 | flag.Float64Var(&scale, "s", 0.5, "Width of image in world coordinates") 29 | flag.Float64Var(&hconst, "hc", 0.05, "hconst") 30 | } 31 | 32 | func renderImage() *image.NRGBA { 33 | fwidth := float64(width) 34 | fheight := float64(height) 35 | 36 | img := image.NewNRGBA(image.Rect(0, 0, width, height)) 37 | 38 | draw.Draw(img, img.Bounds(), &image.Uniform{color.Black}, image.ZP, draw.Src) 39 | 40 | for i := 0; i < width; i += density { 41 | for j := 0; j < height; j += density { 42 | fi := float64(i) 43 | fj := float64(j) 44 | 45 | // Seed pixel, mapping from pixels to world coordinates 46 | x := 2.0 * scale * (fi - fwidth/2.1) / fwidth 47 | y := 2.0 * scale * (fj - fheight/2.1) / fheight 48 | 49 | // Iterate for number of points 50 | for n := 0; n < number; n++ { 51 | // Calculate next point in the series 52 | xnew := x - hconst*math.Sin(y+math.Tan(2.2*y)) 53 | ynew := y - hconst*math.Sin(x+math.Tan(1.1*x)) 54 | 55 | c := getColor(n, number) 56 | bc := color.NRGBA{0, 0, 0, 255} 57 | 58 | bc.R = c.R * 255 59 | bc.G = c.G * 255 60 | bc.B = c.B * 255 61 | 62 | // Mapping from world coordinates to image pixel cordinates 63 | ix := int(0.5*xnew*fwidth/scale + fwidth/2.2) 64 | iy := int(0.5*ynew*fheight/scale + fheight/5.5) 65 | 66 | // Draw the pixel if it is in bounds 67 | if ix >= 0 && iy >= 0 && ix < width && iy < height { 68 | img.Set(ix, iy, bc) 69 | } 70 | 71 | x = xnew 72 | y = ynew 73 | } 74 | } 75 | } 76 | 77 | return img 78 | } 79 | 80 | func run() { 81 | img := renderImage() 82 | 83 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 84 | Bounds: pixel.R(0, 0, float64(width), float64(height)), 85 | VSync: true, 86 | Undecorated: true, 87 | }) 88 | if err != nil { 89 | panic(err) 90 | } 91 | 92 | canvas := pixelgl.NewCanvas(win.Bounds()) 93 | 94 | for !win.Closed() { 95 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 96 | 97 | if win.Pressed(pixelgl.KeyUp) { 98 | hconst += 0.01 99 | img = renderImage() 100 | } 101 | 102 | if win.Pressed(pixelgl.KeyDown) { 103 | hconst -= 0.01 104 | img = renderImage() 105 | } 106 | 107 | if win.JustPressed(pixelgl.KeyLeft) && density > 1 { 108 | density += -1 109 | img = renderImage() 110 | } 111 | 112 | if win.JustPressed(pixelgl.KeyRight) && density < 5 { 113 | density += 1 114 | img = renderImage() 115 | } 116 | 117 | canvas.SetPixels(img.Pix) 118 | canvas.Draw(win, pixel.IM.Moved(win.Bounds().Center())) 119 | 120 | win.Update() 121 | } 122 | } 123 | 124 | func main() { 125 | flag.Parse() 126 | 127 | pixelgl.Run(run) 128 | } 129 | 130 | func getColor(n, number int) color.NRGBA { 131 | k := uint8(number / (n + 1)) 132 | 133 | return color.NRGBA{k / uint8(5), k / uint8(3), k / uint8(n+1), 255} 134 | } 135 | -------------------------------------------------------------------------------- /raycaster/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/raycaster 2 | 3 | https://gist.github.com/peterhellberg/835eccabf95800555120cc8f0c9e16c2 4 | 5 | ## Textured walls 6 | 7 | ![textured](https://user-images.githubusercontent.com/565124/31828029-798e6620-b5b9-11e7-96b7-fda540755745.gif) 8 | 9 | ![gopher](https://user-images.githubusercontent.com/565124/34686123-ed3a9d02-f4aa-11e7-864d-1942eefd18a4.png) 10 | ![corridor](https://user-images.githubusercontent.com/565124/31827817-d0f6f9f0-b5b8-11e7-9f1a-0fda948369db.png) 11 | 12 | ## Textured floor 13 | 14 | ![](https://camo.githubusercontent.com/a6cee30a3c869d4214cf296648bbfc6b5d61d64e/68747470733a2f2f6173736574732e63372e73652f73637265656e73686f74732f7261796361737465722d74657874757265642d32303137313031392d3231323233312e706e67) 15 | 16 | ## Untextured 17 | 18 | ![raycaster-correct-plane](https://user-images.githubusercontent.com/565124/31767008-dcb79532-b4c9-11e7-89dd-7dbb7efddae7.gif) 19 | 20 | ![](https://user-images.githubusercontent.com/565124/31748193-a3f5a61a-b471-11e7-8840-e49b1d9e475d.png) 21 | ![](https://user-images.githubusercontent.com/565124/31748194-a4209032-b471-11e7-8a8b-b747121f7e6c.png) 22 | -------------------------------------------------------------------------------- /starfield/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/starfield 2 | 3 | https://gist.github.com/peterhellberg/4018e228cced61a0bb26991e49299c96 4 | 5 | Classic starfield… with [supposedly accurate stellar colors](http://www.vendian.org/mncharity/dir3/starcolor/) 6 | 7 | ![](https://user-images.githubusercontent.com/565124/32411599-a5fcba72-c1df-11e7-8730-a570470a4eee.gif) 8 | 9 | ![](https://user-images.githubusercontent.com/565124/32411603-de03d6f8-c1df-11e7-999b-90ac928551b1.png) 10 | ![](https://user-images.githubusercontent.com/565124/32411605-de62fa34-c1df-11e7-9d7e-ebf5bc1d3f7e.png) 11 | ![](https://user-images.githubusercontent.com/565124/32411694-43be69ca-c1e2-11e7-8090-b73c08f0ad8d.png) 12 | -------------------------------------------------------------------------------- /starfield/starfield.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image/color" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/imdraw" 10 | "github.com/faiface/pixel/pixelgl" 11 | ) 12 | 13 | const w, h = float64(1024), float64(512) 14 | 15 | var speed = float64(200) 16 | 17 | var stars [1024]*star 18 | 19 | func init() { 20 | rand.Seed(4) 21 | 22 | for i := 0; i < len(stars); i++ { 23 | stars[i] = newStar() 24 | } 25 | } 26 | 27 | type star struct { 28 | pixel.Vec 29 | Z float64 30 | P float64 31 | C color.RGBA 32 | } 33 | 34 | func newStar() *star { 35 | return &star{ 36 | pixel.V(random(-w, w), random(-h, h)), 37 | random(0, w), 0, Colors[rand.Intn(len(Colors))], 38 | } 39 | } 40 | 41 | func (s *star) update(d float64) { 42 | s.P = s.Z 43 | s.Z -= d * speed 44 | 45 | if s.Z < 0 { 46 | s.X = random(-w, w) 47 | s.Y = random(-h, h) 48 | s.Z = w 49 | s.P = s.Z 50 | } 51 | } 52 | 53 | func (s *star) draw(imd *imdraw.IMDraw) { 54 | p := pixel.V( 55 | scale(s.X/s.Z, 0, 1, 0, w), 56 | scale(s.Y/s.Z, 0, 1, 0, h), 57 | ) 58 | 59 | o := pixel.V( 60 | scale(s.X/s.P, 0, 1, 0, w), 61 | scale(s.Y/s.P, 0, 1, 0, h), 62 | ) 63 | 64 | r := scale(s.Z, 0, w, 11, 0) 65 | 66 | imd.Color = s.C 67 | 68 | if p.Sub(o).Len() > 6 { 69 | imd.Push(p, o) 70 | imd.Line(r) 71 | } 72 | 73 | imd.Push(p) 74 | imd.Circle(r, 0) 75 | } 76 | 77 | func run() { 78 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 79 | Bounds: pixel.R(0, 0, w, h), 80 | VSync: true, 81 | Undecorated: true, 82 | }) 83 | if err != nil { 84 | panic(err) 85 | } 86 | 87 | imd := imdraw.New(nil) 88 | 89 | imd.Precision = 7 90 | 91 | imd.SetMatrix(pixel.IM.Moved(win.Bounds().Center())) 92 | 93 | last := time.Now() 94 | 95 | for !win.Closed() { 96 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 97 | 98 | if win.Pressed(pixelgl.KeyUp) { 99 | speed += 10 100 | } 101 | 102 | if win.Pressed(pixelgl.KeyDown) { 103 | if speed > 10 { 104 | speed -= 10 105 | } 106 | } 107 | 108 | if win.Pressed(pixelgl.KeySpace) { 109 | speed = 100 110 | } 111 | 112 | d := time.Since(last).Seconds() 113 | 114 | last = time.Now() 115 | 116 | imd.Clear() 117 | 118 | for _, s := range stars { 119 | s.update(d) 120 | s.draw(imd) 121 | } 122 | 123 | win.Clear(color.Black) 124 | imd.Draw(win) 125 | win.Update() 126 | } 127 | } 128 | 129 | func main() { 130 | pixelgl.Run(run) 131 | } 132 | 133 | func random(min, max float64) float64 { 134 | return rand.Float64()*(max-min) + min 135 | } 136 | 137 | func scale(unscaledNum, min, max, minAllowed, maxAllowed float64) float64 { 138 | return (maxAllowed-minAllowed)*(unscaledNum-min)/(max-min) + minAllowed 139 | } 140 | 141 | // Colors based on stellar types listed at 142 | // http://www.vendian.org/mncharity/dir3/starcolor/ 143 | var Colors = []color.RGBA{ 144 | color.RGBA{157, 180, 255, 255}, 145 | color.RGBA{162, 185, 255, 255}, 146 | color.RGBA{167, 188, 255, 255}, 147 | color.RGBA{170, 191, 255, 255}, 148 | color.RGBA{175, 195, 255, 255}, 149 | color.RGBA{186, 204, 255, 255}, 150 | color.RGBA{192, 209, 255, 255}, 151 | color.RGBA{202, 216, 255, 255}, 152 | color.RGBA{228, 232, 255, 255}, 153 | color.RGBA{237, 238, 255, 255}, 154 | color.RGBA{251, 248, 255, 255}, 155 | color.RGBA{255, 249, 249, 255}, 156 | color.RGBA{255, 245, 236, 255}, 157 | color.RGBA{255, 244, 232, 255}, 158 | color.RGBA{255, 241, 223, 255}, 159 | color.RGBA{255, 235, 209, 255}, 160 | color.RGBA{255, 215, 174, 255}, 161 | color.RGBA{255, 198, 144, 255}, 162 | color.RGBA{255, 190, 127, 255}, 163 | color.RGBA{255, 187, 123, 255}, 164 | color.RGBA{255, 187, 123, 255}, 165 | } 166 | -------------------------------------------------------------------------------- /tree/.gitignore: -------------------------------------------------------------------------------- 1 | tree 2 | -------------------------------------------------------------------------------- /tree/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/tree 2 | 3 | https://gist.github.com/peterhellberg/4413f33014f8232ecb5cdc113034b9ae 4 | 5 | ## Experiments 6 | 7 | ### [tree](/tree/tree.go) 8 | 9 | | Key | Description | 10 | |-------|----------------------------| 11 | | A | Increase fraction by 0.005 | 12 | | Z | Decrease fraction by 0.005 | 13 | | C | Toggle circles | 14 | | L | Toggle light | 15 | | S | Shuffle state | 16 | | R | Red color mask | 17 | | G | Green color mask | 18 | | B | Blue color mask | 19 | | W | White color mask (default) | 20 | | Up | Increase length by 0.5 | 21 | | Down | Decrease length by 0.5 | 22 | | Left | Increase angle by 0.5 | 23 | | Right | Decrease angle by 0.5 | 24 | | 1 | Depth 1 | 25 | | 2 | Depth 2 | 26 | | 3 | Depth 3 | 27 | | 4 | Depth 4 | 28 | | 5 | Depth 5 | 29 | | 6 | Depth 6 | 30 | | 7 | Depth 7 | 31 | | 8 | Depth 8 | 32 | | 9 | Depth 9 | 33 | 34 | ![animation](https://user-images.githubusercontent.com/565124/29730798-1aae495c-89e2-11e7-8071-3359f3c74088.gif) 35 | 36 | ![](https://user-images.githubusercontent.com/565124/29733012-84136aa4-89eb-11e7-98a7-7f60b7ba6399.png) 37 | ![](https://user-images.githubusercontent.com/565124/29733017-87751a62-89eb-11e7-8044-c2f0c52e5034.png) 38 | ![](https://user-images.githubusercontent.com/565124/29733021-8a9efd70-89eb-11e7-86a2-9bff6dfc99ee.png) 39 | ![](https://user-images.githubusercontent.com/565124/29733023-8ca77d9a-89eb-11e7-99c0-b036ded67336.png) 40 | 41 | ```json 42 | {"depth":9,"theta":-321,"angle":-50,"frac":-0.8750000000000012,"length":369,"mask":{"R":128,"G":128,"B":255,"A":255},"circles":true} 43 | ``` 44 | ![](https://user-images.githubusercontent.com/565124/29735357-ad33db96-89f8-11e7-88f0-087dba0a2a87.png) 45 | 46 | ```json 47 | {"depth":5,"theta":37,"angle":47.5,"frac":0.6499999999999999,"length":155,"mask":{"R":255,"G":128,"B":128,"A":255},"circles":false} 48 | ``` 49 | ![](https://user-images.githubusercontent.com/565124/29735431-36e99682-89f9-11e7-9027-99a0f06b0ff1.png) 50 | -------------------------------------------------------------------------------- /tree/lerp-mask-color-states-rgb-gradient.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/png" 8 | "os" 9 | 10 | "github.com/peterhellberg/gradient" 11 | ) 12 | 13 | func main() { 14 | n := 128 15 | 16 | hg := gradient.NewHorizontal(n, 10, []gradient.Stop{ 17 | {0.0, color.NRGBA{243, 119, 54, 255}}, 18 | {0.5, color.NRGBA{3, 146, 207, 255}}, 19 | {1.0, color.NRGBA{123, 192, 67, 255}}, 20 | }) 21 | 22 | saveImage(hg, "/tmp/horizontal-gradient.png") 23 | 24 | for i := 0; i < n; i++ { 25 | c := hg.At(i, 0).(color.NRGBA) 26 | 27 | fmt.Printf(`{`+ 28 | `"depth":7,`+ 29 | `"theta":27,`+ 30 | `"angle":34,`+ 31 | `"frac":0.839,`+ 32 | `"length":93.518,`+ 33 | `"mask":{`+ 34 | `"R":%d,`+ 35 | `"G":%d,`+ 36 | `"B":%d,`+ 37 | `"A":%d`+ 38 | `},`+ 39 | `"circles":true,`+ 40 | `"light":true}`+"\n", 41 | c.R, c.G, c.B, c.A) 42 | } 43 | 44 | } 45 | 46 | func saveImage(m image.Image, path string) error { 47 | f, err := os.Create(path) 48 | if err != nil { 49 | return err 50 | } 51 | defer f.Close() 52 | 53 | return png.Encode(f, m) 54 | } 55 | 56 | //RED: {"depth":7,"theta":27,"angle":34,"frac":0.839,"length":93.518,"mask":{"R":243,"G":119,"B":54,"A":55},"circles":true,"light":true} 57 | //GREEN: {"depth":7,"theta":27,"angle":34,"frac":0.839,"length":93.518,"mask":{"R":123,"G":192,"B":67,"A":55},"circles":true,"light":true} 58 | -------------------------------------------------------------------------------- /tree/lerp-mask-color-states.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/png" 8 | "os" 9 | 10 | colorful "github.com/lucasb-eyer/go-colorful" 11 | ) 12 | 13 | func main() { 14 | keypoints := GradientTable{ 15 | {colorful.MakeColor(color.NRGBA{255, 255, 255, 255}), 0.0}, 16 | {colorful.MakeColor(color.NRGBA{123, 192, 67, 55}), 0.6}, 17 | {colorful.MakeColor(color.NRGBA{243, 119, 54, 55}), 1.0}, 18 | } 19 | 20 | for i := 0; i < 100; i++ { 21 | var ( 22 | c = keypoints.GetInterpolatedColorFor(float64(i+1) * 0.01) 23 | r, g, b = c.RGB255() 24 | d = 1 + i/16 25 | t = (-i * 2) + 50 26 | a = i / 3 27 | f = 0.34 + (float64(i) * 0.00455) 28 | l = float64(i) * 1.45 29 | ) 30 | 31 | fmt.Printf(`{`+ 32 | `"depth":%d,`+ 33 | `"theta":%d,`+ 34 | `"angle":%d,`+ 35 | `"frac":%f,`+ 36 | `"length":%f,`+ 37 | `"mask":{`+ 38 | `"R":%d,`+ 39 | `"G":%d,`+ 40 | `"B":%d,`+ 41 | `"A":%d`+ 42 | `},`+ 43 | `"circles":true,`+ 44 | `"light":false}`+"\n", 45 | d, t, a, f, l, r, g, b, 55) 46 | } 47 | } 48 | 49 | func saveImage(m image.Image, path string) error { 50 | f, err := os.Create(path) 51 | if err != nil { 52 | return err 53 | } 54 | defer f.Close() 55 | 56 | return png.Encode(f, m) 57 | } 58 | 59 | type GradientTable []struct { 60 | Col colorful.Color 61 | Pos float64 62 | } 63 | 64 | func (self GradientTable) GetInterpolatedColorFor(t float64) colorful.Color { 65 | for i := 0; i < len(self)-1; i++ { 66 | c1 := self[i] 67 | c2 := self[i+1] 68 | if c1.Pos <= t && t <= c2.Pos { 69 | t := (t - c1.Pos) / (c2.Pos - c1.Pos) 70 | return c1.Col.BlendHcl(c2.Col, t).Clamped() 71 | } 72 | } 73 | 74 | return self[len(self)-1].Col 75 | } 76 | -------------------------------------------------------------------------------- /tree/steps/.gitignore: -------------------------------------------------------------------------------- 1 | step* 2 | -------------------------------------------------------------------------------- /tree/tree.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "image/color" 7 | "math" 8 | "math/rand" 9 | "os" 10 | "time" 11 | 12 | "github.com/faiface/pixel" 13 | "github.com/faiface/pixel/imdraw" 14 | "github.com/faiface/pixel/pixelgl" 15 | ) 16 | 17 | const w, h = 1024, 600 18 | 19 | var ( 20 | s = newState() 21 | enc = json.NewEncoder(os.Stdout) 22 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 23 | pressed = make(chan pixelgl.Button, 5) 24 | ) 25 | 26 | type state struct { 27 | Depth int `json:"depth"` 28 | Theta int `json:"theta"` 29 | Angle float64 `json:"angle"` 30 | Frac float64 `json:"frac"` 31 | Length float64 `json:"length"` 32 | Mask color.RGBA `json:"mask"` 33 | Circles bool `json:"circles"` 34 | Light bool `json:"light"` 35 | updated bool 36 | } 37 | 38 | func newState() *state { 39 | return &state{ 40 | Depth: 4, 41 | Angle: 29, 42 | Length: 100, 43 | Frac: 0.805, 44 | Mask: color.RGBA{255, 255, 255, 255}, 45 | Light: true, 46 | updated: true, 47 | } 48 | } 49 | 50 | func run() { 51 | var delay time.Duration 52 | 53 | flag.DurationVar(&delay, "delay", 100*time.Millisecond, "delay between frames") 54 | 55 | flag.Parse() 56 | 57 | go func(dec *json.Decoder) { 58 | for { 59 | if err := dec.Decode(s); err != nil { 60 | break 61 | } 62 | 63 | s.updated = true 64 | time.Sleep(delay) 65 | } 66 | }(json.NewDecoder(os.Stdin)) 67 | 68 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 69 | Bounds: pixel.R(0, 0, float64(w), float64(h)), 70 | VSync: true, 71 | Undecorated: true, 72 | }) 73 | if err != nil { 74 | panic(err) 75 | } 76 | 77 | go update() 78 | 79 | imd := imdraw.New(nil) 80 | imd.SetMatrix(flipY) 81 | 82 | for !win.Closed() { 83 | input(win) 84 | 85 | win.Clear(color.RGBA{55, 56, 84, 255}) 86 | 87 | if s.updated { 88 | win.SetColorMask(s.Mask) 89 | 90 | imd.Clear() 91 | branch(imd, w/2, -50, s.Length, 0, s.Depth) 92 | s.updated = false 93 | } 94 | 95 | imd.Draw(win) 96 | 97 | win.Update() 98 | } 99 | } 100 | 101 | func branch(imd *imdraw.IMDraw, x, y, distance, direction float64, depth int) { 102 | direction = direction - float64(s.Theta)/11 103 | 104 | x2 := x + distance*math.Sin(direction*math.Pi/180) 105 | y2 := y - distance*math.Cos(direction*math.Pi/180) 106 | 107 | start, end := pixel.V(x, y), pixel.V(x2, y2) 108 | 109 | if depth > 1 { 110 | imd.Color = color.RGBA{158, 55, 159, 255} 111 | imd.Push(start, end) 112 | imd.Line((float64(depth)) * 5) 113 | } 114 | 115 | if depth > 0 { 116 | imd.Color = color.RGBA{232, 106, 240, 255} 117 | imd.Push(start, end) 118 | imd.Line((float64(depth)) * 4) 119 | } 120 | 121 | imd.Color = color.RGBA{s.Mask.G, s.Mask.B, 32, 255} 122 | imd.Push(start, end) 123 | imd.Line(float64(depth) + 2) 124 | 125 | if depth < 1 { 126 | if s.Light { 127 | next := pixel.V( 128 | x2+distance*math.Sin(direction*math.Pi/180), 129 | y2-distance*math.Cos(direction*math.Pi/180), 130 | ) 131 | 132 | imd.Color = color.RGBA{55, 56, 84, 55} 133 | imd.Push(next, pixel.V(x2, y2), end, pixel.V(w/2, -h)) 134 | imd.Polygon(1) 135 | } 136 | } else { 137 | branch(imd, x2, y2, distance*s.Frac, direction-s.Angle, depth-1) 138 | branch(imd, x2, y2, distance*s.Frac, direction+s.Angle, depth-1) 139 | 140 | if s.Circles { 141 | if depth > 0 { 142 | imd.Color = color.RGBA{s.Mask.R + 32%100 + 55, s.Mask.G + 32%100 + 55, s.Mask.B + 32%100 + 55, 72} 143 | imd.Push(start) 144 | imd.Circle(distance/5, float64(depth)/1.5+1) 145 | 146 | imd.Color = color.RGBA{255, 255, 255, 144} 147 | imd.Push(start, end) 148 | imd.Circle(distance/8, 0) 149 | } else { 150 | imd.Color = color.RGBA{232, 106, 240, 55} 151 | imd.Push(start, end) 152 | imd.Line((float64(depth) + 1) * 2) 153 | } 154 | } 155 | } 156 | } 157 | 158 | func update() { 159 | for key := range pressed { 160 | switch key { 161 | case pixelgl.KeyA: 162 | s.Frac += 0.005 163 | case pixelgl.KeyZ: 164 | s.Frac -= 0.005 165 | case pixelgl.KeyC: 166 | s.Circles = !s.Circles 167 | case pixelgl.KeyR: 168 | s.Mask = color.RGBA{243, 119, 54, 55} 169 | case pixelgl.KeyG: 170 | s.Mask = color.RGBA{123, 192, 67, 55} 171 | case pixelgl.KeyB: 172 | s.Mask = color.RGBA{3, 146, 207, 100} 173 | case pixelgl.KeyW: 174 | s.Mask = color.RGBA{255, 255, 255, 255} 175 | case pixelgl.KeyS: 176 | s.Angle = rand.Float64() * 180 177 | s.Length = rand.Float64() * 180 178 | s.Frac = rand.Float64() 179 | s.Theta = rand.Intn(100) - 50 180 | case pixelgl.KeyL: 181 | s.Light = !s.Light 182 | case pixelgl.KeyUp: 183 | s.Length += 0.5 184 | case pixelgl.KeyDown: 185 | s.Length -= 0.5 186 | case pixelgl.KeyLeft: 187 | s.Angle += 0.5 188 | s.Theta += rand.Intn(5) 189 | case pixelgl.KeyRight: 190 | s.Angle -= 0.5 191 | s.Theta -= rand.Intn(5) 192 | case pixelgl.Key1: 193 | s.Depth = 1 194 | case pixelgl.Key2: 195 | s.Depth = 2 196 | case pixelgl.Key3: 197 | s.Depth = 3 198 | case pixelgl.Key4: 199 | s.Depth = 4 200 | case pixelgl.Key5: 201 | s.Depth = 5 202 | case pixelgl.Key6: 203 | s.Depth = 6 204 | case pixelgl.Key7: 205 | s.Depth = 7 206 | case pixelgl.Key8: 207 | s.Depth = 8 208 | case pixelgl.Key9: 209 | s.Depth = 9 210 | } 211 | 212 | enc.Encode(s) 213 | 214 | s.updated = true 215 | } 216 | } 217 | 218 | func input(win *pixelgl.Window) { 219 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 220 | 221 | if win.Pressed(pixelgl.KeyA) { 222 | pressed <- pixelgl.KeyA 223 | } 224 | 225 | if win.Pressed(pixelgl.KeyZ) { 226 | pressed <- pixelgl.KeyZ 227 | } 228 | 229 | if win.JustPressed(pixelgl.KeyC) { 230 | pressed <- pixelgl.KeyC 231 | } 232 | 233 | if win.JustPressed(pixelgl.KeyR) { 234 | pressed <- pixelgl.KeyR 235 | } 236 | 237 | if win.JustPressed(pixelgl.KeyG) { 238 | pressed <- pixelgl.KeyG 239 | } 240 | 241 | if win.JustPressed(pixelgl.KeyB) { 242 | pressed <- pixelgl.KeyB 243 | } 244 | 245 | if win.JustPressed(pixelgl.KeyW) { 246 | pressed <- pixelgl.KeyW 247 | } 248 | 249 | if win.JustPressed(pixelgl.KeyS) { 250 | pressed <- pixelgl.KeyS 251 | } 252 | 253 | if win.JustPressed(pixelgl.KeyL) { 254 | pressed <- pixelgl.KeyL 255 | } 256 | 257 | if win.Pressed(pixelgl.KeyUp) { 258 | pressed <- pixelgl.KeyUp 259 | } 260 | 261 | if win.Pressed(pixelgl.KeyDown) { 262 | pressed <- pixelgl.KeyDown 263 | } 264 | 265 | if win.Pressed(pixelgl.KeyLeft) { 266 | pressed <- pixelgl.KeyLeft 267 | } 268 | 269 | if win.Pressed(pixelgl.KeyRight) { 270 | pressed <- pixelgl.KeyRight 271 | } 272 | 273 | if win.JustPressed(pixelgl.Key1) { 274 | pressed <- pixelgl.Key1 275 | } 276 | 277 | if win.JustPressed(pixelgl.Key2) { 278 | pressed <- pixelgl.Key2 279 | } 280 | 281 | if win.JustPressed(pixelgl.Key3) { 282 | pressed <- pixelgl.Key3 283 | } 284 | 285 | if win.JustPressed(pixelgl.Key4) { 286 | pressed <- pixelgl.Key4 287 | } 288 | 289 | if win.JustPressed(pixelgl.Key5) { 290 | pressed <- pixelgl.Key5 291 | } 292 | 293 | if win.JustPressed(pixelgl.Key6) { 294 | pressed <- pixelgl.Key6 295 | } 296 | 297 | if win.JustPressed(pixelgl.Key7) { 298 | pressed <- pixelgl.Key7 299 | } 300 | 301 | if win.JustPressed(pixelgl.Key8) { 302 | pressed <- pixelgl.Key8 303 | } 304 | 305 | if win.JustPressed(pixelgl.Key9) { 306 | pressed <- pixelgl.Key9 307 | } 308 | 309 | if win.JustPressed(pixelgl.Key0) { 310 | pressed <- pixelgl.Key0 311 | } 312 | 313 | } 314 | 315 | func main() { 316 | pixelgl.Run(run) 317 | } 318 | -------------------------------------------------------------------------------- /tunnel/.gitignore: -------------------------------------------------------------------------------- 1 | plasma-tunnel 2 | xor-tunnel 3 | -------------------------------------------------------------------------------- /tunnel/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/tunnel 2 | 3 | https://gist.github.com/peterhellberg/80df53fa909a2012f5acc00e2b908fc0 4 | 5 | This code is heavily based on http://lodev.org/cgtutor/tunnel.html#The_code 6 | 7 | ## Experiments 8 | 9 | ### [plasma-tunnel](/tunnel/plasma-tunnel.go) 10 | ![plasma-tunnel](https://cloud.githubusercontent.com/assets/565124/25528930/48bc195c-2c20-11e7-8db8-d3b01b4a8903.gif) 11 | 12 | ### [xor-tunnel](/tunnel/xor-tunnel.go) 13 | ![xor-tunnel](https://cloud.githubusercontent.com/assets/565124/25475885/8f018e6c-2b38-11e7-9a9e-9ca281f99c1b.png) 14 | -------------------------------------------------------------------------------- /tunnel/plasma-tunnel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "math" 6 | "time" 7 | 8 | "github.com/faiface/pixel" 9 | "github.com/faiface/pixel/pixelgl" 10 | 11 | "github.com/peterhellberg/plasma" 12 | "github.com/peterhellberg/plasma/palette" 13 | ) 14 | 15 | const ( 16 | w, h, size = 640, 480, 256 17 | fw, fh, fsize = float64(w), float64(h), float64(size) 18 | ) 19 | 20 | var ( 21 | start = time.Now() 22 | texture = plasmaImage(size, size, 1) 23 | distanceTable = [h * 2][w * 2]int{} 24 | angleTable = [h * 2][w * 2]int{} 25 | ratio = 16.0 26 | ) 27 | 28 | func run() { 29 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 30 | Bounds: pixel.R(0, 0, float64(w), float64(h)), 31 | VSync: true, 32 | Undecorated: true, 33 | }) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | win.SetSmooth(false) 39 | 40 | canvas := pixelgl.NewCanvas(win.Bounds()) 41 | 42 | go func() { 43 | s := 1 44 | for range time.Tick(48 * time.Millisecond) { 45 | s++ 46 | 47 | texture = plasmaImage(size, size, s) 48 | } 49 | }() 50 | 51 | for y := 0; y < h*2; y++ { 52 | for x := 0; x < w*2; x++ { 53 | fx, fy := float64(x), float64(y) 54 | 55 | distance := int(ratio*size/math.Sqrt((fx-fw)*(fx-fw)+(fy-fh)*(fy-fh))) % size 56 | angle := int(0.5 * size * math.Atan2(fy-fh, fx-fw) / math.Pi) 57 | 58 | distanceTable[y][x] = distance 59 | angleTable[y][x] = angle 60 | } 61 | } 62 | 63 | c := win.Bounds().Center() 64 | 65 | m := pixel.IM.Moved(c) 66 | 67 | for !win.Closed() { 68 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 69 | 70 | drawFrame(canvas) 71 | 72 | canvas.Draw(win, m) 73 | 74 | win.Update() 75 | } 76 | } 77 | 78 | func drawFrame(canvas *pixelgl.Canvas) { 79 | animation := time.Since(start).Seconds() 80 | 81 | shiftX := int(fsize * 0.1 * animation) 82 | shiftY := int(0) 83 | 84 | shiftLookX := int(fw/3 + float64(int(fw/3*math.Sin(animation)))) 85 | shiftLookY := int(fh/3 + float64(int(fh/3*math.Sin(animation/2)))) 86 | 87 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 88 | 89 | for y := 0; y < h; y++ { 90 | for x := 0; x < w; x++ { 91 | buffer.Set(x, y, texture.At( 92 | int(uint(distanceTable[y+shiftLookY][x+shiftLookX]+shiftX)%size), 93 | int(uint(angleTable[y+shiftLookY][x+shiftLookX]+shiftY)%size), 94 | )) 95 | } 96 | } 97 | 98 | canvas.SetPixels(buffer.Pix) 99 | } 100 | 101 | func plasmaImage(w, h, s int) image.Image { 102 | return plasma.New(w, h, 4).Image(w, h, s, palette.DefaultGradient) 103 | } 104 | 105 | func main() { 106 | pixelgl.Run(run) 107 | } 108 | -------------------------------------------------------------------------------- /tunnel/xor-tunnel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/pixelgl" 11 | ) 12 | 13 | const ( 14 | w, h, size = 640, 480, 128 15 | fw, fh, fsize = float64(w), float64(h), float64(size) 16 | ) 17 | 18 | var ( 19 | start = time.Now() 20 | texture = xorImage(size, size) 21 | distanceTable = [h * 2][w * 2]int{} 22 | angleTable = [h * 2][w * 2]int{} 23 | ratio = 16.0 24 | ) 25 | 26 | func run() { 27 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 28 | Bounds: pixel.R(0, 0, float64(w), float64(h)), 29 | VSync: true, 30 | Undecorated: true, 31 | }) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | win.SetSmooth(false) 37 | 38 | canvas := pixelgl.NewCanvas(win.Bounds()) 39 | 40 | for y := 0; y < h*2; y++ { 41 | for x := 0; x < w*2; x++ { 42 | fx, fy := float64(x), float64(y) 43 | 44 | distance := int(ratio*size/math.Sqrt((fx-fw)*(fx-fw)+(fy-fh)*(fy-fh))) % size 45 | angle := int(0.5 * size * math.Atan2(fy-fh, fx-fw) / math.Pi) 46 | 47 | distanceTable[y][x] = distance 48 | angleTable[y][x] = angle 49 | } 50 | } 51 | 52 | c := win.Bounds().Center() 53 | 54 | m := pixel.IM.Moved(c) 55 | 56 | for !win.Closed() { 57 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 58 | 59 | drawFrame(canvas) 60 | 61 | canvas.Draw(win, m) 62 | 63 | win.Update() 64 | } 65 | } 66 | 67 | func drawFrame(canvas *pixelgl.Canvas) { 68 | animation := time.Since(start).Seconds() 69 | 70 | shiftX := int(fsize * 0.16 * animation) 71 | shiftY := int(0) 72 | 73 | shiftLookX := int(fw/3 + float64(int(fw/3*math.Sin(animation)))) 74 | shiftLookY := int(fh/2 + float64(int(fh/3*math.Sin(animation*2.0)))) 75 | 76 | buffer := image.NewRGBA(image.Rect(0, 0, w, h)) 77 | 78 | for y := 0; y < h; y++ { 79 | for x := 0; x < w; x++ { 80 | buffer.Set(x, y, texture.At( 81 | int(uint(distanceTable[y+shiftLookY][x+shiftLookX]+shiftX)%size), 82 | int(uint(angleTable[y+shiftLookY][x+shiftLookX]+shiftY)%size), 83 | )) 84 | } 85 | } 86 | 87 | canvas.SetPixels(buffer.Pix) 88 | } 89 | 90 | func xorImage(w, h int) image.Image { 91 | m := image.NewRGBA(image.Rect(0, 0, w, h)) 92 | 93 | for x := 0; x < w; x++ { 94 | for y := 0; y < h; y++ { 95 | u := uint8(x ^ y) 96 | 97 | c := color.RGBA{ 98 | (u) % 192, 99 | 24, 100 | (u & uint8(y)) % 128, 101 | 200, 102 | } 103 | 104 | m.Set(x, y, c) 105 | } 106 | } 107 | 108 | return m 109 | } 110 | 111 | func main() { 112 | pixelgl.Run(run) 113 | } 114 | -------------------------------------------------------------------------------- /warping/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/warping 2 | 3 | https://gist.github.com/peterhellberg/cc35049a535a53296de897d4db5ea48d 4 | 5 | ## Experiments 6 | 7 | ![](https://user-images.githubusercontent.com/565124/30234247-a470418a-94fb-11e7-9cf5-7bbb783da228.gif) 8 | ![](https://user-images.githubusercontent.com/565124/30235840-519c335c-950e-11e7-8eba-7654041b93aa.gif) 9 | 10 | ![](https://user-images.githubusercontent.com/565124/30235035-e5b205e0-9503-11e7-8195-1927591059fb.png) 11 | ![](https://user-images.githubusercontent.com/565124/30235040-e979eb70-9503-11e7-8a61-35275097885b.png) 12 | ![](https://user-images.githubusercontent.com/565124/30235044-eb595fca-9503-11e7-8a48-7211fdbce4b4.png) 13 | 14 | ![](https://user-images.githubusercontent.com/565124/30237181-f153a2d6-952c-11e7-91cf-b40f3126a7ec.png) 15 | 16 | ![](https://user-images.githubusercontent.com/565124/30237339-4cf4c5c2-9530-11e7-9007-4b9ab1b2a051.png) 17 | ![](https://user-images.githubusercontent.com/565124/30237342-518cb202-9530-11e7-9542-07a281144e60.png) 18 | ![](https://user-images.githubusercontent.com/565124/30237344-57444a70-9530-11e7-95c3-d6a2da79f8fb.png) 19 | ![](https://user-images.githubusercontent.com/565124/30237345-5ae3dc36-9530-11e7-9750-19411bb0217e.png) 20 | ![](https://user-images.githubusercontent.com/565124/30237349-60310a60-9530-11e7-928f-0b93b64f13aa.png) 21 | ![](https://user-images.githubusercontent.com/565124/30237354-65293772-9530-11e7-9c2f-49eb534d55e0.png) 22 | ![](https://user-images.githubusercontent.com/565124/30237355-6afa0a14-9530-11e7-91cd-7807369818ef.png) 23 | -------------------------------------------------------------------------------- /warping/cuttlefish.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/warping/cuttlefish.jpg -------------------------------------------------------------------------------- /warping/flower.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/warping/flower.png -------------------------------------------------------------------------------- /warping/smokestack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterhellberg/pixel-experiments/476ad0088448cb8048927fc6211ba6a40fe5f231/warping/smokestack.jpg -------------------------------------------------------------------------------- /warping/steps/step01.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image" 6 | "math/rand" 7 | "os" 8 | 9 | "image/color" 10 | _ "image/gif" 11 | _ "image/jpeg" 12 | _ "image/png" 13 | 14 | perlin "github.com/aquilax/go-perlin" 15 | 16 | "github.com/faiface/pixel" 17 | "github.com/faiface/pixel/pixelgl" 18 | ) 19 | 20 | var ( 21 | alpha, beta float64 22 | seed int64 23 | 24 | w, h int 25 | 26 | source *image.NRGBA 27 | 28 | noise *perlin.Perlin 29 | 30 | bounds pixel.Rect 31 | matrix pixel.Matrix 32 | 33 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 34 | ) 35 | 36 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 37 | frame := image.NewRGBA(image.Rect(0, 0, w, h)) 38 | 39 | for x := 0; x < w; x++ { 40 | for y := 0; y < h; y++ { 41 | p := pixel.V(float64(x)*0.012, float64(y)*0.04) 42 | v := pattern(p) 43 | 44 | frame.Set(x, y, color.NRGBA{ 45 | uint8(v * 255), 46 | uint8(v * 255), 47 | uint8(v * 255), 48 | 255, 49 | }) 50 | } 51 | } 52 | 53 | canvas.Draw(win, matrix) 54 | 55 | canvas.SetPixels(frame.Pix) 56 | } 57 | 58 | func pattern(p pixel.Vec) float64 { 59 | return pattern1(p) 60 | } 61 | 62 | func pattern1(p pixel.Vec) float64 { 63 | return fbm(p) 64 | } 65 | 66 | func fbm(p pixel.Vec) float64 { 67 | return noise.Noise2D(p.X, p.Y) 68 | } 69 | 70 | func main() { 71 | var fn string 72 | 73 | flag.StringVar(&fn, "i", "../smokestack.jpg", "image") 74 | flag.Float64Var(&alpha, "a", 7.99, "alpha") 75 | flag.Float64Var(&beta, "b", 1.42, "beta") 76 | flag.Int64Var(&seed, "s", 123, "seed") 77 | 78 | flag.Parse() 79 | 80 | if err := setup(fn); err == nil { 81 | pixelgl.Run(run) 82 | } 83 | } 84 | 85 | func setup(fn string) error { 86 | noise = perlin.NewPerlinRandSource(alpha, beta, 1, rand.NewSource(seed)) 87 | 88 | m, err := loadImage(fn) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 94 | 95 | source = m 96 | bounds = pixel.R(0, 0, float64(w), float64(w)) 97 | matrix = flipY.Moved(bounds.Center()) 98 | 99 | return nil 100 | } 101 | 102 | func run() { 103 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 104 | Bounds: bounds, 105 | VSync: true, 106 | Undecorated: true, 107 | }) 108 | if err != nil { 109 | panic(err) 110 | } 111 | 112 | canvas := pixelgl.NewCanvas(bounds) 113 | 114 | for !win.Closed() { 115 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 116 | 117 | render(win, canvas) 118 | 119 | win.Update() 120 | } 121 | } 122 | 123 | func loadImage(fn string) (*image.NRGBA, error) { 124 | f, err := os.Open(fn) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | m, _, err := image.Decode(f) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | if rgba, ok := m.(*image.NRGBA); ok { 135 | return rgba, nil 136 | } 137 | 138 | b := m.Bounds() 139 | 140 | rgba := image.NewNRGBA(b) 141 | 142 | for y := b.Min.Y; y < b.Max.Y; y++ { 143 | for x := b.Min.X; x < b.Max.X; x++ { 144 | rgba.Set(x, y, m.At(x, y)) 145 | } 146 | } 147 | 148 | return rgba, nil 149 | } 150 | -------------------------------------------------------------------------------- /warping/steps/step02.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "image" 6 | "image/color" 7 | "os" 8 | "time" 9 | 10 | _ "image/gif" 11 | _ "image/jpeg" 12 | _ "image/png" 13 | 14 | pixel "github.com/faiface/pixel" 15 | pixelgl "github.com/faiface/pixel/pixelgl" 16 | opensimplex "github.com/ojrac/opensimplex-go" 17 | zerolog "github.com/rs/zerolog" 18 | log "github.com/rs/zerolog/log" 19 | ) 20 | 21 | var ( 22 | w, h int 23 | 24 | mouse pixel.Vec 25 | 26 | scale float64 27 | 28 | delay time.Duration 29 | 30 | noise *opensimplex.Noise 31 | 32 | source *image.RGBA 33 | target *image.RGBA 34 | 35 | bounds pixel.Rect 36 | matrix pixel.Matrix 37 | 38 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 39 | ) 40 | 41 | func main() { 42 | var ( 43 | fn string 44 | alpha float64 45 | beta float64 46 | n int 47 | seed int64 48 | d time.Duration 49 | ) 50 | 51 | flag.StringVar(&fn, "i", "../smokestack.jpg", "image") 52 | flag.Float64Var(&alpha, "a", 0.105, "alpha") 53 | flag.Float64Var(&beta, "b", 0.085, "beta") 54 | flag.IntVar(&n, "n", 2, "n") 55 | flag.Int64Var(&seed, "s", 123, "seed") 56 | flag.DurationVar(&d, "d", 16*time.Millisecond, "delay") 57 | flag.Parse() 58 | 59 | if d <= 0 { 60 | d = 1 * time.Millisecond 61 | } 62 | 63 | if err := setup(fn, alpha, beta, n, seed, d); err != nil { 64 | log.Fatal().Err(err).Msg("setup") 65 | } 66 | 67 | pixelgl.Run(run) 68 | } 69 | 70 | func setup(fn string, alpha, beta float64, n int, seed int64, d time.Duration) error { 71 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 72 | 73 | delay = d 74 | noise = opensimplex.NewWithSeed(seed) 75 | 76 | m, err := loadImage(fn) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 82 | 83 | source = m 84 | target = image.NewRGBA(source.Bounds()) 85 | bounds = pixel.R(0, 0, float64(w), float64(w)) 86 | matrix = flipY.Moved(bounds.Center()) 87 | 88 | return nil 89 | } 90 | 91 | func run() { 92 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 93 | Bounds: bounds, 94 | VSync: true, 95 | Undecorated: true, 96 | }) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | canvas := pixelgl.NewCanvas(bounds) 102 | 103 | ticker := time.NewTicker(delay) 104 | 105 | defer ticker.Stop() 106 | 107 | go update(ticker) 108 | 109 | for !win.Closed() { 110 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 111 | 112 | mouse = win.MousePosition() 113 | 114 | if win.JustPressed(pixelgl.Key0) { 115 | scale = 0 116 | } 117 | 118 | if win.JustPressed(pixelgl.Key1) { 119 | scale = 1 120 | } 121 | 122 | if win.JustPressed(pixelgl.Key2) { 123 | scale = 2 124 | } 125 | 126 | if win.JustPressed(pixelgl.Key3) { 127 | scale = 3 128 | } 129 | 130 | if win.Pressed(pixelgl.KeyUp) { 131 | scale += 0.01 132 | } 133 | 134 | if win.Pressed(pixelgl.KeyDown) { 135 | scale -= 0.01 136 | } 137 | 138 | render(win, canvas) 139 | 140 | win.Update() 141 | } 142 | } 143 | 144 | func update(ticker *time.Ticker) { 145 | sampled := log.Sample(&zerolog.BasicSampler{N: 1000000}) 146 | 147 | var expand bool 148 | 149 | //start := time.Now() 150 | 151 | for range ticker.C { 152 | //d := time.Since(start).Seconds() 153 | 154 | if expand { 155 | scale += 0.005 156 | 157 | if scale > 200 { 158 | expand = false 159 | } 160 | } else { 161 | scale -= 0.005 162 | 163 | if scale < -200 { 164 | expand = true 165 | } 166 | } 167 | 168 | frame := image.NewRGBA(source.Bounds()) 169 | 170 | for x := 0; x < w; x++ { 171 | for y := 0; y < h; y++ { 172 | 173 | v := pattern(pixel.V( 174 | float64(x)*0.002, 175 | float64(y)*0.002, 176 | )) 177 | 178 | if v < 0 { 179 | v = -v 180 | } 181 | 182 | if false { 183 | sampled.Print(v) 184 | } 185 | 186 | c := source.At(x, y).(color.RGBA) 187 | 188 | var r, g, b uint8 189 | 190 | switch "darken" { 191 | default: 192 | r, g, b = c.R, c.G, c.B 193 | case "black-white": 194 | uv := uint8(int(v*2) % 255) 195 | 196 | if nr := (255 - c.R) + uv; nr < 255 { 197 | r = nr + c.R 198 | } 199 | 200 | if ng := (255 - c.G) + uv; ng < 255 { 201 | g = ng + c.G 202 | } 203 | 204 | if nb := (255 - c.B) + uv; nb < 255 { 205 | b = nb + c.B 206 | } 207 | case "darken": 208 | uv := uint8(int(v*255) % 255) 209 | 210 | if uv < c.R { 211 | r = c.R - uv 212 | } 213 | 214 | if uv < c.G { 215 | g = c.G - uv 216 | } 217 | 218 | if uv < c.B { 219 | b = c.B - uv 220 | } 221 | } 222 | 223 | frame.Set(x, y, color.RGBA{r, g, b, 255}) 224 | } 225 | } 226 | 227 | target.Pix = frame.Pix 228 | } 229 | } 230 | 231 | // Inspired by this article 232 | // http://www.iquilezles.org/www/articles/warp/warp.htm 233 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 234 | canvas.SetPixels(target.Pix) 235 | 236 | canvas.Draw(win, matrix) 237 | } 238 | 239 | func pattern1(p pixel.Vec) float64 { 240 | return fbm(p) 241 | } 242 | 243 | func pattern2(p pixel.Vec) float64 { 244 | q := pixel.V( 245 | fbm(p.Add(pixel.V(0.0, 0.0))), 246 | fbm(p.Add(pixel.V(5.2, 1.3))), 247 | ) 248 | 249 | return fbm(p.Add(q.Scaled(scale))) 250 | } 251 | 252 | func pattern3(p pixel.Vec) float64 { 253 | q := pixel.V( 254 | fbm(p.Add(pixel.V(0.0, 0.0))), 255 | fbm(p.Add(pixel.V(5.2, 1.3))), 256 | ) 257 | 258 | r := pixel.V( 259 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 260 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 261 | ) 262 | 263 | return fbm(p.Add(r.Scaled(scale))) 264 | } 265 | 266 | func pattern(p pixel.Vec) float64 { 267 | return pattern3(p) 268 | } 269 | 270 | func fbm(p pixel.Vec) float64 { 271 | return noise.Eval2(p.X, p.Y) 272 | } 273 | 274 | func loadImage(fn string) (*image.RGBA, error) { 275 | f, err := os.Open(fn) 276 | if err != nil { 277 | return nil, err 278 | } 279 | 280 | m, _, err := image.Decode(f) 281 | if err != nil { 282 | return nil, err 283 | } 284 | 285 | if rgba, ok := m.(*image.RGBA); ok { 286 | return rgba, nil 287 | } 288 | 289 | b := m.Bounds() 290 | 291 | rgba := image.NewRGBA(b) 292 | 293 | for y := b.Min.Y; y < b.Max.Y; y++ { 294 | for x := b.Min.X; x < b.Max.X; x++ { 295 | rgba.Set(x, y, m.At(x, y)) 296 | } 297 | } 298 | 299 | return rgba, nil 300 | } 301 | -------------------------------------------------------------------------------- /warping/steps/step03.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern(p pixel.Vec) float64 { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(5.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))) 36 | } 37 | 38 | func fbm(p pixel.Vec) float64 { 39 | return noise.Eval3(p.X, p.Y, mouse.Cross(p)/float64(w)) 40 | } 41 | 42 | func update(ticker *time.Ticker) { 43 | var expand bool 44 | 45 | for range ticker.C { 46 | if expand { 47 | scale += 0.005 48 | 49 | if scale > 100 { 50 | expand = false 51 | } 52 | } else { 53 | scale -= 0.005 54 | 55 | if scale < -100 { 56 | expand = true 57 | } 58 | } 59 | 60 | frame := image.NewRGBA(source.Bounds()) 61 | 62 | for x := 0; x < w; x++ { 63 | for y := 0; y < h; y++ { 64 | 65 | v := pattern(pixel.V( 66 | float64(x)*0.0012, 67 | float64(y)*0.002, 68 | )) 69 | 70 | if v < 0 { 71 | v = -v * 2 72 | } 73 | 74 | c := source.At(x, y).(color.RGBA) 75 | 76 | var r, g, b uint8 77 | 78 | switch mode { 79 | case 1: 80 | uv := uint8(int(v*255) % 255) 81 | 82 | if uv < c.R { 83 | r = c.R - uv 84 | } 85 | 86 | if uv < c.G { 87 | g = c.G - uv 88 | } 89 | 90 | if uv < c.B { 91 | b = c.B - uv 92 | } 93 | case 2: 94 | uv := uint8(int(v*2) % 255) 95 | 96 | if nr := (255 - c.R) + uv; nr < 255 { 97 | r = nr + c.R 98 | } 99 | 100 | if ng := (255 - c.G) + uv; ng < 255 { 101 | g = ng + c.G 102 | } 103 | 104 | if nb := (255 - c.B) + uv; nb < 255 { 105 | b = nb + c.B 106 | } 107 | default: 108 | r, g, b = c.R, c.G, c.B 109 | } 110 | 111 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 112 | } 113 | } 114 | 115 | target.Pix = frame.Pix 116 | } 117 | } 118 | 119 | func main() { 120 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 121 | 122 | var ( 123 | fn string 124 | seed int64 125 | d time.Duration 126 | ) 127 | 128 | flag.StringVar(&fn, "i", "../smokestack.jpg", "image") 129 | flag.Int64Var(&seed, "s", 1024, "seed") 130 | flag.DurationVar(&d, "d", 8*time.Millisecond, "delay") 131 | 132 | flag.Parse() 133 | 134 | if d <= 0 { 135 | d = 1 * time.Millisecond 136 | } 137 | 138 | if err := setup(fn, seed, d); err != nil { 139 | log.Fatal().Err(err).Msg("setup") 140 | } 141 | 142 | pixelgl.Run(run) 143 | } 144 | 145 | func setup(fn string, seed int64, d time.Duration) error { 146 | delay = d 147 | noise = opensimplex.NewWithSeed(seed) 148 | 149 | m, err := loadImage(fn) 150 | if err != nil { 151 | return err 152 | } 153 | 154 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 155 | 156 | source = m 157 | target = image.NewRGBA(source.Bounds()) 158 | bounds = pixel.R(0, 0, float64(w), float64(w)) 159 | matrix = flipY.Moved(bounds.Center()) 160 | 161 | return nil 162 | } 163 | 164 | func run() { 165 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 166 | Title: "Domain Warping", 167 | Bounds: bounds, 168 | Resizable: false, 169 | VSync: true, 170 | Undecorated: true, 171 | }) 172 | if err != nil { 173 | panic(err) 174 | } 175 | 176 | canvas := pixelgl.NewCanvas(bounds) 177 | 178 | canvas.SetComposeMethod(pixel.ComposeXor) 179 | 180 | ticker := time.NewTicker(delay) 181 | 182 | defer ticker.Stop() 183 | 184 | go update(ticker) 185 | 186 | for !win.Closed() { 187 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 188 | 189 | mouse = win.MousePosition() 190 | 191 | if win.JustPressed(pixelgl.Key0) { 192 | scale = 0 193 | } 194 | 195 | if win.JustPressed(pixelgl.Key1) { 196 | mode = 1 197 | } 198 | 199 | if win.JustPressed(pixelgl.Key2) { 200 | mode = 2 201 | } 202 | 203 | if win.JustPressed(pixelgl.Key3) { 204 | mode = 3 205 | } 206 | 207 | if win.JustPressed(pixelgl.KeyRight) { 208 | mode += 1 209 | 210 | if mode > 2 { 211 | mode = 0 212 | } 213 | } 214 | 215 | if win.JustPressed(pixelgl.KeyLeft) { 216 | mode -= 1 217 | 218 | if mode < 0 { 219 | mode = 2 220 | } 221 | } 222 | 223 | if win.Pressed(pixelgl.KeyUp) { 224 | scale += 0.01 225 | } 226 | 227 | if win.Pressed(pixelgl.KeyDown) { 228 | scale -= 0.01 229 | } 230 | 231 | if win.JustPressed(pixelgl.KeyL) { 232 | log.Info(). 233 | Int("mode", mode). 234 | Float64("scale", scale). 235 | Msg("State") 236 | } 237 | 238 | render(win, canvas) 239 | 240 | win.Update() 241 | } 242 | } 243 | 244 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 245 | canvas.SetPixels(target.Pix) 246 | 247 | canvas.Draw(win, matrix) 248 | } 249 | 250 | func loadImage(fn string) (*image.RGBA, error) { 251 | f, err := os.Open(fn) 252 | if err != nil { 253 | return nil, err 254 | } 255 | 256 | m, _, err := image.Decode(f) 257 | if err != nil { 258 | return nil, err 259 | } 260 | 261 | if rgba, ok := m.(*image.RGBA); ok { 262 | return rgba, nil 263 | } 264 | 265 | b := m.Bounds() 266 | 267 | rgba := image.NewRGBA(b) 268 | 269 | for y := b.Min.Y; y < b.Max.Y; y++ { 270 | for x := b.Min.X; x < b.Max.X; x++ { 271 | rgba.Set(x, y, m.At(x, y)) 272 | } 273 | } 274 | 275 | return rgba, nil 276 | } 277 | 278 | var ( 279 | w, h int 280 | 281 | mouse pixel.Vec 282 | 283 | scale float64 284 | 285 | delay time.Duration 286 | 287 | noise *opensimplex.Noise 288 | 289 | source *image.RGBA 290 | target *image.RGBA 291 | 292 | bounds pixel.Rect 293 | matrix pixel.Matrix 294 | 295 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 296 | 297 | mode = 1 298 | ) 299 | -------------------------------------------------------------------------------- /warping/steps/step04.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern3(p pixel.Vec) (float64, pixel.Vec, pixel.Vec) { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(5.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))), q, r 36 | } 37 | 38 | func pattern(p pixel.Vec) float64 { 39 | q := pixel.V( 40 | fbm(p.Add(pixel.V(0.0, 0.0))), 41 | fbm(p.Add(pixel.V(5.2, 1.3))), 42 | ) 43 | 44 | r := pixel.V( 45 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 46 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 47 | ) 48 | 49 | return fbm(p.Add(r.Scaled(scale))) 50 | } 51 | 52 | func fbm(p pixel.Vec) float64 { 53 | return noise.Eval2(p.X, p.Y) 54 | } 55 | 56 | func update(ticker *time.Ticker) { 57 | 58 | for range ticker.C { 59 | if expand { 60 | scale += 0.005 61 | 62 | if scale > 100 { 63 | expand = false 64 | } 65 | } else { 66 | scale -= 0.005 67 | 68 | if scale < -100 { 69 | expand = true 70 | } 71 | } 72 | 73 | frame := image.NewRGBA(source.Bounds()) 74 | 75 | for x := 0; x < w; x++ { 76 | for y := 0; y < h; y++ { 77 | fx, fy := float64(x), float64(y) 78 | 79 | v, q, k := pattern3(pixel.V( 80 | float64(x)*0.0012, 81 | float64(y)*0.002, 82 | )) 83 | 84 | if v < 0 { 85 | v = -v 86 | } 87 | 88 | wx, wy := int(fx*k.X)%w, int(fy*k.Y)%h 89 | 90 | if wx < 0 || wx > w { 91 | wx = int(fx - k.X) 92 | } 93 | 94 | if wy < 0 || wy > h { 95 | wy = int(fy - k.Y) 96 | } 97 | 98 | c := source.At(wx, wy).(color.RGBA) 99 | 100 | var r, g, b uint8 101 | 102 | switch mode { 103 | case 1: 104 | uv := uint8(int(v*255) % 255) 105 | 106 | if uv < c.R { 107 | r = c.R - uv + uint8(q.X*fw) 108 | } 109 | 110 | if uv < c.G { 111 | g = c.G - uv + uint8(q.X*fw) 112 | } 113 | 114 | if uv < c.B { 115 | b = c.B - uv + uint8(q.X*fw) 116 | } 117 | case 2: 118 | uv := uint8(int(v*2) % 255) 119 | 120 | if nr := (255 - c.R) + uv; nr < 255 { 121 | r = nr + c.R 122 | } 123 | 124 | if ng := (255 - c.G) + uv; ng < 255 { 125 | g = ng + c.G 126 | } 127 | 128 | if nb := (255 - c.B) + uv; nb < 255 { 129 | b = nb + c.B 130 | } 131 | default: 132 | r, g, b = c.R, c.G, c.B 133 | } 134 | 135 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 136 | } 137 | } 138 | 139 | target.Pix = frame.Pix 140 | } 141 | } 142 | 143 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 144 | canvas.SetPixels(target.Pix) 145 | 146 | canvas.Draw(win, matrix) 147 | } 148 | 149 | func main() { 150 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 151 | 152 | var ( 153 | fn string 154 | seed int64 155 | d time.Duration 156 | ) 157 | 158 | flag.StringVar(&fn, "i", "../smokestack.jpg", "image") 159 | flag.Float64Var(&scale, "scale", -0.18009999999963955, "scale") 160 | flag.Int64Var(&seed, "seed", 1024, "seed") 161 | flag.IntVar(&mode, "mode", 0, "mode") 162 | 163 | flag.DurationVar(&d, "d", 8*time.Millisecond, "delay") 164 | 165 | flag.Parse() 166 | 167 | if d <= 0 { 168 | d = 1 * time.Millisecond 169 | } 170 | 171 | if err := setup(fn, seed, d); err != nil { 172 | log.Fatal().Err(err).Msg("setup") 173 | } 174 | 175 | pixelgl.Run(run) 176 | } 177 | 178 | func setup(fn string, seed int64, d time.Duration) error { 179 | delay = d 180 | noise = opensimplex.NewWithSeed(seed) 181 | 182 | m, err := loadImage(fn) 183 | if err != nil { 184 | return err 185 | } 186 | 187 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 188 | fw, fh = float64(w), float64(h) 189 | 190 | source = m 191 | target = image.NewRGBA(source.Bounds()) 192 | bounds = pixel.R(0, 0, float64(w), float64(w)) 193 | matrix = flipY.Moved(bounds.Center()) 194 | 195 | return nil 196 | } 197 | 198 | func run() { 199 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 200 | Title: "Domain Warping", 201 | Bounds: bounds, 202 | Resizable: false, 203 | VSync: true, 204 | Undecorated: true, 205 | }) 206 | if err != nil { 207 | panic(err) 208 | } 209 | 210 | canvas := pixelgl.NewCanvas(bounds) 211 | 212 | canvas.SetComposeMethod(pixel.ComposeXor) 213 | 214 | ticker := time.NewTicker(delay) 215 | 216 | defer ticker.Stop() 217 | 218 | go update(ticker) 219 | 220 | for !win.Closed() { 221 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 222 | 223 | mouse = win.MousePosition() 224 | 225 | if win.JustPressed(pixelgl.Key0) { 226 | scale = 0 227 | } 228 | 229 | if win.JustPressed(pixelgl.Key1) { 230 | mode = 1 231 | } 232 | 233 | if win.JustPressed(pixelgl.Key2) { 234 | mode = 2 235 | } 236 | 237 | if win.JustPressed(pixelgl.Key3) { 238 | mode = 3 239 | } 240 | 241 | if win.JustPressed(pixelgl.KeyUp) { 242 | mode += 1 243 | 244 | if mode > 2 { 245 | mode = 0 246 | } 247 | } 248 | 249 | if win.JustPressed(pixelgl.KeyDown) { 250 | mode -= 1 251 | 252 | if mode < 0 { 253 | mode = 2 254 | } 255 | } 256 | 257 | if win.Pressed(pixelgl.KeyLeft) { 258 | scale += 0.01 259 | expand = true 260 | } 261 | 262 | if win.Pressed(pixelgl.KeyRight) { 263 | scale -= 0.01 264 | expand = false 265 | } 266 | 267 | if win.JustPressed(pixelgl.KeyL) { 268 | log.Info(). 269 | Int("mode", mode). 270 | Float64("scale", scale). 271 | Msg("State") 272 | } 273 | 274 | render(win, canvas) 275 | 276 | win.Update() 277 | } 278 | } 279 | 280 | func loadImage(fn string) (*image.RGBA, error) { 281 | f, err := os.Open(fn) 282 | if err != nil { 283 | return nil, err 284 | } 285 | 286 | m, _, err := image.Decode(f) 287 | if err != nil { 288 | return nil, err 289 | } 290 | 291 | if rgba, ok := m.(*image.RGBA); ok { 292 | return rgba, nil 293 | } 294 | 295 | b := m.Bounds() 296 | 297 | rgba := image.NewRGBA(b) 298 | 299 | for y := b.Min.Y; y < b.Max.Y; y++ { 300 | for x := b.Min.X; x < b.Max.X; x++ { 301 | rgba.Set(x, y, m.At(x, y)) 302 | } 303 | } 304 | 305 | return rgba, nil 306 | } 307 | 308 | var ( 309 | mode int 310 | scale float64 311 | expand bool 312 | 313 | w, h int 314 | fw, fh float64 315 | 316 | mouse pixel.Vec 317 | 318 | delay time.Duration 319 | 320 | noise *opensimplex.Noise 321 | 322 | source *image.RGBA 323 | target *image.RGBA 324 | 325 | bounds pixel.Rect 326 | matrix pixel.Matrix 327 | 328 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 329 | ) 330 | -------------------------------------------------------------------------------- /warping/steps/step05.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern3(p pixel.Vec) (float64, pixel.Vec, pixel.Vec) { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(5.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))), q, r 36 | } 37 | 38 | func pattern(p pixel.Vec) float64 { 39 | q := pixel.V( 40 | fbm(p.Add(pixel.V(0.0, 0.0))), 41 | fbm(p.Add(pixel.V(5.2, 1.3))), 42 | ) 43 | 44 | r := pixel.V( 45 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 46 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 47 | ) 48 | 49 | return fbm(p.Add(r.Scaled(scale))) 50 | } 51 | 52 | func fbm(p pixel.Vec) float64 { 53 | return noise.Eval2(p.X, p.Y) 54 | } 55 | 56 | func drawFrame() { 57 | frame := image.NewRGBA(source.Bounds()) 58 | 59 | for x := 0; x < w; x++ { 60 | for y := 0; y < h; y++ { 61 | fx, fy := float64(x), float64(y) 62 | 63 | v, q, k := pattern3(pixel.V( 64 | float64(x)*0.0012, 65 | float64(y)*0.002, 66 | )) 67 | 68 | if v < 0 { 69 | v = -v 70 | } 71 | 72 | wx, wy := int(fx*k.X)%w, int(fy*k.Y)%h 73 | 74 | if wx < 0 || wx > w { 75 | wx = int(fx - k.X) 76 | } 77 | 78 | if wy < 0 || wy > h { 79 | wy = int(fy - k.Y) 80 | } 81 | 82 | c := source.At(wx, wy).(color.RGBA) 83 | 84 | var r, g, b uint8 85 | 86 | switch mode { 87 | case 1: 88 | uv := uint8(int(v*255) % 255) 89 | 90 | if uv < c.R { 91 | r = c.R - uv + uint8(q.X*fw) 92 | } 93 | 94 | if uv < c.G { 95 | g = c.G - uv + uint8(q.X*fw) 96 | } 97 | 98 | if uv < c.B { 99 | b = c.B - uv + uint8(q.X*fw) 100 | } 101 | case 2: 102 | uv := uint8(int(v*255) % 20) 103 | 104 | if nr := (255 - c.R) + uv; nr < 255 { 105 | r = uv + c.R 106 | } 107 | 108 | if ng := (255 - c.G) + uv; ng < 255 { 109 | g = uv + c.G 110 | } 111 | 112 | if nb := (255 - c.B) + uv; nb < 255 { 113 | b = uv + c.B 114 | } 115 | default: 116 | r, g, b = c.R, c.G, c.B 117 | } 118 | 119 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 120 | } 121 | } 122 | 123 | target.Pix = frame.Pix 124 | } 125 | 126 | func update(ticker *time.Ticker) { 127 | drawFrame() 128 | 129 | for range ticker.C { 130 | if expand { 131 | scale += 0.0025 132 | 133 | if scale > 100 { 134 | expand = false 135 | } 136 | } else { 137 | scale -= 0.0025 138 | 139 | if scale < -100 { 140 | expand = true 141 | } 142 | } 143 | 144 | drawFrame() 145 | } 146 | } 147 | 148 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 149 | canvas.SetPixels(target.Pix) 150 | 151 | canvas.Draw(win, matrix) 152 | } 153 | 154 | func main() { 155 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 156 | 157 | var ( 158 | fn string 159 | seed int64 160 | d time.Duration 161 | ) 162 | 163 | flag.StringVar(&fn, "image", "../flower.png", "image") 164 | flag.Float64Var(&scale, "scale", 0.8199000000003624, "scale") 165 | flag.Int64Var(&seed, "seed", 120, "seed") 166 | flag.IntVar(&mode, "mode", 0, "mode") 167 | flag.DurationVar(&d, "delay", 8*time.Millisecond, "delay") 168 | 169 | flag.Parse() 170 | 171 | if d <= 0 { 172 | d = 1 * time.Millisecond 173 | } 174 | 175 | if err := setup(fn, seed, d); err != nil { 176 | log.Fatal().Err(err).Msg("setup") 177 | } 178 | 179 | pixelgl.Run(run) 180 | } 181 | 182 | func setup(fn string, seed int64, d time.Duration) error { 183 | delay = d 184 | noise = opensimplex.NewWithSeed(seed) 185 | 186 | m, err := loadImage(fn) 187 | if err != nil { 188 | return err 189 | } 190 | 191 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 192 | fw, fh = float64(w), float64(h) 193 | 194 | source = m 195 | target = image.NewRGBA(source.Bounds()) 196 | bounds = pixel.R(0, 0, float64(w), float64(w)) 197 | matrix = flipY.Moved(bounds.Center()) 198 | 199 | return nil 200 | } 201 | 202 | func run() { 203 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 204 | Title: "Domain Warping", 205 | Bounds: bounds, 206 | Resizable: false, 207 | VSync: true, 208 | Undecorated: true, 209 | }) 210 | if err != nil { 211 | panic(err) 212 | } 213 | 214 | canvas := pixelgl.NewCanvas(bounds) 215 | 216 | canvas.SetComposeMethod(pixel.ComposeXor) 217 | 218 | ticker := time.NewTicker(delay) 219 | 220 | defer ticker.Stop() 221 | 222 | go update(ticker) 223 | 224 | for !win.Closed() { 225 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 226 | 227 | mouse = win.MousePosition() 228 | 229 | if win.JustPressed(pixelgl.Key0) { 230 | scale = 0 231 | } 232 | 233 | if win.JustPressed(pixelgl.Key1) { 234 | mode = 1 235 | } 236 | 237 | if win.JustPressed(pixelgl.Key2) { 238 | mode = 2 239 | } 240 | 241 | if win.JustPressed(pixelgl.Key3) { 242 | mode = 3 243 | } 244 | 245 | if win.JustPressed(pixelgl.KeyUp) { 246 | mode += 1 247 | 248 | if mode > 2 { 249 | mode = 0 250 | } 251 | } 252 | 253 | if win.JustPressed(pixelgl.KeyDown) { 254 | mode -= 1 255 | 256 | if mode < 0 { 257 | mode = 2 258 | } 259 | } 260 | 261 | if win.Pressed(pixelgl.KeyLeft) { 262 | scale += 0.01 263 | expand = true 264 | } 265 | 266 | if win.Pressed(pixelgl.KeyRight) { 267 | scale -= 0.01 268 | expand = false 269 | } 270 | 271 | if win.JustPressed(pixelgl.KeyL) { 272 | log.Info(). 273 | Int("mode", mode). 274 | Float64("scale", scale). 275 | Msg("State") 276 | } 277 | 278 | render(win, canvas) 279 | 280 | win.Update() 281 | } 282 | } 283 | 284 | func loadImage(fn string) (*image.RGBA, error) { 285 | f, err := os.Open(fn) 286 | if err != nil { 287 | return nil, err 288 | } 289 | 290 | m, _, err := image.Decode(f) 291 | if err != nil { 292 | return nil, err 293 | } 294 | 295 | if rgba, ok := m.(*image.RGBA); ok { 296 | return rgba, nil 297 | } 298 | 299 | b := m.Bounds() 300 | 301 | rgba := image.NewRGBA(b) 302 | 303 | for y := b.Min.Y; y < b.Max.Y; y++ { 304 | for x := b.Min.X; x < b.Max.X; x++ { 305 | rgba.Set(x, y, m.At(x, y)) 306 | } 307 | } 308 | 309 | return rgba, nil 310 | } 311 | 312 | var ( 313 | mode int 314 | scale float64 315 | expand bool 316 | 317 | w, h int 318 | fw, fh float64 319 | 320 | mouse pixel.Vec 321 | 322 | delay time.Duration 323 | 324 | noise *opensimplex.Noise 325 | 326 | source *image.RGBA 327 | target *image.RGBA 328 | 329 | bounds pixel.Rect 330 | matrix pixel.Matrix 331 | 332 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 333 | ) 334 | -------------------------------------------------------------------------------- /warping/steps/step06.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern3(p pixel.Vec) (float64, pixel.Vec, pixel.Vec) { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(15.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))), q, r 36 | } 37 | 38 | func pattern(p pixel.Vec) float64 { 39 | q := pixel.V( 40 | fbm(p.Add(pixel.V(0.0, 0.0))), 41 | fbm(p.Add(pixel.V(5.2, 1.3))), 42 | ) 43 | 44 | r := pixel.V( 45 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 46 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 47 | ) 48 | 49 | return fbm(p.Add(r.Scaled(scale))) 50 | } 51 | 52 | func fbm(p pixel.Vec) float64 { 53 | return noise.Eval2(p.X, p.Y) 54 | } 55 | 56 | func drawFrame() { 57 | frame := image.NewRGBA(source.Bounds()) 58 | 59 | for x := 0; x < w; x++ { 60 | for y := 0; y < h; y++ { 61 | fx, fy := float64(x), float64(y) 62 | 63 | v, q, k := pattern3(pixel.V( 64 | float64(x)*0.0012, 65 | float64(y)*0.002, 66 | )) 67 | 68 | if v < 0 { 69 | v = -v 70 | } 71 | 72 | wx, wy := int(fx*k.X)%w, int(fy*k.Y)%h 73 | 74 | if wx < 0 || wx > w { 75 | wx = int(fx - k.X) 76 | } 77 | 78 | if wy < 0 || wy > h { 79 | wy = int(fy - k.Y) 80 | } 81 | 82 | c := source.At(wx, wy).(color.RGBA) 83 | 84 | var r, g, b uint8 85 | 86 | switch mode { 87 | case 1: 88 | uv := uint8(int(v*255) % 255) 89 | 90 | if uv > 100 && uv < c.R { 91 | r = c.R - uv + uint8(q.X*fw) 92 | } else { 93 | r = (c.R * 2) % 255 94 | } 95 | 96 | if uv > 100 && uv < c.G { 97 | g = c.G - uv + uint8(q.X*fw) 98 | } else { 99 | g = (c.G * 2) % 255 100 | } 101 | 102 | if uv > 100 && uv < c.B { 103 | b = c.B - uv + uint8(q.X*fw) 104 | } else { 105 | b = (c.B * 2) % 255 106 | } 107 | case 2: 108 | uv := uint8(int(v*250) % 254) 109 | 110 | if nr := (255 - c.R) + uv; nr < 155 { 111 | r = uv 112 | } 113 | 114 | if ng := (255 - c.G) + uv; ng < 255 { 115 | g = c.G 116 | } 117 | case 3: 118 | uv := uint8(int(v*250) % 254) 119 | 120 | if nb := (255 - c.B) + uv; nb < 255 { 121 | b = uv 122 | } 123 | default: 124 | r, g, b = c.R, c.G, c.B 125 | } 126 | 127 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 128 | } 129 | } 130 | 131 | target.Pix = frame.Pix 132 | } 133 | 134 | func update(ticker *time.Ticker) { 135 | drawFrame() 136 | 137 | for range ticker.C { 138 | if expand { 139 | scale += 0.0025 140 | 141 | if scale > 100 { 142 | expand = false 143 | } 144 | } else { 145 | scale -= 0.0025 146 | 147 | if scale < -100 { 148 | expand = true 149 | } 150 | } 151 | 152 | drawFrame() 153 | } 154 | } 155 | 156 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 157 | canvas.SetPixels(target.Pix) 158 | 159 | canvas.Draw(win, matrix) 160 | } 161 | 162 | func main() { 163 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 164 | 165 | var ( 166 | fn string 167 | seed int64 168 | d time.Duration 169 | ) 170 | 171 | flag.StringVar(&fn, "image", "../flower.png", "image") 172 | flag.Float64Var(&scale, "scale", 7.847599999999528, "scale") 173 | flag.Int64Var(&seed, "seed", 1, "seed") 174 | flag.IntVar(&mode, "mode", 0, "mode") 175 | flag.DurationVar(&d, "delay", 8*time.Millisecond, "delay") 176 | 177 | flag.Parse() 178 | 179 | if d <= 0 { 180 | d = 1 * time.Millisecond 181 | } 182 | 183 | if err := setup(fn, seed, d); err != nil { 184 | log.Fatal().Err(err).Msg("setup") 185 | } 186 | 187 | pixelgl.Run(run) 188 | } 189 | 190 | func setup(fn string, seed int64, d time.Duration) error { 191 | delay = d 192 | noise = opensimplex.NewWithSeed(seed) 193 | 194 | m, err := loadImage(fn) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 200 | fw, fh = float64(w), float64(h) 201 | 202 | source = m 203 | target = image.NewRGBA(source.Bounds()) 204 | bounds = pixel.R(0, 0, float64(w), float64(h)) 205 | matrix = flipY.Moved(bounds.Center()) 206 | 207 | input = make(chan pixelgl.Button, 1) 208 | 209 | return nil 210 | } 211 | 212 | func run() { 213 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 214 | Title: "Domain Warping", 215 | Bounds: bounds, 216 | Resizable: false, 217 | VSync: true, 218 | Undecorated: true, 219 | }) 220 | if err != nil { 221 | panic(err) 222 | } 223 | 224 | canvas := pixelgl.NewCanvas(bounds) 225 | 226 | ticker := time.NewTicker(delay) 227 | defer ticker.Stop() 228 | 229 | go update(ticker) 230 | go handleInput() 231 | 232 | for !win.Closed() { 233 | mouse = win.MousePosition() 234 | 235 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 236 | 237 | for _, button := range pressedButtons { 238 | if win.Pressed(button) { 239 | input <- button 240 | } 241 | } 242 | 243 | for _, button := range justPressedButtons { 244 | if win.JustPressed(button) { 245 | input <- button 246 | } 247 | } 248 | 249 | render(win, canvas) 250 | 251 | win.Update() 252 | } 253 | } 254 | 255 | func handleInput() { 256 | for button := range input { 257 | switch button { 258 | case pixelgl.Key0: 259 | mode = 0 260 | case pixelgl.Key1: 261 | mode = 1 262 | case pixelgl.Key2: 263 | mode = 2 264 | case pixelgl.Key3: 265 | mode = 3 266 | case pixelgl.Key4: 267 | mode = 4 268 | case pixelgl.Key5: 269 | mode = 5 270 | case pixelgl.Key6: 271 | mode = 6 272 | case pixelgl.Key7: 273 | mode = 7 274 | case pixelgl.Key8: 275 | mode = 8 276 | case pixelgl.Key9: 277 | mode = 9 278 | case pixelgl.KeyUp: 279 | mode += 1 280 | 281 | if mode > 9 { 282 | mode = 0 283 | } 284 | case pixelgl.KeyDown: 285 | mode -= 1 286 | 287 | if mode < 0 { 288 | mode = 9 289 | } 290 | case pixelgl.KeyLeft: 291 | scale += 0.01 292 | expand = true 293 | case pixelgl.KeyRight: 294 | scale -= 0.01 295 | expand = false 296 | } 297 | 298 | logState() 299 | } 300 | } 301 | 302 | var pressedButtons = []pixelgl.Button{ 303 | pixelgl.KeyLeft, 304 | pixelgl.KeyRight, 305 | } 306 | 307 | var justPressedButtons = []pixelgl.Button{ 308 | pixelgl.Key0, 309 | pixelgl.Key1, 310 | pixelgl.Key2, 311 | pixelgl.Key3, 312 | pixelgl.Key4, 313 | pixelgl.Key5, 314 | pixelgl.Key6, 315 | pixelgl.Key7, 316 | pixelgl.Key8, 317 | pixelgl.Key9, 318 | pixelgl.KeyUp, 319 | pixelgl.KeyDown, 320 | pixelgl.KeyL, 321 | } 322 | 323 | func logState() { 324 | log.Info(). 325 | Int("mode", mode). 326 | Float64("scale", scale). 327 | Msg("State") 328 | } 329 | 330 | func loadImage(fn string) (*image.RGBA, error) { 331 | f, err := os.Open(fn) 332 | if err != nil { 333 | return nil, err 334 | } 335 | 336 | m, _, err := image.Decode(f) 337 | if err != nil { 338 | return nil, err 339 | } 340 | 341 | if rgba, ok := m.(*image.RGBA); ok { 342 | return rgba, nil 343 | } 344 | 345 | b := m.Bounds() 346 | 347 | rgba := image.NewRGBA(b) 348 | 349 | for y := b.Min.Y; y < b.Max.Y; y++ { 350 | for x := b.Min.X; x < b.Max.X; x++ { 351 | rgba.Set(x, y, m.At(x, y)) 352 | } 353 | } 354 | 355 | return rgba, nil 356 | } 357 | 358 | var ( 359 | mode int 360 | scale float64 361 | expand bool 362 | 363 | w, h int 364 | fw, fh float64 365 | 366 | mouse pixel.Vec 367 | 368 | input chan pixelgl.Button 369 | 370 | delay time.Duration 371 | 372 | noise *opensimplex.Noise 373 | 374 | source *image.RGBA 375 | target *image.RGBA 376 | 377 | bounds pixel.Rect 378 | matrix pixel.Matrix 379 | 380 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 381 | ) 382 | -------------------------------------------------------------------------------- /warping/variations/good-looking-offset-rendering.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern3(p pixel.Vec) (float64, pixel.Vec, pixel.Vec) { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(5.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))), q, r 36 | } 37 | 38 | func pattern(p pixel.Vec) float64 { 39 | q := pixel.V( 40 | fbm(p.Add(pixel.V(0.0, 0.0))), 41 | fbm(p.Add(pixel.V(5.2, 1.3))), 42 | ) 43 | 44 | r := pixel.V( 45 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 46 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 47 | ) 48 | 49 | return fbm(p.Add(r.Scaled(scale))) 50 | } 51 | 52 | func fbm(p pixel.Vec) float64 { 53 | return noise.Eval2(p.X, p.Y) 54 | } 55 | 56 | func update(ticker *time.Ticker) { 57 | 58 | for range ticker.C { 59 | if expand { 60 | scale += 0.005 61 | 62 | if scale > 100 { 63 | expand = false 64 | } 65 | } else { 66 | scale -= 0.005 67 | 68 | if scale < -100 { 69 | expand = true 70 | } 71 | } 72 | 73 | frame := image.NewRGBA(source.Bounds()) 74 | 75 | for x := 0; x < w; x++ { 76 | for y := 0; y < h; y++ { 77 | fx, fy := float64(x), float64(y) 78 | 79 | v, q, k := pattern3(pixel.V( 80 | float64(x)*0.0012, 81 | float64(y)*0.002, 82 | )) 83 | 84 | if v < 0 { 85 | v = -v 86 | } 87 | 88 | wx, wy := int(fx*k.X)%w, int(fy*k.Y)%h 89 | 90 | if wx < 0 || wx > w { 91 | wx = int(fx - k.X) 92 | } 93 | 94 | if wy < 0 || wy > h { 95 | wy = int(fy - k.Y) 96 | } 97 | 98 | c := source.At(wx, wy).(color.RGBA) 99 | 100 | var r, g, b uint8 101 | 102 | switch mode { 103 | case 1: 104 | uv := uint8(int(v*255) % 255) 105 | 106 | if uv < c.R { 107 | r = c.R - uv + uint8(q.X*fw) 108 | } 109 | 110 | if uv < c.G { 111 | g = c.G - uv + uint8(q.X*fw) 112 | } 113 | 114 | if uv < c.B { 115 | b = c.B - uv + uint8(q.X*fw) 116 | } 117 | case 2: 118 | uv := uint8(int(v*2) % 255) 119 | 120 | if nr := (255 - c.R) + uv; nr < 255 { 121 | r = nr + c.R 122 | } 123 | 124 | if ng := (255 - c.G) + uv; ng < 255 { 125 | g = ng + c.G 126 | } 127 | 128 | if nb := (255 - c.B) + uv; nb < 255 { 129 | b = nb + c.B 130 | } 131 | default: 132 | r, g, b = c.R, c.G, c.B 133 | } 134 | 135 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 136 | } 137 | } 138 | 139 | target.Pix = frame.Pix 140 | } 141 | } 142 | 143 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 144 | canvas.SetPixels(target.Pix) 145 | 146 | canvas.Draw(win, matrix) 147 | } 148 | 149 | func main() { 150 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 151 | 152 | var ( 153 | fn string 154 | seed int64 155 | d time.Duration 156 | ) 157 | 158 | flag.StringVar(&fn, "i", "../smokestack.jpg", "image") 159 | flag.Float64Var(&scale, "scale", 0.8449000000003625, "scale") 160 | flag.Int64Var(&seed, "seed", 420, "seed") 161 | flag.IntVar(&mode, "mode", 0, "mode") 162 | flag.DurationVar(&d, "d", 8*time.Millisecond, "delay") 163 | 164 | flag.Parse() 165 | 166 | if d <= 0 { 167 | d = 1 * time.Millisecond 168 | } 169 | 170 | if err := setup(fn, seed, d); err != nil { 171 | log.Fatal().Err(err).Msg("Error") 172 | } 173 | 174 | pixelgl.Run(run) 175 | } 176 | 177 | func setup(fn string, seed int64, d time.Duration) error { 178 | delay = d 179 | noise = opensimplex.NewWithSeed(seed) 180 | 181 | m, err := loadImage(fn) 182 | if err != nil { 183 | return err 184 | } 185 | 186 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 187 | fw, fh = float64(w), float64(h) 188 | 189 | source = m 190 | target = image.NewRGBA(source.Bounds()) 191 | bounds = pixel.R(0, 0, float64(w), float64(w)) 192 | matrix = flipY.Moved(bounds.Center()) 193 | 194 | return nil 195 | } 196 | 197 | func run() { 198 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 199 | Title: "Domain Warping", 200 | Bounds: bounds, 201 | Resizable: false, 202 | VSync: true, 203 | Undecorated: true, 204 | }) 205 | if err != nil { 206 | panic(err) 207 | } 208 | 209 | canvas := pixelgl.NewCanvas(bounds) 210 | 211 | canvas.SetComposeMethod(pixel.ComposeXor) 212 | 213 | ticker := time.NewTicker(delay) 214 | 215 | defer ticker.Stop() 216 | 217 | go update(ticker) 218 | 219 | for !win.Closed() { 220 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 221 | 222 | mouse = win.MousePosition() 223 | 224 | if win.JustPressed(pixelgl.Key0) { 225 | scale = 0 226 | } 227 | 228 | if win.JustPressed(pixelgl.Key1) { 229 | mode = 1 230 | } 231 | 232 | if win.JustPressed(pixelgl.Key2) { 233 | mode = 2 234 | } 235 | 236 | if win.JustPressed(pixelgl.Key3) { 237 | mode = 3 238 | } 239 | 240 | if win.JustPressed(pixelgl.KeyUp) { 241 | mode += 1 242 | 243 | if mode > 2 { 244 | mode = 0 245 | } 246 | } 247 | 248 | if win.JustPressed(pixelgl.KeyDown) { 249 | mode -= 1 250 | 251 | if mode < 0 { 252 | mode = 2 253 | } 254 | } 255 | 256 | if win.Pressed(pixelgl.KeyLeft) { 257 | scale += 0.01 258 | expand = true 259 | } 260 | 261 | if win.Pressed(pixelgl.KeyRight) { 262 | scale -= 0.01 263 | expand = false 264 | } 265 | 266 | if win.JustPressed(pixelgl.KeyL) { 267 | log.Info(). 268 | Int("mode", mode). 269 | Float64("scale", scale). 270 | Msg("State") 271 | } 272 | 273 | render(win, canvas) 274 | 275 | win.Update() 276 | } 277 | } 278 | 279 | func loadImage(fn string) (*image.RGBA, error) { 280 | f, err := os.Open(fn) 281 | if err != nil { 282 | return nil, err 283 | } 284 | 285 | m, _, err := image.Decode(f) 286 | if err != nil { 287 | return nil, err 288 | } 289 | 290 | if rgba, ok := m.(*image.RGBA); ok { 291 | return rgba, nil 292 | } 293 | 294 | b := m.Bounds() 295 | 296 | rgba := image.NewRGBA(b) 297 | 298 | for y := b.Min.Y; y < b.Max.Y; y++ { 299 | for x := b.Min.X; x < b.Max.X; x++ { 300 | rgba.Set(x, y, m.At(x, y)) 301 | } 302 | } 303 | 304 | return rgba, nil 305 | } 306 | 307 | var ( 308 | mode int 309 | scale float64 310 | expand bool 311 | 312 | w, h int 313 | fw, fh float64 314 | 315 | mouse pixel.Vec 316 | 317 | delay time.Duration 318 | 319 | noise *opensimplex.Noise 320 | 321 | source *image.RGBA 322 | target *image.RGBA 323 | 324 | bounds pixel.Rect 325 | matrix pixel.Matrix 326 | 327 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 328 | ) 329 | -------------------------------------------------------------------------------- /warping/variations/yet-another-good-looking-offset-rendering-2.go: -------------------------------------------------------------------------------- 1 | // Inspired by this article 2 | // http://www.iquilezles.org/www/articles/warp/warp.htm 3 | 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "image" 9 | "image/color" 10 | "os" 11 | "time" 12 | 13 | _ "image/gif" 14 | _ "image/jpeg" 15 | _ "image/png" 16 | 17 | pixel "github.com/faiface/pixel" 18 | pixelgl "github.com/faiface/pixel/pixelgl" 19 | opensimplex "github.com/ojrac/opensimplex-go" 20 | zerolog "github.com/rs/zerolog" 21 | log "github.com/rs/zerolog/log" 22 | ) 23 | 24 | func pattern3(p pixel.Vec) (float64, pixel.Vec, pixel.Vec) { 25 | q := pixel.V( 26 | fbm(p.Add(pixel.V(0.0, 0.0))), 27 | fbm(p.Add(pixel.V(5.2, 1.3))), 28 | ) 29 | 30 | r := pixel.V( 31 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 32 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 33 | ) 34 | 35 | return fbm(p.Add(r.Scaled(scale))), q, r 36 | } 37 | 38 | func pattern(p pixel.Vec) float64 { 39 | q := pixel.V( 40 | fbm(p.Add(pixel.V(0.0, 0.0))), 41 | fbm(p.Add(pixel.V(5.2, 1.3))), 42 | ) 43 | 44 | r := pixel.V( 45 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(1.7, 9.2)))), 46 | fbm(p.Add(q.Scaled(scale).Add(pixel.V(8.3, 2.8)))), 47 | ) 48 | 49 | return fbm(p.Add(r.Scaled(scale))) 50 | } 51 | 52 | func fbm(p pixel.Vec) float64 { 53 | return noise.Eval2(p.X, p.Y) 54 | } 55 | 56 | func drawFrame() { 57 | frame := image.NewRGBA(source.Bounds()) 58 | 59 | for x := 0; x < w; x++ { 60 | for y := 0; y < h; y++ { 61 | fx, fy := float64(x), float64(y) 62 | 63 | v, q, k := pattern3(pixel.V( 64 | float64(x)*0.0012, 65 | float64(y)*0.002, 66 | )) 67 | 68 | if v < 0 { 69 | v = -v 70 | } 71 | 72 | wx, wy := int(fx*k.X)%w, int(fy*k.Y)%h 73 | 74 | if wx < 0 || wx > w { 75 | wx = int(fx - k.X) 76 | } 77 | 78 | if wy < 0 || wy > h { 79 | wy = int(fy - k.Y) 80 | } 81 | 82 | c := source.At(wx, wy).(color.RGBA) 83 | 84 | var r, g, b uint8 85 | 86 | switch mode { 87 | case 1: 88 | uv := uint8(int(v*255) % 255) 89 | 90 | if uv < c.R { 91 | r = c.R - uv + uint8(q.X*fw) 92 | } 93 | 94 | if uv < c.G { 95 | g = c.G - uv + uint8(q.X*fw) 96 | } 97 | 98 | if uv < c.B { 99 | b = c.B - uv + uint8(q.X*fw) 100 | } 101 | case 2: 102 | uv := uint8(int(v*255) % 20) 103 | 104 | if nr := (255 - c.R) + uv; nr < 255 { 105 | r = uv + c.R 106 | } 107 | 108 | if ng := (255 - c.G) + uv; ng < 255 { 109 | g = uv + c.G 110 | } 111 | 112 | if nb := (255 - c.B) + uv; nb < 255 { 113 | b = uv + c.B 114 | } 115 | default: 116 | r, g, b = c.R, c.G, c.B 117 | } 118 | 119 | frame.SetRGBA(x, y, color.RGBA{r, g, b, 255}) 120 | } 121 | } 122 | 123 | target.Pix = frame.Pix 124 | } 125 | 126 | func update(ticker *time.Ticker) { 127 | drawFrame() 128 | 129 | for range ticker.C { 130 | if expand { 131 | scale += 0.0025 132 | 133 | if scale > 100 { 134 | expand = false 135 | } 136 | } else { 137 | scale -= 0.0025 138 | 139 | if scale < -100 { 140 | expand = true 141 | } 142 | } 143 | 144 | drawFrame() 145 | } 146 | } 147 | 148 | func render(win *pixelgl.Window, canvas *pixelgl.Canvas) { 149 | canvas.SetPixels(target.Pix) 150 | canvas.Draw(win, matrix) 151 | } 152 | 153 | func main() { 154 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) 155 | 156 | var ( 157 | fn string 158 | seed int64 159 | d time.Duration 160 | ) 161 | 162 | flag.StringVar(&fn, "image", "../flower.png", "image") 163 | flag.Float64Var(&scale, "scale", 0.8199000000003624, "scale") 164 | flag.Int64Var(&seed, "seed", 120, "seed") 165 | flag.IntVar(&mode, "mode", 0, "mode") 166 | flag.DurationVar(&d, "delay", 8*time.Millisecond, "delay") 167 | 168 | flag.Parse() 169 | 170 | if d <= 0 { 171 | d = 1 * time.Millisecond 172 | } 173 | 174 | if err := setup(fn, seed, d); err != nil { 175 | log.Fatal().Err(err).Msg("Error") 176 | } 177 | 178 | pixelgl.Run(run) 179 | } 180 | 181 | func setup(fn string, seed int64, d time.Duration) error { 182 | delay = d 183 | noise = opensimplex.NewWithSeed(seed) 184 | 185 | m, err := loadImage(fn) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | w, h = m.Bounds().Dx(), m.Bounds().Dy() 191 | fw, fh = float64(w), float64(h) 192 | 193 | source = m 194 | target = image.NewRGBA(source.Bounds()) 195 | bounds = pixel.R(0, 0, float64(w), float64(w)) 196 | matrix = flipY.Moved(bounds.Center()) 197 | 198 | return nil 199 | } 200 | 201 | func run() { 202 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 203 | Title: "Domain Warping", 204 | Bounds: bounds, 205 | Resizable: false, 206 | VSync: true, 207 | Undecorated: true, 208 | }) 209 | if err != nil { 210 | panic(err) 211 | } 212 | 213 | canvas := pixelgl.NewCanvas(bounds) 214 | 215 | canvas.SetComposeMethod(pixel.ComposeXor) 216 | 217 | ticker := time.NewTicker(delay) 218 | 219 | defer ticker.Stop() 220 | 221 | go update(ticker) 222 | 223 | for !win.Closed() { 224 | win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) 225 | 226 | mouse = win.MousePosition() 227 | 228 | if win.JustPressed(pixelgl.Key0) { 229 | scale = 0 230 | } 231 | 232 | if win.JustPressed(pixelgl.Key1) { 233 | mode = 1 234 | } 235 | 236 | if win.JustPressed(pixelgl.Key2) { 237 | mode = 2 238 | } 239 | 240 | if win.JustPressed(pixelgl.Key3) { 241 | mode = 3 242 | } 243 | 244 | if win.JustPressed(pixelgl.KeyUp) { 245 | mode += 1 246 | 247 | if mode > 2 { 248 | mode = 0 249 | } 250 | } 251 | 252 | if win.JustPressed(pixelgl.KeyDown) { 253 | mode -= 1 254 | 255 | if mode < 0 { 256 | mode = 2 257 | } 258 | } 259 | 260 | if win.Pressed(pixelgl.KeyLeft) { 261 | scale += 0.01 262 | expand = true 263 | } 264 | 265 | if win.Pressed(pixelgl.KeyRight) { 266 | scale -= 0.01 267 | expand = false 268 | } 269 | 270 | if win.JustPressed(pixelgl.KeyL) { 271 | log.Info(). 272 | Int("mode", mode). 273 | Float64("scale", scale). 274 | Msg("State") 275 | } 276 | 277 | render(win, canvas) 278 | 279 | win.Update() 280 | } 281 | } 282 | 283 | func loadImage(fn string) (*image.RGBA, error) { 284 | f, err := os.Open(fn) 285 | if err != nil { 286 | return nil, err 287 | } 288 | 289 | m, _, err := image.Decode(f) 290 | if err != nil { 291 | return nil, err 292 | } 293 | 294 | if rgba, ok := m.(*image.RGBA); ok { 295 | return rgba, nil 296 | } 297 | 298 | b := m.Bounds() 299 | 300 | rgba := image.NewRGBA(b) 301 | 302 | for y := b.Min.Y; y < b.Max.Y; y++ { 303 | for x := b.Min.X; x < b.Max.X; x++ { 304 | rgba.Set(x, y, m.At(x, y)) 305 | } 306 | } 307 | 308 | return rgba, nil 309 | } 310 | 311 | var ( 312 | mode int 313 | scale float64 314 | expand bool 315 | 316 | w, h int 317 | fw, fh float64 318 | 319 | mouse pixel.Vec 320 | 321 | delay time.Duration 322 | 323 | noise *opensimplex.Noise 324 | 325 | source *image.RGBA 326 | target *image.RGBA 327 | 328 | bounds pixel.Rect 329 | matrix pixel.Matrix 330 | 331 | flipY = pixel.IM.ScaledXY(pixel.ZV, pixel.V(1, -1)) 332 | ) 333 | -------------------------------------------------------------------------------- /water-ripple/README.md: -------------------------------------------------------------------------------- 1 | # pixel-experiments/water-ripple 2 | 3 | https://gist.github.com/peterhellberg/9e672d7e2e210d0e713f1d6aaad95570 4 | 5 | ![water-ripple](https://user-images.githubusercontent.com/565124/50576119-09999500-0e0b-11e9-9cb9-793aeb29e571.png) 6 | 7 | Based on 8 | -------------------------------------------------------------------------------- /water-ripple/water-ripple.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "math/rand" 7 | "time" 8 | 9 | "github.com/faiface/pixel" 10 | "github.com/faiface/pixel/pixelgl" 11 | ) 12 | 13 | const ( 14 | width = 512 15 | height = 512 16 | size = width * (height + 2) * 2 17 | delay = 100 * time.Millisecond 18 | 19 | halfWidth = width >> 1 20 | halfHeight = height >> 1 21 | ) 22 | 23 | var ( 24 | oldIdx = width 25 | newIdx = width * (height + 3) 26 | rippleRad = 3 27 | 28 | texture = xorImage(width, height) 29 | ripple = xorImage(width, height) 30 | 31 | rippleMap = make([]int, size) 32 | lastMap = make([]int, size) 33 | ) 34 | 35 | func run() { 36 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 37 | Bounds: pixel.R(0, 0, float64(width), float64(height)), 38 | VSync: true, 39 | Undecorated: true, 40 | Resizable: false, 41 | }) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | ticker := time.NewTicker(delay) 47 | 48 | go func() { 49 | for range ticker.C { 50 | if rand.Intn(2) == 1 { 51 | dropAt(rand.Intn(width), rand.Intn(height)) 52 | } 53 | } 54 | }() 55 | 56 | c := win.Bounds().Center() 57 | 58 | for !win.Closed() { 59 | newRippleFrame() 60 | 61 | p := pixel.PictureDataFromImage(ripple) 62 | s := pixel.NewSprite(p, p.Bounds()) 63 | 64 | s.Draw(win, pixel.IM.Moved(c)) 65 | 66 | mouse := win.MousePosition() 67 | 68 | dropAt(int(mouse.X), int(height-mouse.Y)) 69 | 70 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 71 | return 72 | } 73 | 74 | win.Update() 75 | } 76 | } 77 | 78 | func main() { 79 | pixelgl.Run(run) 80 | } 81 | 82 | func dropAt(dx, dy int) { 83 | if dx > 1 && dy > 1 && dx < width && dy < height-rippleRad { 84 | for j := dy - rippleRad; j < dy+rippleRad; j++ { 85 | for k := dx - rippleRad; k < dx+rippleRad; k++ { 86 | rippleMap[oldIdx+(j*width)+k] += 512 87 | } 88 | } 89 | } 90 | } 91 | 92 | func newRippleFrame() { 93 | i := oldIdx 94 | 95 | oldIdx = newIdx 96 | newIdx = i 97 | 98 | i = 0 99 | mapIdx := oldIdx 100 | 101 | var data, oldData int 102 | 103 | for y := 0; y < height; y++ { 104 | for x := 0; x < width; x++ { 105 | data = (rippleMap[mapIdx-width] + 106 | rippleMap[mapIdx+width] + 107 | rippleMap[mapIdx-1] + 108 | rippleMap[mapIdx+1]) >> 1 109 | 110 | data -= rippleMap[newIdx+i] 111 | data -= data >> 5 112 | 113 | rippleMap[newIdx+i] = data 114 | 115 | data = 1024 - data 116 | 117 | oldData = lastMap[i] 118 | lastMap[i] = data 119 | 120 | if oldData != data { 121 | a := (((x - halfWidth) * data / 1024) << 0) + halfWidth 122 | b := (((y - halfHeight) * data / 1024) << 0) + halfHeight 123 | 124 | if a >= width { 125 | a = width - 1 126 | } 127 | 128 | if a < 0 { 129 | a = 0 130 | } 131 | 132 | if b >= height { 133 | b = height - 1 134 | } 135 | 136 | if b < 0 { 137 | b = 0 138 | } 139 | 140 | newPixel := (a + (b * width)) * 4 141 | curPixel := i * 4 142 | 143 | ripple.Pix[curPixel] = texture.Pix[newPixel] 144 | ripple.Pix[curPixel+1] = texture.Pix[newPixel+1] 145 | ripple.Pix[curPixel+2] = texture.Pix[newPixel+2] 146 | } 147 | 148 | mapIdx++ 149 | i++ 150 | } 151 | } 152 | } 153 | 154 | func xorImage(w, h int) *image.RGBA { 155 | m := image.NewRGBA(image.Rect(0, 0, w, h)) 156 | 157 | for x := 0; x < w; x++ { 158 | for y := 0; y < h; y++ { 159 | c := uint8(x ^ y) 160 | 161 | m.Set(x, y, color.RGBA{c, c % 192, c, 255}) 162 | } 163 | } 164 | 165 | return m 166 | } 167 | -------------------------------------------------------------------------------- /xor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | 7 | "github.com/faiface/pixel" 8 | "github.com/faiface/pixel/pixelgl" 9 | ) 10 | 11 | const ( 12 | width = 768 13 | height = 768 14 | size = 256 15 | ) 16 | 17 | func run() { 18 | scale := float64(height) / float64(size) 19 | 20 | win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ 21 | Bounds: pixel.R(0, 0, float64(width), float64(height)), 22 | VSync: true, 23 | Undecorated: true, 24 | Resizable: false, 25 | }) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | win.SetSmooth(false) 31 | 32 | c := win.Bounds().Center() 33 | p := xorPicture(size, size) 34 | s := pixel.NewSprite(p, p.Bounds()) 35 | 36 | for !win.Closed() { 37 | win.Update() 38 | 39 | s.Draw(win, pixel.IM.Moved(c).Scaled(c, scale)) 40 | 41 | if win.Pressed(pixelgl.KeyUp) { 42 | scale += 0.1 43 | } 44 | 45 | if win.Pressed(pixelgl.KeyDown) { 46 | scale -= 0.1 47 | } 48 | 49 | if win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ) { 50 | return 51 | } 52 | } 53 | } 54 | 55 | func xorPicture(w, h int) *pixel.PictureData { 56 | m := image.NewRGBA(image.Rect(0, 0, w, h)) 57 | 58 | for x := 0; x < w; x++ { 59 | for y := 0; y < h; y++ { 60 | c := uint8(x ^ y) 61 | 62 | m.Set(x, y, color.RGBA{c, c % 192, c, 255}) 63 | } 64 | } 65 | 66 | return pixel.PictureDataFromImage(m) 67 | } 68 | 69 | func main() { 70 | pixelgl.Run(run) 71 | } 72 | --------------------------------------------------------------------------------