Files
sumi/main.go
2025-12-16 17:45:21 -06:00

258 lines
5.6 KiB
Go

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()
}
}