Skip to content

Instantly share code, notes, and snippets.

@peterhellberg
Created November 3, 2017 18:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save peterhellberg/674f32a15a7d2d249e634ce781f333e8 to your computer and use it in GitHub Desktop.
Save peterhellberg/674f32a15a7d2d249e634ce781f333e8 to your computer and use it in GitHub Desktop.
Bouncing balls using the Pixel package
package main
import (
"image/color"
"math/rand"
"time"
"github.com/faiface/pixel"
"github.com/faiface/pixel/imdraw"
"github.com/faiface/pixel/pixelgl"
)
var (
w, h, scale = float64(320), float64(180), float64(6)
p = newPalette(PinkColors)
bg = color.RGBA{7, 9, 9, 255}
lc, pc color.RGBA
v = rand.Float64() * scale
balls = []*ball{
newRandomBall(4 + v),
newRandomBall(6 + v),
newRandomBall(10 + v),
newRandomBall(14 + v),
newRandomBall(18 + v),
}
)
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.Precision = 3
go func() {
var step int
for range time.Tick(256 * time.Millisecond) {
switch imd.Precision {
case 3:
step = 1
case 9:
step = -1
}
imd.Precision += step
}
}()
for !win.Closed() {
win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))
var positions = []pixel.Vec{}
for _, ball := range balls {
positions = append(positions, ball.pos)
}
imd.Clear()
imd.Color = lc
imd.Push(positions...)
imd.Push(positions[0])
imd.Line(scale)
imd.Color = pc
imd.Push(positions...)
imd.Polygon(0)
for _, ball := range balls {
imd.Color = ball.color
imd.Push(ball.pos)
imd.Circle(ball.radius, 0)
}
win.Clear(bg)
imd.Draw(win)
win.Update()
}
}
func main() {
rand.Seed(42)
go func() {
for range time.Tick(16 * time.Millisecond) {
for _, ball := range balls {
ball.update()
}
}
}()
updatePolygonColor(p.next())
pixelgl.Run(run)
}
func updatePolygonColor(c color.RGBA) {
lc = color.RGBA{c.R / 4, c.G / 4, c.B / 4, 192}
pc = color.RGBA{c.R / 6, c.G / 6, c.B / 6, 64}
}
func newRandomBall(radius float64) *ball {
pos := pixel.V(w/4+(w/4*3)*rand.Float64(), h/4+(h/4*3)*rand.Float64())
dir := pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(32 / radius)
return &ball{pos, dir, radius, p.color(), p.clone()}
}
type ball struct {
pos pixel.Vec
dir pixel.Vec
radius float64
color color.RGBA
palette *palette
}
func (b *ball) update() {
b.pos.X += b.dir.X
b.pos.Y += b.dir.Y
if b.pos.Y <= b.radius || b.pos.Y >= h-b.radius {
b.dir.Y *= -1
b.color = b.palette.next()
updatePolygonColor(p.next())
}
if b.pos.X <= b.radius || b.pos.X >= w-b.radius {
b.dir.X *= -1
b.color = b.palette.next()
updatePolygonColor(p.next())
}
}
func newPalette(colors []color.RGBA) *palette {
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, len(p.colors), p.index}
}
func (p *palette) next() color.RGBA {
p.index++
if p.index+1 >= 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 (
Pink = color.RGBA{255, 192, 203, 255}
LightPink = color.RGBA{255, 182, 193, 255}
HotPink = color.RGBA{255, 105, 180, 255}
DeepPink = color.RGBA{255, 20, 147, 255}
PaleVioletRed = color.RGBA{219, 112, 147, 255}
MediumVioletRed = color.RGBA{199, 21, 133, 255}
)
var PinkColors = []color.RGBA{
Pink,
LightPink,
HotPink,
DeepPink,
PaleVioletRed,
MediumVioletRed,
}
@peterhellberg
Copy link
Author

peterhellberg commented Nov 3, 2017

bouncing

bouncing-balls-20171103-194844

@peterhellberg
Copy link
Author

ezgif com-optimize

@peterhellberg
Copy link
Author

ezgif com-optimize 1

@peterhellberg
Copy link
Author

bouncing-collisions-20171103-232907

bouncing-collisions-20171103-232914

@peterhellberg
Copy link
Author

Bouncing with collisions

package main

import (
	"image/color"
	"image/color/palette"
	"math"
	"math/rand"
	"time"

	"github.com/faiface/pixel"
	"github.com/faiface/pixel/imdraw"
	"github.com/faiface/pixel/pixelgl"
)

var (
	w, h, scale = float64(640), float64(360), float64(6)

	p, bg = newPalette(palette.Plan9), color.RGBA{255, 228, 225, 255}

	balls = []*ball{
		newRandomBall(30),
		newRandomBall(40),
		newRandomBall(30),
	}
)

func run() {
	win, err := pixelgl.NewWindow(pixelgl.WindowConfig{
		Bounds:      pixel.R(0, 0, w, h),
		VSync:       true,
		Undecorated: true,
	})
	if err != nil {
		panic(err)
	}

	win.SetSmooth(true)

	imd := imdraw.New(nil)
	imd.EndShape = imdraw.RoundEndShape

	for !win.Closed() {
		win.SetClosed(win.JustPressed(pixelgl.KeyEscape) || win.JustPressed(pixelgl.KeyQ))

		c0 := balls[0].color
		c1 := balls[1].color
		c2 := balls[2].color

		lc := color.RGBA{(c1.R / 3) * 2, (c1.G / 3) * 2, (c1.B / 3) * 2, 255}

		imd.Clear()

		imd.Color = lc
		imd.Push(balls[0].pos, balls[1].pos, balls[2].pos)
		imd.Line(scale * 3)

		imd.Push(balls[1].pos)
		imd.Circle(balls[1].radius, scale*4)

		imd.Color = color.RGBA{
			uint8((int(c0.R) + int(c2.R) + int(c1.R)) / 4),
			uint8((int(c0.G) + int(c2.G) + int(c1.G)) / 4),
			uint8((int(c0.B) + int(c2.B) + int(c1.B)) / 4),
			128,
		}

		imd.Push(balls[0].pos, balls[2].pos)
		imd.Line(balls[0].radius * 2.5)

		for _, ball := range []*ball{balls[0], balls[2]} {
			imd.Color = ball.color
			imd.Push(ball.pos)
			imd.Circle(ball.radius, 0)
		}

		imd.Color = balls[1].color
		imd.Push(balls[1].pos)
		imd.Circle(balls[1].radius, 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 {
				ball.update()
			}
		}
	}()

	pixelgl.Run(run)
}

func newRandomBall(radius float64) *ball {
	bp := p.clone()

	mass := math.Pi * (radius * radius)

	return &ball{
		pixel.V(w/2, h/2),
		pixel.V((rand.Float64()*2)-1, (rand.Float64()*2)-1).Scaled(scale),
		mass, radius, bp.random(), bp,
	}
}

type ball struct {
	pos     pixel.Vec
	vel     pixel.Vec
	mass    float64
	radius  float64
	color   color.RGBA
	palette *Palette
}

func (b *ball) update() {
	b.pos = b.pos.Add(b.vel)

	if b.pos.Y <= b.radius+scale || b.pos.Y >= h-(b.radius+scale) {
		b.vel.Y *= -1.0
		b.color = b.palette.next()
	}

	if b.pos.X <= b.radius+scale || b.pos.X >= w-(b.radius+scale) {
		b.vel.X *= -1.0
		b.color = b.palette.next()
	}

	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 * a.mass))
			b.vel = b.vel.Add(u.Scaled(v * b.mass))

			a.color = a.palette.next()
			b.color = b.palette.next()
		}
	}
}

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 {
	p.index++

	if p.index+1 >= 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]
}

@peterhellberg
Copy link
Author

bouncing-ribbon-20171104-014634

bouncing-ribbon-20171104-014708

bouncing-ribbon-20171104-015236

bouncing-ribbon-20171104-015329

bouncing-ribbon-20171104-015331

bouncing-ribbon-20171104-014317

bouncing-ribbon-20171104-015334

@peterhellberg
Copy link
Author

ezgif com-optimize 2

@peterhellberg
Copy link
Author

bouncing-particles

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment