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  }