diff --git a/field.go b/field.go index 559d877..f070c62 100644 --- a/field.go +++ b/field.go @@ -102,7 +102,7 @@ type FieldLayer struct { dirty bool } -func (fl *FieldLayer) Update(env *Env) { +func (fl *FieldLayer) Update(env *Env, g *sg.Graphics) { } diff --git a/internal/graphics/graphics.go b/internal/graphics/graphics.go index 141b36a..4d0d5d5 100644 --- a/internal/graphics/graphics.go +++ b/internal/graphics/graphics.go @@ -31,15 +31,35 @@ type Style struct { Stroke, Fill bool } +func BeginDrawing() { + rl.BeginDrawing() +} + +func EndDrawing() { + rl.EndDrawing() +} + func (g *Graphics) Center() Point { return g.Bounds.Center() } func (g *Graphics) Begin() { - rl.BeginBlendMode(rl.BlendAlphaPremultiply) g.PushMatrix() - g.Translate(g.Bounds.UL()) g.BeginClip(g.Bounds) + g.Translate(g.Bounds.UL()) +} + +func (g *Graphics) BeginPremultiplyBlend() { + rl.BeginBlendMode(rl.BlendAlphaPremultiply) +} + +func (g *Graphics) EndPremultiplyBlend() { + rl.EndBlendMode() +} + +func (g *Graphics) End() { + g.PopMatrix() + g.EndClip() } func (g *Graphics) Clear() { @@ -50,12 +70,6 @@ func (g *Graphics) Background(c color.RGBA) { rl.ClearBackground(c) } -func (g *Graphics) End() { - g.EndBlend() - g.EndClip() - g.PopMatrix() -} - func (g *Graphics) Width() float32 { return g.Bounds.Width } @@ -133,13 +147,14 @@ func (g *Graphics) BeginTexture(t rl.RenderTexture2D) { rl.BeginTextureMode(t) } - func (g *Graphics) DrawTexture(t rl.Texture2D, p Point, c color.RGBA) { rl.DrawTexture(t, int32(p.X), int32(p.Y), c) } - func (g *Graphics) TransferTexture(t rl.Texture2D, src Rect, dst Rect, tint color.RGBA) { + // flip source since textures are upside down on GPU + //src.Y += src.Height + src.Height = -src.Height rl.DrawTexturePro(t, src.ToRL(), dst.ToRL(), rl.Vector2{}, 0, tint) } @@ -185,7 +200,16 @@ type Rect rl.Rectangle func (r *Rect) Center() Point { return Point { X: r.X + r.Width / 2, - Y: r.X + r.Height / 2, + Y: r.Y + r.Height / 2, + } +} + +func (r *Rect) ContractByAbs(px float32) Rect { + return Rect { + X: r.X + px, + Y: r.Y + px, + Width: r.Width - 2*px, + Height: r.Height - 2*px, } } diff --git a/main.go b/main.go index 5be2df6..37ba9fe 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,6 @@ import ( "time" sg "github.com/d2fn/sumi/internal/graphics" - "github.com/ojrac/opensimplex-go" gui "github.com/gen2brain/raylib-go/raygui" rl "github.com/gen2brain/raylib-go/raylib" @@ -71,7 +70,7 @@ func Bootstrap() *Env { os.Exit(1) } - layout := Layout{ + layout := Layout { Monitor: sg.Rect{X: 0, Y: 0, Width: float32(monitorWidth), Height: float32(monitorHeight)}, Window: sg.Rect{X: 0, Y: 0, Width: float32(windowWidth), Height: float32(windowHeight)}, Controls: sg.Rect{X: 0, Y: 0, Width: float32(controlsWidth), Height: float32(windowHeight)}, @@ -81,7 +80,7 @@ func Bootstrap() *Env { //rl.SetConfigFlags(rl.FlagMsaa4xHint) rl.InitWindow(int32(layout.Window.Width), int32(layout.Window.Height), "sumi sierpinski arrow") - rl.SetTargetFPS(60) + rl.SetTargetFPS(30) env := NewEnv() env.Layout = layout @@ -105,37 +104,37 @@ func main() { rng := rand.New(rand.NewSource(env.Time.Unix())) //imageField := NewImageField("/home/d/Dropbox/art/data/david.png") - noiseField := &SimplexNoiseField{Noise: opensimplex.New32(env.Time.Unix())} - sinXYField := &SinXYField{} + //noiseField := &SimplexNoiseField{Noise: opensimplex.New32(env.Time.Unix())} //imageField := NewImageField("/home/d/Dropbox/art/data/ramstatue.png") //imageField := NewImageField("/home/d/Dropbox/art/data/bassrockastro/Photo Dec 24 2025, 5 58 23 PM.jpg") //imageField := NewImageField("/home/d/Dropbox/art/data/bassrockastro/andromeda.jpg") - //imageField := NewImageField("/home/d/Dropbox/art/data/moses_statue.jpg") + imageField := NewImageField("/home/d/Dropbox/art/data/moses_statue.jpg") + //imageLayer := NewImageLayer("/home/d/Dropbox/art/data/moses_statue.jpg") + field := &TranslateField{ x: -float32(env.Offscreen.Bounds.Width / 2.0), y: -float32(env.Offscreen.Bounds.Height / 2.0), - field: &ScaleField{ - scale: 100.0, - field: &AdderField{ + field: + &AdderField{ fields: []Field{ - &ScaleField{scale: 10, field: noiseField}, - sinXYField, + &ScaleField{scale: 3, field: imageField}, + &ScaleField{scale: 150, field: &SinXYField{ }}, }, }, - }, } - //sierpinskiLayer := &SierpinskiArrow { dirty: true } + sierpinskiLayer := &SierpinskiArrow { dirty: true } sketch := NewSketch(env) fieldColor := colorCycle.Next() fmt.Printf("field color = %v\n", fieldColor) - sketch.AddColorLayer("background-magenta", rl.Magenta) + sketch.AddColorLayer("background-blue", rl.Blue) sketch.AddColorLayer("background-black", rl.Black) - //sketch.AddLayer("field", &FieldLayer{field: field, loColor: rl.NewColor(0, 0, 0, 0), hiColor: fieldColor, dirty: true}) + //sketch.AddLayer("moses", imageLayer) + sketch.AddLayer("field", &FieldLayer{field: field, loColor: rl.NewColor(0, 0, 0, 0), hiColor: fieldColor, dirty: true}) actorColor := colorCycle.Next() fmt.Printf("actor color = %v\n", actorColor) @@ -149,9 +148,9 @@ func main() { //NewColor(11, 35, 176, 50), //r - contourLayer := NewContourLayer(&sketch, rng, field, actorColor, -25*math.Pi, 25*math.Pi) + contourLayer := NewContourLayer(&sketch, rng, field, actorColor, -4*math.Pi, 4*math.Pi) sketch.AddLayer("contours", contourLayer) - //sketch.AddLayer("sierpinski-arrowhead", sierpinskiLayer) + sketch.AddLayer("sierpinski-arrowhead", sierpinskiLayer) // aurora := NewImageLayer("/home/d/Dropbox/photos/Events/2025/Aurora/Photo Nov 11 2025, 9 52 03 PM.jpg") // sketch.AddLayer("aurora", aurora) // cave := NewImageLayer("/home/d/Dropbox/photos/Events/2025/ Chelsea and James visit Lindell/Photo Nov 29 2025, 5 26 40 PM (29).jpg") @@ -185,14 +184,6 @@ func main() { sketch.Update(env) - /** - * MAIN DRAWING - */ - rl.BeginDrawing() - rl.ClearBackground(rl.GetColor(uint(gui.GetStyle(gui.DEFAULT, gui.BACKGROUND_COLOR)))) - - sketch.Draw(env) - gui.SetStyle(gui.DEFAULT, gui.BACKGROUND_COLOR, 0x181818FF) gui.SetStyle(gui.DEFAULT, gui.BASE_COLOR_NORMAL, 0x2A2A2AFF) gui.SetStyle(gui.DEFAULT, gui.BASE_COLOR_FOCUSED, 0x3A3A3AFF) @@ -201,19 +192,34 @@ func main() { gui.SetStyle(gui.DEFAULT, gui.TEXT_COLOR_FOCUSED, 0xFFFFFFFF) gui.SetStyle(gui.DEFAULT, gui.BORDER_COLOR_NORMAL, 0x404040FF) - y := float32(10) + /** + * MAIN DRAWING + */ + sg.BeginDrawing() - minX := float32(60) - maxX := float32(g.Layout.Controls.X + g.Layout.Controls.Width - 20) - sliderWidth := maxX - minX - 20 + env.Window.Begin() + env.Window.Background(rl.GetColor(uint(gui.GetStyle(gui.DEFAULT, gui.BACKGROUND_COLOR)))) + env.Window.End() + + sketch.Draw(env) + + margin := 10 + y := float32(margin) + minX := float32(margin) + maxX := float32(env.Layout.Controls.X + env.Layout.Controls.Width - 20) + sliderWidth := maxX - 0 - 20 controlRowHeight := 20 + controlRect := env.Layout.Controls + c := env.Controls + c.Begin() + //rl.ClearBackground(rl.GetColor(uint(gui.GetStyle(gui.DEFAULT, gui.BACKGROUND_COLOR)))) for _, layerTools := range sketch.layerToolsOrdered { config := layerTools.config //layerTools.texture.Texture - gui.Label(rl.Rectangle{X: minX, Y: y, Width: 120, Height: 24}, layerTools.name) + gui.Label(rl.Rectangle{X: minX, Y: y, Width: controlRect.Width, Height: 24}, layerTools.name) y += float32(controlRowHeight + 10) @@ -235,24 +241,16 @@ func main() { config.bVisible = gui.Toggle(rl.Rectangle{X: minX, Y: y, Width: 16, Height: 16}, "B", config.bVisible) config.b = uint8(gui.Slider(rl.Rectangle{X: minX + 20, Y: y, Width: sliderWidth, Height: 16}, "", "", float32(config.b), 0, 255)) - /* - // don't do anything with saturation / k values yet - y += float32(controlRowHeight) - config.desaturate = !gui.Toggle(rl.Rectangle{X: minX, Y: y, Width: 16, Height: 16}, "S", !config.desaturate) - config.saturation = gui.Slider(rl.Rectangle{X: minX + 20, Y: y, Width: sliderWidth, Height: 16}, "", "", config.saturation, 0, 100) - y += float32(controlRowHeight) - gui.Label(rl.Rectangle{X: minX, Y: y, Width: 16, Height: 16}, "K") - config.kValue = gui.Slider(rl.Rectangle{X: minX + 20, Y: y, Width: sliderWidth, Height: 16}, "", "", config.kValue, 0, 2) - */ - y += float32(controlRowHeight + 10) } - rl.EndDrawing() + c.End() + + sg.EndDrawing() if rl.IsKeyDown(rl.KeySpace) { - capture := sketch.Capture() - if _, err := storage.Save(capture); err != nil { + capture := sketch.Capture(env) + if _, err := env.Storage.Save(capture); err != nil { log.Printf("Error saving snapshot: %v\n", err) } } diff --git a/sierpinski.go b/sierpinski.go index 71b0165..a838941 100644 --- a/sierpinski.go +++ b/sierpinski.go @@ -2,19 +2,20 @@ package main import ( rl "github.com/gen2brain/raylib-go/raylib" + sg "github.com/d2fn/sumi/internal/graphics" ) type SierpinskiArrow struct { dirty bool } -func (s *SierpinskiArrow) Draw(env *Env) { - rl.Translatef(float32(env.GetGraphicsWidth())/2.0, float32(env.GetGraphicsHeight())/2.0, 0) +func (s *SierpinskiArrow) Draw(env *Env, g *sg.Graphics) { + rl.Translatef(env.Offscreen.Width()/2.0, env.Offscreen.Height()/2.0, 0) rl.ClearBackground(rl.NewColor(0, 0, 0, 0)) sierpinskiArrow(env, int(env.Ports["sierpinskiArrowDepth"]), env.Ports["sierpinskiArrowLength"]) } -func (s *SierpinskiArrow) Update(_ *Env) { +func (s *SierpinskiArrow) Update(_ *Env, g *sg.Graphics) { s.dirty = true } diff --git a/sketch.go b/sketch.go index b142954..2f804ff 100644 --- a/sketch.go +++ b/sketch.go @@ -44,6 +44,8 @@ type LayerConfig struct { func NewSketch(env *Env) Sketch { + fmt.Printf("Creating new sketch with env: %v\n", env) + // point at source center // put source center at center of screen var camera = TextureCam{ @@ -51,13 +53,15 @@ func NewSketch(env *Env) Sketch { Zoom: 1.0, } - return Sketch{ + s := Sketch { env: env, layerTools: make(map[string]*LayerTools), layerToolsOrdered: []*LayerTools{}, composite: rl.LoadRenderTexture(env.Offscreen.WidthInt32(), env.Offscreen.HeightInt32()), cam: &camera, } + + return s } func (s *Sketch) AddLayer(name string, layer Layer) { @@ -72,6 +76,8 @@ func (s *Sketch) AddLayer(name string, layer Layer) { } s.layerToolsOrdered = append(s.layerToolsOrdered, &layerTools) s.layerTools[name] = &layerTools + + } func (s *Sketch) AddColorLayer(name string, c rl.Color) { @@ -94,11 +100,11 @@ func (s *Sketch) RedrawLayers(env *Env, g *sg.Graphics) { layer.Update(env, g) // re-render to texture if dirty if instance.layer.IsDirty() { - lg.Begin() lg.BeginTexture(instance.texture) + lg.Begin() layer.Draw(env, lg) - lg.EndTexture() lg.End() + lg.EndTexture() } } } @@ -109,11 +115,6 @@ func (s *Sketch) Draw(env *Env) { offscreen := env.Offscreen s.RedrawLayers(env, offscreen) - // copy from full texture for compositing, with vertical flipping - src := offscreen.Bounds - src.Height = -src.Height - dst := offscreen.Bounds - /* src := g.Rect { X: 0, Y: 0, @@ -127,14 +128,12 @@ func (s *Sketch) Draw(env *Env) { } */ - offscreen.Begin() - // calculate the viewable region of the offscreen buffer viewport := s.CalcViewport(env) // scale the offscreen buffer to the viewport maintaining aspect ratio outputRect := offscreen.Bounds.ScaleTo(env.Viewport.Bounds) - fmt.Printf("outputRect = %v\n", outputRect) + //fmt.Printf("scaling %v to %v => outputRect = %v\n", offscreen.Bounds, env.Viewport.Bounds, outputRect) x := float32(0) y := float32(0) @@ -142,19 +141,20 @@ func (s *Sketch) Draw(env *Env) { h := outputRect.Height output := sg.CreateGraphics(outputRect) - + + // render a checker pattern indicating transparency output.Begin() output.SetFill(true) output.SetStroke(false) - checkSize := float32(outputRect.Width / 30.0) - grey := sg.RGBA(220, 220, 220, 255) + checkSize := float32(outputRect.Width / 50.0) + grey := rl.NewColor(220, 220, 220, 255) cellX := 0 cellY := 0 for y < h { x = 0 cellX = 0 for x < w { - c := sg.RGBA(rl.White.R, rl.White.G, rl.White.B, rl.White.A) + c := rl.White if ((cellX + cellY) & 1) == 1 { c = grey } @@ -167,9 +167,16 @@ func (s *Sketch) Draw(env *Env) { y += checkSize cellY++ } + output.End() - // render each layer onto the output graphics context + rl.GenTextureMipmaps(&s.composite.Texture) + rl.SetTextureFilter(s.composite.Texture, rl.FilterTrilinear) + + + // render each layer onto the composite + offscreen.BeginPremultiplyBlend() offscreen.BeginTexture(s.composite) + offscreen.Begin() offscreen.Clear() //rl.BeginBlendMode(rl.BlendAlphaPremultiply) @@ -197,35 +204,57 @@ func (s *Sketch) Draw(env *Env) { g = uint8(float32(g) * (float32(config.a) / 255.0)) b = uint8(float32(b) * (float32(config.a) / 255.0)) tint := rl.NewColor(r, g, b, config.a) - offscreen.TransferTexture(instance.texture.Texture, src, dst, tint) + offscreen.TransferTexture(instance.texture.Texture, offscreen.Bounds, offscreen.Bounds, tint) //rl.DrawTexturePro(instance.texture.Texture, src.ToRL(), dst.ToRL(), rl.Vector2{}, 0, tint) } } - offscreen.EndTexture() offscreen.End() + offscreen.EndTexture() + offscreen.EndBlend() - rl.GenTextureMipmaps(&s.composite.Texture) - rl.SetTextureFilter(s.composite.Texture, rl.FilterTrilinear) + // minimap indexing for composite so that it scales down nicely + //rl.DrawTexturePro(s.composite.Texture, rl.Rectangle { X: viewport.X, Y: viewport.Y, Width: viewport.Width, Height: viewport.Height}, dst.ToRL(), rl.Vector2{}, 0, rl.White) - output.TransferTexture(s.composite.Texture, viewport, outputRect, rl.White) + //fmt.Printf("viewport -> %v\n", viewport) - output.SetFill(false) - output.SetStroke(true) - output.SetStrokeColor(rl.Gray) - output.DrawRect(outputRect) + // finally, transfer composite texture to output rect on screen + /** + rl.PushMatrix() + rl.BeginScissorMode(int32(offscreen.Bounds.X), int32(offscreen.Bounds.Y), int32(offscreen.Bounds.Width), int32(offscreen.Bounds.Height)) + rl.Translatef(float32(offscreen.Bounds.UL().X), offscreen.Bounds.UL().Y, 0) + rl.DrawTexturePro(s.composite.Texture, viewport.ToRL(), outputRect.ToRL(), rl.Vector2{}, 0, rl.White) + rl.EndScissorMode() + rl.PopMatrix() + **/ - output.End() + env.Window.Begin() + env.Window.TransferTexture(s.composite.Texture, viewport, outputRect, rl.White) + env.Window.SetFill(false) + env.Window.SetStroke(true) + env.Window.SetStrokeColor(rl.Gray) + env.Window.DrawRect(outputRect) + env.Window.End() + + /* + env.Window.Begin() + env.Window.SetStroke(true) + env.Window.SetFill(false) + env.Window.SetStrokeWeight(5.0) + env.Window.SetStrokeColor(rl.Magenta) + env.Window.DrawRect(outputRect) + env.Window.End() + */ } // calculate the visible clip of the offscreen buffer based on the camera /zoom func (s *Sketch) CalcViewport(env *Env) sg.Rect { viewportWidth := rl.Clamp(env.Offscreen.Width()/s.cam.Zoom, 0, env.Offscreen.Width()) - viewportHeight := rl.Clamp(env.Offscreen.Height()/s.cam.Zoom, 0, env.Offscreen.Width()) + viewportHeight := rl.Clamp(env.Offscreen.Height()/s.cam.Zoom, 0, env.Offscreen.Height()) return sg.Rect{ X: rl.Clamp(s.cam.LookAt.X-viewportWidth/2.0, 0, env.Layout.Offscreen.Width-viewportWidth), Y: rl.Clamp(s.cam.LookAt.Y-viewportHeight/2.0, 0, env.Layout.Offscreen.Height-viewportHeight), Width: viewportWidth, - Height: -viewportHeight, + Height: viewportHeight, } } @@ -325,7 +354,10 @@ func (cl *ColorLayer) Update(ctx *Env, g *sg.Graphics) { } func (cl *ColorLayer) Draw(ctx *Env, g *sg.Graphics) { - g.Background(cl.color) + g.SetFill(true) + g.SetFillColor(cl.color) + g.SetStroke(false) + g.DrawRect(g.Bounds) //rl.ClearBackground(cl.color) cl.dirty = false } @@ -353,10 +385,12 @@ func (il *ImageLayer) Update(ctx *Env, g *sg.Graphics) { } func (il *ImageLayer) Draw(env *Env, g *sg.Graphics) { + rl.PushMatrix() rl.Translatef( g.Width()/2.0-float32(il.texture.Width)/2.0, - g.Width()/2.0-float32(il.texture.Height)/2.0, 0) + g.Height()/2.0-float32(il.texture.Height)/2.0, 0) g.DrawTexture(il.texture, sg.Origin, rl.White) + rl.PopMatrix() } func (il *ImageLayer) IsDirty() bool {