github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/pixel/flowers/main.go (about) 1 package main 2 3 import ( 4 "image/color" 5 "math" 6 "math/rand" 7 "time" 8 9 "github.com/faiface/pixel" 10 "github.com/faiface/pixel/imdraw" 11 "github.com/faiface/pixel/pixelgl" 12 ) 13 14 func main() { 15 pixelgl.Run(run) 16 } 17 18 func run() { 19 cfg := pixelgl.WindowConfig{ 20 Title: "Flowers!", 21 Bounds: pixel.R(0, 0, 480, 320), 22 Resizable: true, 23 VSync: true, 24 } 25 26 win, err := pixelgl.NewWindow(cfg) 27 if err != nil { 28 panic(err) 29 } 30 31 camera := pixel.V(0, 0) 32 branch := NewRoot() 33 34 canvas := imdraw.New(nil) 35 background := 0.0 36 now := time.Now() 37 for !win.Closed() { 38 win.SetClosed(win.JustPressed(pixelgl.KeyEscape)) 39 40 next := time.Now() 41 dt := next.Sub(now).Seconds() 42 now = next 43 if dt > 1.0/15.0 { 44 dt = 1.0 / 15.0 45 } 46 47 camera = pixel.Lerp(camera, branch.Head(), 0.7*dt) 48 background += dt 49 50 branch.Update(dt) 51 52 win.Clear(HSL{background, 0.6, 0.99}) 53 //win.Clear(HSL{TAU / 3, 0.66, 0.99}) 54 55 canvas.Clear() 56 canvas.Reset() 57 canvas.SetMatrix(pixel.IM.Moved(win.Bounds().Center()).Moved(camera.Scaled(-1))) 58 { 59 branch.Draw(canvas) 60 } 61 canvas.Draw(win) 62 63 win.Update() 64 } 65 } 66 67 const ANGLESNAP = TAU / 8 68 69 type Branch struct { 70 Time float64 71 72 PathLimit int 73 Path []pixel.Vec 74 75 Thickness float64 76 Lightness float64 77 Accelerate float64 78 Speed float64 79 Direction float64 80 Turn float64 81 Length float64 82 Travel float64 83 84 IsRoot bool 85 Life int 86 SpawnCountdown float64 87 SpawnInterval float64 88 Branches []*Branch 89 } 90 91 func randomsnap(min, max float64, snap float64) float64 { 92 return min + math.Floor(rand.Float64()*(max-min)/snap)*snap 93 } 94 95 func random(min, max float64) float64 { 96 return min + rand.Float64()*(max-min) 97 } 98 99 func NewRoot() *Branch { 100 branch := &Branch{} 101 branch.PathLimit = 300 102 branch.Speed = 100.0 103 branch.Direction = random(0, TAU) 104 branch.IsRoot = true 105 branch.Thickness = 8.0 106 branch.Lightness = 0.7 107 108 branch.SpawnInterval = 0.3 109 branch.SpawnCountdown = branch.SpawnInterval 110 111 branch.NextSegment() 112 113 return branch 114 } 115 116 func NewBranch(root *Branch) *Branch { 117 child := NewRoot() 118 child.Thickness = root.Thickness * 0.3 119 child.IsRoot = false 120 child.Lightness = root.Lightness * 0.5 121 child.Life = len(root.Path) 122 child.Path = []pixel.Vec{root.Head()} 123 child.Direction = root.Direction 124 child.Turn = randomsnap(-TAU*3/4, TAU*3/4, ANGLESNAP) 125 return child 126 } 127 128 func (branch *Branch) Head() pixel.Vec { 129 if len(branch.Path) == 0 { 130 return pixel.V(0, 0) 131 } 132 return branch.Path[len(branch.Path)-1] 133 } 134 135 func (branch *Branch) NextSegment() { 136 branch.Accelerate += 1.0 137 branch.Turn = randomsnap(-TAU/2, TAU/2, ANGLESNAP) 138 branch.Length = randomsnap(100, 200, 25) 139 branch.Travel = branch.Length 140 } 141 142 func (branch *Branch) IsDying() bool { 143 return !branch.IsRoot && (branch.Life < 0) 144 } 145 func (branch *Branch) IsDead() bool { 146 return !branch.IsRoot && (branch.Life < 0) && (len(branch.Path) == 0) 147 } 148 149 func (branch *Branch) Update(dt float64) { 150 branch.Life-- 151 152 alive := branch.Branches[:0] 153 for _, child := range branch.Branches { 154 child.Update(dt) 155 if !child.IsDead() { 156 alive = append(alive, child) 157 } 158 } 159 branch.Branches = alive 160 161 if branch.Accelerate > 0 { 162 branch.Accelerate -= dt * 5 163 dt += Bounce(branch.Accelerate, 0, 1, 1) * dt 164 } 165 branch.Time += dt 166 167 if branch.IsRoot { 168 branch.SpawnCountdown -= dt 169 if branch.SpawnCountdown < 0 { 170 branch.SpawnCountdown = branch.SpawnInterval 171 child := NewBranch(branch) 172 branch.Branches = append(branch.Branches, child) 173 } 174 } 175 176 if !branch.IsDying() { 177 distance := branch.Speed * dt 178 179 if branch.IsRoot || branch.Travel > 0 { 180 sn, cs := math.Sincos(branch.Direction) 181 branch.Direction += branch.Turn * distance / branch.Length 182 dir := pixel.V(cs, sn).Scaled(distance) 183 branch.Travel -= distance 184 185 branch.Path = append(branch.Path, branch.Head().Add(dir)) 186 if len(branch.Path) > branch.PathLimit { 187 copy(branch.Path, branch.Path[1:]) 188 branch.Path = branch.Path[:len(branch.Path)-1] 189 } 190 } 191 192 if branch.IsRoot && branch.Travel <= 0 { 193 branch.NextSegment() 194 } 195 } else { 196 branch.Path = branch.Path[1:] 197 } 198 } 199 200 func (branch *Branch) Draw(m *imdraw.IMDraw) { 201 for _, child := range branch.Branches { 202 child.Draw(m) 203 } 204 205 Line(m, branch.Path, func(i int, at pixel.Vec) (float64, color.Color) { 206 p := float64(i) / float64(branch.PathLimit) 207 radius := branch.Thickness * (math.Sin(p*4*TAU+branch.Time*6) + 5) / 5 208 209 pp := float64(i) / float64(len(branch.Path)) 210 if pp > 0.85 { 211 radius *= 1 - (pp-0.85)/0.3 212 } 213 if !branch.IsRoot { 214 if pp < 0.05 { 215 radius *= pp / 0.05 216 } 217 } 218 color := HSL{branch.Time + p*TAU/8, 0.3, 1 - branch.Lightness} 219 return radius, color 220 }) 221 } 222 223 func Line(m *imdraw.IMDraw, path []pixel.Vec, attrfn func(i int, at pixel.Vec) (float64, color.Color)) { 224 if len(path) < 2 { 225 return 226 } 227 228 a := path[0] 229 var x1, x2, xn pixel.Vec 230 231 for i, b := range path { 232 if i > 0 && a.Sub(b).Len() < 1 { 233 continue 234 } 235 radius, color := attrfn(i, b) 236 m.Color = color 237 238 abn := ScaleTo(SegmentNormal(a, b), radius) 239 // segment-corners 240 a1, a2 := a.Add(abn), a.Sub(abn) 241 b1, b2 := b.Add(abn), b.Sub(abn) 242 243 if i > 0 && radius > 0.5 { 244 d := Rotate(xn).Dot(abn) 245 if d < 0 { 246 m.Push(x1, a1, a) 247 } else { 248 m.Push(x2, a2, a) 249 } 250 m.Polygon(0) 251 } 252 253 m.Push(a1, b1, b2, a2) 254 m.Polygon(0) 255 256 a = b 257 x1, x2, xn = b1, b2, abn 258 } 259 }