Skip to content

Instantly share code, notes, and snippets.

@jphsd
Last active October 30, 2025 18:51
Show Gist options
  • Save jphsd/fc9f07586ad277987e91ec503f34f5d3 to your computer and use it in GitHub Desktop.
Save jphsd/fc9f07586ad277987e91ec503f34f5d3 to your computer and use it in GitHub Desktop.
Side scrolling landscape using glui and graphics2d packages
package main
import (
"flag"
"github.com/jphsd/glui"
"github.com/jphsd/graphics2d"
"github.com/jphsd/graphics2d/color"
"github.com/jphsd/graphics2d/image"
"image/draw"
"math"
"math/rand"
)
// From an idea by J. Tarbell
// http://www.complexification.net/gallery/machines/sandstroke/
type Sand struct {
width int
height int
grains int
gage float64
r float64
sg float64
img *image.RGBA
}
// GLUI scrolling landscape
func main() {
rf := flag.Float64("r", 2, "point radius")
gf := flag.Int("g", 200, "grains")
flag.Parse()
s := &Sand{width: 1000, height: 500, grains: *gf, gage: 200, r: *rf}
win := glui.NewGLWin(s.width, s.height, "Sand", s.First(), true)
glui.NewKeyListener(win, func(k, _ int, _ glui.Action, _ glui.ModifierKey) {
if glui.GetKeyName(k) == "ESCAPE" {
win.Win.SetShouldClose(true)
}
})
// Allow the system to render it
glui.Loop(func() {
img := s.Next()
if img != nil {
win.SetImage(img)
}
})
}
func (s *Sand) First() *image.RGBA {
s.sg = rand.Float64()*.09 + 0.01 // [0.01,0.1]
s.img = image.NewRGBA(s.width, s.height, color.White)
x, dx, y := 0.0, 1.0, float64(s.height)/2
for range s.width {
s.sg += rand.Float64()*0.1 - 0.05
if s.sg < 0 {
s.sg = 0
} else if s.sg > 1 {
s.sg = 1
}
w := s.sg / float64(s.grains-1)
for i := range s.grains {
a := 0.1 - float64(i)/float64(s.grains*10)
ai := uint8(a * 256)
//dy := s.gage * float64(i) * w
//dy := s.gage * math.Sin(float64(i)*w)
dy := s.gage * math.Sin(math.Sin(float64(i)*w))
if i > 0 {
point(s.img, x, y+dy, s.r, color.RGBA{0, 0, 0, ai})
}
point(s.img, x, y-dy, s.r, color.RGBA{0, 0, 0, ai})
}
x += dx
}
return s.img
}
func (s *Sand) Next() *image.RGBA {
bounds := s.img.Bounds()
img := image.NewRGBA(s.width, s.height, color.Black) // Saves fill on creation
// Blit old image and set remainder to white
rect := image.Rect(bounds.Min.X, bounds.Min.Y, bounds.Max.X-1, bounds.Max.Y)
soffs := image.Point{X: 1, Y: 0}
draw.Draw(img, rect, s.img, soffs, draw.Src)
sliver := image.Rect(bounds.Max.X-1, bounds.Min.Y, bounds.Max.X, bounds.Max.Y)
draw.Draw(img, sliver, &image.Uniform{color.White}, soffs, draw.Src)
// Fill in new edge
x, y := float64(s.width-1), float64(s.height)/2
s.sg += rand.Float64()*0.1 - 0.05
if s.sg < 0 {
s.sg = 0
} else if s.sg > 1 {
s.sg = 1
}
w := s.sg / float64(s.grains-1)
for i := range s.grains {
a := 0.1 - float64(i)/float64(s.grains*10)
ai := uint8(a * 256)
//dy := s.gage * float64(i) * w
//dy := s.gage * math.Sin(float64(i)*w)
dy := s.gage * math.Sin(math.Sin(float64(i)*w))
if i > 0 {
point(img, x, y+dy, s.r, color.RGBA{0, 0, 0, ai})
}
point(img, x, y-dy, s.r, color.RGBA{0, 0, 0, ai})
}
s.img = img
return img
}
func point(img *image.RGBA, x, y, r float64, c color.RGBA) {
graphics2d.FillPath(img, graphics2d.Square([]float64{x, y}, r), graphics2d.NewPen(c, 1))
}

How To Run

  • Download Go for your platform from here
  • Create a new directory and switch to it
  • Download sand.go into it
  • Run $ go mod init main this will create a file called go.mod that's needed for the next step
  • Run $ go mod tidy this will pull down the packages needed to run the program
  • Run $ go run sand.go hit ESC to quit, or just close the window
@jphsd
Copy link
Author

jphsd commented Aug 5, 2025

sand3

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