From da55b9ddca557326b6e71bc2c82b0dc1a962e135 Mon Sep 17 00:00:00 2001
From: Peter Hellberg <peter@c7.se>
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

-- 
GitLab