package main import ( "fmt" "log" "math" "os" "time" "github.com/gen2brain/raylib-go/raylib" "github.com/ojrac/opensimplex-go" ) func clamp01(v float64) float64 { if v < 0 { return 0 } if v > 1 { return 1 } return v } func GrayCurve(v, k float64) rl.Color { v = math.Pow(clamp01(v), k) // k < 1 boosts highlights, k > 1 boosts shadows c := uint8(v * 255) return rl.Color{R: c, G: c, B: c, A: 255} } const ( screenWidth = 1400 screenHeight = 700 displayScale = 2 snapshotsDir = "snapshots" ) func main() { os.MkdirAll(snapshotsDir, 0755) log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile) storage, err := NewStorage(snapshotsDir) if err != nil { log.Printf("Error loading storage: %v\n", err) os.Exit(1) } rl.SetConfigFlags(rl.FlagWindowHighdpi) rl.InitWindow(screenWidth, screenHeight, "sumi sierpinski arrow") log.Printf("screen=%dx%d render=%dx%d", rl.GetScreenWidth(), rl.GetScreenHeight(), rl.GetRenderWidth(), rl.GetRenderHeight(), ) w := rl.GetRenderWidth() h := rl.GetRenderHeight() angles := make([]float32, 1000) noise := opensimplex.NewNormalized(0) r := 0.75 dtheta := 360.0/float64(len(angles)) for i := range len(angles) { rad := float64(i) * dtheta * math.Pi / 180.0 x := r * math.Cos(rad) y := r * math.Sin(rad) angles[i] = float32(noise.Eval2(x, y) * 360.0) } sketches := []Sketch{ &SierpinskiArrow{}, &Worm{ position: rl.Vector2 { X: 50, Y: 50 }, angles: angles, angleIndex: 0, stepSize: 2, }, } var camera = rl.Camera2D{ Target: rl.Vector2{X: 0, Y: 0}, Offset: rl.Vector2{X: float32(w) / 2, Y: float32(h) / 2}, Rotation: 0, Zoom: 1.0, } rl.SetTargetFPS(60) t0 := time.Now() ports := MakePorts() ports["sierpinskiArrowLength"] = Const{ V: 1200, } ports["sierpinskiArrowDepth"] = Const{ V: 7, } ports["sierpinskiArrowAngle"] = Sine{ Amp: 120, Bias: 100, Freq: 0.1, } for !rl.WindowShouldClose() { updateCamera(&camera) // begin drawing rl.BeginDrawing() rl.ClearBackground(rl.RayWhite) rl.BeginMode2D(camera) t := time.Since(t0).Seconds() // set up RenderCtx renderCtx := &RenderCtx{ Width: int32(w), Height: int32(h), Time: t, Ports: ports.Eval(t), } /** MAIN DRAWING **/ for _, s := range sketches { rl.PushMatrix() s.Draw(renderCtx) rl.PopMatrix() } if rl.IsKeyDown(rl.KeySpace) { if _, err := storage.Save(); err != nil { log.Printf("Error saving snapshot: %v\n", err) } } rl.EndMode2D() rl.DrawLine(10, 10, int32(w-10), int32(h-10), rl.Black) // HUD rl.DrawText("Mouse right button drag to move, mouse wheel to zoom", 10, 10, 20, rl.Black) rl.EndDrawing() } rl.CloseWindow() } func updateCamera(camera *rl.Camera2D) { // Get the world point that is under the mouse mouseVec2 := rl.GetMousePosition() if rl.IsMouseButtonDown(rl.MouseRightButton) { // get mouse delta from last frame delta := rl.GetMouseDelta() // compute the amount to move scaled by the camera zoom delta = rl.Vector2Scale(delta, -1.0/camera.Zoom) camera.Target = rl.Vector2Add(camera.Target, delta) } // Zoom based on mouse wheel wheel := rl.GetMouseWheelMove() if wheel != 0 { mouseWorldPos := rl.GetScreenToWorld2D(mouseVec2, *camera) // Set the offset to where the mouse is camera.Offset = mouseVec2 // Set the target to match, so that the camera maps the world space point // under the cursor to the screen space point under the cursor at any zoom camera.Target = mouseWorldPos // Zoom increment const zoomIncrement float32 = 0.125 camera.Zoom += (wheel * zoomIncrement) if camera.Zoom < zoomIncrement { camera.Zoom = zoomIncrement } } } type Worm struct { position rl.Vector2 angles []float32 angleIndex int stepSize int } func (w *Worm) Draw(ctx *RenderCtx) { rl.PushMatrix() rl.Translatef(w.position.X, w.position.Y, 0) lastAngle := float32(0.0) for i := range w.angles { ii := (i + w.angleIndex) % len(w.angles) angle := w.angles[ii] rl.Rotatef(angle - lastAngle, 0, 0, 1) rl.DrawLine(0, 0, int32(w.stepSize), 0, rl.Black) rl.Translatef(float32(w.stepSize), 0, 0) lastAngle = angle } rl.PopMatrix() w.angleIndex = (w.angleIndex + 1) % len(w.angles) } type SierpinskiArrow struct{} func (s *SierpinskiArrow) Draw(ctx *RenderCtx) { sierpinskiArrow(ctx, int(ctx.Ports["sierpinskiArrowDepth"]), ctx.Ports["sierpinskiArrowLength"]) } func sierpinskiArrow(ctx *RenderCtx, order int, length float64) { if order == 0 { curve(ctx, order, length, ctx.Ports["sierpinskiArrowAngle"]) } else { rl.Rotatef(float32(ctx.Ports["sierpinskiArrowAngle"]), 0, 0, 1) curve(ctx, order, length, -ctx.Ports["sierpinskiArrowAngle"]) } } func curve(ctx *RenderCtx, order int, length float64, angle float64) { if order == 0 { len := int32(length) rl.DrawLine(0, 0, len, 0, rl.Black) rl.Translatef(float32(length), 0, 0) } else { curve(ctx, order-1, length/2, -angle) rl.Rotatef(float32(angle), 0, 0, 1) curve(ctx, order-1, length/2, angle) rl.Rotatef(float32(angle), 0, 0, 1) curve(ctx, order-1, length/2, -angle) } } func main2() { angles := make([]float32, 1000) noise := opensimplex.NewNormalized(0) for i := range len(angles) { angles[i] = float32(noise.Eval2(float64(i)*0.05, 0.00))*0.1 - 0.05 } frameNum := 0 for !rl.WindowShouldClose() { frameNum++ // initial transform by halfway again through angle array angleIndex := (frameNum / 10) % len(angles) angle := angles[angleIndex] initAngle := angles[(angleIndex+len(angles)/2)%len(angles)] rl.Rotatef(2500*initAngle, 0, 0, 1) rl.Translatef(100*initAngle, 100*initAngle, 0) fmt.Printf("%.3f", angle) rl.EndMode2D() } }