- Download Go for your platform from here
- Create a new directory and switch to it
- Download the .go files from this Gist into it
- Run
$ go mod init mainthis will create a file called go.mod that's needed for the next step - Run
$ go mod tidythis will pull down the packages needed to run the program - Run
$ go run tangle.go - The result will be in tangle.png
Last active
November 7, 2025 19:45
-
-
Save jphsd/b31bd1157e7c59010c0ed15ba67b8e6d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //go:build ignore | |
| package main | |
| import ( | |
| "flag" | |
| g2d "github.com/jphsd/graphics2d" | |
| "github.com/jphsd/graphics2d/color" | |
| "github.com/jphsd/graphics2d/image" | |
| "math" | |
| "math/rand" | |
| ) | |
| // Generalized Brownian motion | |
| func main() { | |
| vf := flag.Float64("v", 1, "variance") | |
| rf := flag.Float64("s", 5, "step size") | |
| tf := flag.Int("t", 1, "max TTL") | |
| flag.Parse() | |
| v, r, mt := *vf, *rf, *tf | |
| w, h := 1000, 1000 | |
| bb := [][]float64{{0, 0}, {1000, 1000}} | |
| // Starting from the center | |
| p := []float64{500, 500} | |
| points0 := [][]float64{p} | |
| head := (rand.Float64()*2 - 1) * g2d.Pi | |
| dh := NextDh(v) | |
| ttl := rand.Intn(mt) | |
| orig := head | |
| odh := dh | |
| for PointInBox(p, bb) { | |
| if ttl == 0 { | |
| dh = NextDh(v) | |
| ttl = rand.Intn(mt) | |
| } else { | |
| ttl-- | |
| } | |
| head += dh | |
| dx, dy := r*math.Cos(head), r*math.Sin(head) | |
| p = []float64{p[0] + dx, p[1] + dy} | |
| points0 = append(points0, p) | |
| } | |
| np := len(points0) | |
| points := make([][]float64, np, 2*np) | |
| np-- | |
| for i, pt := range points0 { | |
| points[np-i] = pt | |
| } | |
| // Now the opposite direction | |
| p = points[np] | |
| head = orig + g2d.Pi | |
| dh = odh | |
| ttl = rand.Intn(mt) | |
| for PointInBox(p, bb) { | |
| if ttl == 0 { | |
| dh = NextDh(v) | |
| ttl = rand.Intn(mt) | |
| } else { | |
| ttl-- | |
| } | |
| head += dh | |
| dx, dy := r*math.Cos(head), r*math.Sin(head) | |
| p = []float64{p[0] + dx, p[1] + dy} | |
| points = append(points, p) | |
| } | |
| shape := &g2d.Shape{} | |
| np = len(points) | |
| for i, p := range points { | |
| if i == np-1 { | |
| break | |
| } | |
| shape.AddPaths(g2d.Line(p, points[i+1])) | |
| } | |
| img := image.NewRGBA(w, h, color.White) | |
| g2d.DrawShape(img, shape, g2d.BlackPen) | |
| image.SaveImage(img, "tangle") | |
| } | |
| func NextDh(v float64) float64 { | |
| dh := rand.NormFloat64() * v * g2d.Pi | |
| if dh < -g2d.Pi || dh > g2d.Pi { | |
| // Rather than just clamping since that leads to a fat tail | |
| return NextDh(v) | |
| } | |
| return dh | |
| } | |
| func PointInBox(point []float64, box [][]float64) bool { | |
| if box == nil { | |
| return false | |
| } | |
| return !(point[0] < box[0][0] || point[1] < box[0][1] || point[0] > box[1][0] || point[1] > box[1][1]) | |
| } |
Author
jphsd
commented
Nov 7, 2025

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