diff --git a/examples/community/bouncing/README.md b/examples/community/bouncing/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cb1c7f12f0f7aa9fa1af34728977695aaa631a07
--- /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 0000000000000000000000000000000000000000..b2b43f286f4660e5a41b827cf4f6c4a8d15317de
--- /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
Binary files /dev/null and b/examples/community/bouncing/screenshot.png differ