From da55b9ddca557326b6e71bc2c82b0dc1a962e135 Mon Sep 17 00:00:00 2001 From: Peter Hellberg Date: Thu, 11 Jan 2018 22:42:04 +0100 Subject: [PATCH] Add bouncing experiment to community examples --- examples/community/bouncing/README.md | 16 ++ examples/community/bouncing/bouncing.go | 303 +++++++++++++++++++++ examples/community/bouncing/screenshot.png | Bin 0 -> 7056 bytes 3 files changed, 319 insertions(+) create mode 100644 examples/community/bouncing/README.md create mode 100644 examples/community/bouncing/bouncing.go create mode 100644 examples/community/bouncing/screenshot.png diff --git a/examples/community/bouncing/README.md b/examples/community/bouncing/README.md new file mode 100644 index 0000000..cb1c7f1 --- /dev/null +++ b/examples/community/bouncing/README.md @@ -0,0 +1,16 @@ +# bouncing + +Bouncing particles using the [imdraw](https://godoc.org/github.com/faiface/pixel/imdraw) package. + +Made by [Peter Hellberg](https://github.com/peterhellberg/) as part of his [pixel-experiments](https://github.com/peterhellberg/pixel-experiments) + +## Screenshots + +![bouncing animation](https://user-images.githubusercontent.com/565124/32401910-7cd87fb2-c119-11e7-8121-7fb46e5e11a8.gif) + +![bouncing screenshot](screenshot.png) + +## Links + + - https://github.com/peterhellberg/pixel-experiments/tree/master/bouncing + - https://gist.github.com/peterhellberg/674f32a15a7d2d249e634ce781f333e8 diff --git a/examples/community/bouncing/bouncing.go b/examples/community/bouncing/bouncing.go new file mode 100644 index 0000000..b2b43f2 --- /dev/null +++ b/examples/community/bouncing/bouncing.go @@ -0,0 +1,303 @@ +package main + +import ( + "image/color" + "math" + "math/rand" + "time" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" +) + +var ( + w, h, s, scale = float64(640), float64(360), float64(2.3), float64(32) + + p, bg = newPalette(Colors), color.RGBA{32, p.color().G, 32, 255} + + balls = []*ball{ + newRandomBall(scale), + newRandomBall(scale), + } +) + +func run() { + win, err := pixelgl.NewWindow(pixelgl.WindowConfig{ + Bounds: pixel.R(0, 0, w, h), + VSync: true, + Undecorated: true, + }) + if err != nil { + panic(err) + } + + imd := imdraw.New(nil) + + imd.EndShape = imdraw.RoundEndShape + imd.Precision = 3 + + go func() { + start := time.Now() + + for range time.Tick(16 * time.Millisecond) { + bg = color.RGBA{32 + (p.color().R/128)*4, 32 + (p.color().G/128)*4, 32 + (p.color().B/128)*4, 255} + s = pixel.V(math.Sin(time.Since(start).Seconds())*0.8, 0).Len()*2 - 1 + scale = 64 + 15*s + imd.Intensity = 1.2 * s + } + }() + + for !win.Closed() { + win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ)) + + if win.JustPressed(pixelgl.KeySpace) { + for _, ball := range balls { + ball.color = ball.palette.next() + } + } + + if win.JustPressed(pixelgl.KeyEnter) { + for _, ball := range balls { + ball.pos = center() + ball.vel = randomVelocity() + } + } + + imd.Clear() + + for _, ball := range balls { + imd.Color = ball.color + imd.Push(ball.pos) + } + + imd.Polygon(scale) + + for _, ball := range balls { + imd.Color = color.RGBA{ball.color.R, ball.color.G, ball.color.B, 128 - uint8(128*s)} + imd.Push(ball.pos) + } + + imd.Polygon(scale * s) + + for _, ball := range balls { + aliveParticles := []*particle{} + + for _, particle := range ball.particles { + if particle.life > 0 { + aliveParticles = append(aliveParticles, particle) + } + } + + for _, particle := range aliveParticles { + imd.Color = particle.color + imd.Push(particle.pos) + imd.Circle(16*particle.life, 0) + } + } + + win.Clear(bg) + imd.Draw(win) + win.Update() + } +} + +func main() { + rand.Seed(4) + + go func() { + for range time.Tick(32 * time.Millisecond) { + for _, ball := range balls { + go ball.update() + + for _, particle := range ball.particles { + go particle.update() + } + } + } + }() + + pixelgl.Run(run) +} + +func newParticleAt(pos, vel pixel.Vec) *particle { + c := p.color() + c.A = 5 + + return &particle{pos, vel, c, rand.Float64() * 1.5} +} + +func newRandomBall(radius float64) *ball { + return &ball{ + center(), randomVelocity(), + math.Pi * (radius * radius), + radius, p.random(), p, []*particle{}, + } +} + +func center() pixel.Vec { + return pixel.V(w/2, h/2) +} + +func randomVelocity() pixel.Vec { + return pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale / 4) +} + +type particle struct { + pos pixel.Vec + vel pixel.Vec + color color.RGBA + life float64 +} + +func (p *particle) update() { + p.pos = p.pos.Add(p.vel) + p.life -= 0.03 + + switch { + case p.pos.Y < 0 || p.pos.Y >= h: + p.vel.Y *= -1.0 + case p.pos.X < 0 || p.pos.X >= w: + p.vel.X *= -1.0 + } +} + +type ball struct { + pos pixel.Vec + vel pixel.Vec + mass float64 + radius float64 + color color.RGBA + palette *Palette + particles []*particle +} + +func (b *ball) update() { + b.pos = b.pos.Add(b.vel) + + var bounced bool + + switch { + case b.pos.Y <= b.radius || b.pos.Y >= h-b.radius: + b.vel.Y *= -1.0 + bounced = true + + if b.pos.Y < b.radius { + b.pos.Y = b.radius + } else { + b.pos.Y = h - b.radius + } + case b.pos.X <= b.radius || b.pos.X >= w-b.radius: + b.vel.X *= -1.0 + bounced = true + + if b.pos.X < b.radius { + b.pos.X = b.radius + } else { + b.pos.X = w - b.radius + } + } + + for _, a := range balls { + if a != b { + d := a.pos.Sub(b.pos) + + if d.Len() > a.radius+b.radius { + continue + } + + pen := d.Unit().Scaled(a.radius + b.radius - d.Len()) + + a.pos = a.pos.Add(pen.Scaled(b.mass / (a.mass + b.mass))) + b.pos = b.pos.Sub(pen.Scaled(a.mass / (a.mass + b.mass))) + + u := d.Unit() + v := 2 * (a.vel.Dot(u) - b.vel.Dot(u)) / (a.mass + b.mass) + + a.vel = a.vel.Sub(u.Scaled(v * b.mass)) + b.vel = b.vel.Add(u.Scaled(v * a.mass)) + + bounced = true + } + } + + if bounced { + b.color = p.next() + b.particles = append(b.particles, + newParticleAt(b.pos, b.vel.Rotated(1).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(2).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(3).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(4).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(5).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(6).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(7).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(8).Scaled(rand.Float64())), + newParticleAt(b.pos, b.vel.Rotated(9).Scaled(rand.Float64())), + + newParticleAt(b.pos, b.vel.Rotated(10).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(20).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(30).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(40).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(50).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(60).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(70).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(80).Scaled(rand.Float64()+1)), + newParticleAt(b.pos, b.vel.Rotated(90).Scaled(rand.Float64()+1)), + ) + } +} + +func newPalette(cc []color.Color) *Palette { + colors := []color.RGBA{} + + for _, v := range cc { + if c, ok := v.(color.RGBA); ok { + colors = append(colors, c) + } + } + + return &Palette{colors, len(colors), 0} +} + +type Palette struct { + colors []color.RGBA + size int + index int +} + +func (p *Palette) clone() *Palette { + return &Palette{p.colors, p.size, p.index} +} + +func (p *Palette) next() color.RGBA { + if p.index++; p.index >= p.size { + p.index = 0 + } + + return p.colors[p.index] +} + +func (p *Palette) color() color.RGBA { + return p.colors[p.index] +} + +func (p *Palette) random() color.RGBA { + p.index = rand.Intn(p.size) + + return p.colors[p.index] +} + +var Colors = []color.Color{ + color.RGBA{190, 38, 51, 255}, + color.RGBA{224, 111, 139, 255}, + color.RGBA{73, 60, 43, 255}, + color.RGBA{164, 100, 34, 255}, + color.RGBA{235, 137, 49, 255}, + color.RGBA{247, 226, 107, 255}, + color.RGBA{47, 72, 78, 255}, + color.RGBA{68, 137, 26, 255}, + color.RGBA{163, 206, 39, 255}, + color.RGBA{0, 87, 132, 255}, + color.RGBA{49, 162, 242, 255}, + color.RGBA{178, 220, 239, 255}, +} diff --git a/examples/community/bouncing/screenshot.png b/examples/community/bouncing/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..72d0fc03d286bef50d46343b1ae6f24ee96c18fa GIT binary patch literal 7056 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 literal 0 HcmV?d00001