💾 Archived View for thrig.me › blog › 2023 › 05 › 12 › triangle.go captured on 2024-06-16 at 13:32:49.

View Raw

More Information

⬅️ Previous capture (2023-05-24)

-=-=-=-=-=-=-

// triangle - draws "mountains" in a somewhat random fashion

package main

import (
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"log"
	"math"
	"math/rand"
	"os"
	"time"
)

var palette = []color.Color{
	color.NRGBA{245, 222, 179, 255},
	color.Black,
}

const (
	bg_color = 0
	fg_color = 1
)

func clamp(min, max, value int) int {
	if value < min {
		return min
	} else if value > max {
		return max
	} else {
		return value
	}
}

// TODO this probably could be improved
func mark(c *image.Paletted, x, y int) {
	// KLUGE prevent overlaps
	for nx := -1; nx < 8; nx++ {
		for ny := -1; ny < 5; ny++ {
			if c.At(x+nx, y-ny) == palette[fg_color] {
				return
			}
		}
	}
	c.SetColorIndex(int(x), int(y), fg_color)
	c.SetColorIndex(int(x)+1, int(y)-1, fg_color)
	c.SetColorIndex(int(x)+2, int(y)-2, fg_color)
	c.SetColorIndex(int(x)+3, int(y)-3, fg_color)
	c.SetColorIndex(int(x)+4, int(y)-2, fg_color)
	c.SetColorIndex(int(x)+5, int(y)-1, fg_color)
	c.SetColorIndex(int(x)+6, int(y), fg_color)
}

func origin(xmax, ymax int) (x, y int) {
	x = int(rand.Int63n(int64(xmax)))
	y = int(rand.Int63n(int64(ymax)))
	return x, y
}

func main() {
	const (
		baseangle = 15 // originally 60 degrees for a Sierpinski triangle

		border = 8 // avoid the edges by this amount

		maxlen = 256 // longest step to make
		minlen = 8   // down to this value

		xsize = 480 // image dimensions
		ysize = 640

		nreps = 120 // how many things to draw
	)

	out, err := os.Create("out.gif")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	rand.Seed(time.Now().UTC().UnixNano())

	rect := image.Rect(0, 0, xsize, ysize)
	canvas := image.NewPaletted(rect, palette)

	arclen := baseangle * math.Pi / 180
	narcs := int64(math.Round(2 * math.Pi / arclen))

	// TODO better way to fill a canvas?
	for x := 0; x < xsize; x++ {
		for y := 0; y < ysize; y++ {
			canvas.SetColorIndex(x, y, bg_color)
		}
	}

	startx, starty := origin(xsize, ysize)
	startx = clamp(border, xsize-border, startx)
	starty = clamp(border, ysize-border, starty)
	n := maxlen
	x := startx
	y := starty

	for i := 0; i < nreps; i++ {
		// another option would be to shuffle the available
		// angles, see if that moves the point out-of-bounds,
		// and if so to pick a different angle until those run
		// out, and then maybe to backtrack through a queue.
		// that's more complicated, but might be interesting?
		angle := float64(rand.Int63n(narcs)) * arclen
		//fmt.Printf("dbg angle %v [%v,%v]\n", angle, x, y)
		mark(canvas, int(x), int(y))

		x += int(math.Cos(angle) * float64(n))
		y += int(math.Sin(angle) * float64(n))

		if x < border || x >= xsize-border || y < border || y >= ysize-border {
			// or the origin could be randomized ...
			x = startx
			y = starty
		} else {
			n /= 2
			if n < minlen {
				n = maxlen
			}
		}
	}

	options := &gif.Options{
		NumColors: len(palette),
	}
	gif.Encode(out, canvas, options)
	// KLUGE so I can add debug fmt.* calls without have to twiddle
	// the import line
	fmt.Println("ok")
}