304 lines
6.4 KiB
Go
304 lines
6.4 KiB
Go
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},
|
|
}
|