package main import ( "fmt" sg "github.com/d2fn/sumi/internal/graphics" "github.com/gen2brain/raylib-go/raylib" "math" ) type Sketch struct { graphics *sg.Graphics cam *TextureCam composite rl.RenderTexture2D layerTools map[string]*LayerTools layerToolsOrdered []*LayerTools } type TextureCam struct { LookAt rl.Vector2 Zoom float32 } type LayerTools struct { name string layer Layer texture rl.RenderTexture2D capture *rl.Image config *LayerConfig } type LayerConfig struct { visible bool a uint8 rVisible bool r uint8 gVisible bool g uint8 bVisible bool b uint8 desaturate bool saturation float32 kValue float32 } func NewSketch(g *sg.Graphics) Sketch { // point at source center // put source center at center of screen var camera = TextureCam{ LookAt: rl.Vector2{X: float32(g.Layout.Graphics.Width) / 2.0, Y: float32(g.Layout.Graphics.Height) / 2.0}, Zoom: 1.0, } return Sketch{ graphics: g, layerTools: make(map[string]*LayerTools), layerToolsOrdered: []*LayerTools{}, composite: rl.LoadRenderTexture(int32(g.Layout.Graphics.Width), int32(g.Layout.Graphics.Height)), cam: &camera, } } func (s *Sketch) AddLayer(name string, layer Layer) { texture := rl.LoadRenderTexture(int32(s.graphics.Layout.Graphics.Width), int32(s.graphics.Layout.Graphics.Height)) config := NewLayerConfig() layerTools := LayerTools{ name: name, texture: texture, layer: layer, config: &config, } s.layerToolsOrdered = append(s.layerToolsOrdered, &layerTools) s.layerTools[name] = &layerTools } func (s *Sketch) AddColorLayer(name string, c rl.Color) { colorLayer := &ColorLayer{ color: c, dirty: true, } s.AddLayer(name, colorLayer) } func (s *Sketch) Redraw(ctx *Env) { g := ctx.Graphics // render onto all layer textures for _, instance := range s.layerToolsOrdered { layer := instance.layer // ignore this layer entirely unless it's visible if instance.config.visible { layer.Update(ctx) // re-render to texture if dirty if instance.layer.IsDirty() { g.PushMatrix() g.BeginTexture(instance.texture) layer.Draw(ctx) g.PopMatrix() g.EndTexture() } } } } func (s *Sketch) Draw(ctx *Env) { g := ctx.Graphics s.Redraw(ctx) // copy from full texture for compositing, with vertical flipping src := g.Layout.Graphics src.Height = -src.Height dst := g.Layout.Graphics /* src := g.Rect { X: 0, Y: 0, Width: float32(ctx.SourceWidth), Height: -float32(ctx.SourceHeight), } dst := g.Rect{ X: 0, Y: 0, Width: float32(ctx.SourceWidth), Height: float32(ctx.SourceHeight), } */ viewport := s.CalcViewport(ctx) outputRect := g.Layout.Graphics.ScaleTo(g.Layout.Viewport) fmt.Printf("outputRect = %v\n", outputRect) x := float32(0) y := float32(0) w := outputRect.Width h := outputRect.Height g.PushMatrix() g.Translate(outputRect.UL()) g.BeginClip(outputRect) checkSize := float32(25.0) grey := rl.NewColor(220, 220, 220, 255) cellX := 0 cellY := 0 for y < h { x = 0 cellX = 0 for x < w { c := rl.White if ((cellX + cellY) & 1) == 1 { c = grey } rl.DrawRectangle(int32(x), int32(y), int32(checkSize), int32(checkSize), c) x += checkSize cellX++ } y += checkSize cellY++ } g.EndClip() g.PopMatrix() rl.BeginBlendMode(rl.BlendAlphaPremultiply) //rl.BeginBlendMode(rl.BlendAlpha) rl.BeginTextureMode(s.composite) rl.ClearBackground(rl.Blank) //rl.ClearBackground(rl.Black) for _, instance := range s.layerToolsOrdered { config := instance.config if config.visible { var r uint8 = 0 if config.rVisible { r = config.r } var g uint8 = 0 if config.gVisible { g = config.g } var b uint8 = 0 if config.bVisible { b = config.b } r = uint8(float32(r) * (float32(config.a) / 255.0)) 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) rl.DrawTexturePro(instance.texture.Texture, src.ToRL(), dst.ToRL(), rl.Vector2{}, 0, tint) } } rl.EndTextureMode() rl.EndBlendMode() rl.GenTextureMipmaps(&s.composite.Texture) rl.SetTextureFilter(s.composite.Texture, rl.FilterTrilinear) rl.DrawTexturePro(s.composite.Texture, viewport.ToRL(), outputRect.ToRL(), rl.Vector2{}, 0, rl.White) outputRectRL := outputRect.ToRL() outlineRect := (&outputRectRL).ToInt32() rl.DrawRectangleLines(outlineRect.X, outlineRect.Y, outlineRect.Width, outlineRect.Height, rl.Gray) } func (s *Sketch) CalcViewport(ctx *Env) sg.Rect { viewportWidth := rl.Clamp(float32(ctx.Graphics.Layout.Graphics.Width)/s.cam.Zoom, 0, float32(ctx.Graphics.Layout.Graphics.Width)) viewportHeight := rl.Clamp(float32(ctx.Graphics.Layout.Graphics.Height)/s.cam.Zoom, 0, float32(ctx.Graphics.Layout.Graphics.Height)) return sg.Rect{ X: rl.Clamp(s.cam.LookAt.X-viewportWidth/2.0, 0, float32(ctx.Graphics.Layout.Graphics.Width)-viewportWidth), Y: rl.Clamp(s.cam.LookAt.Y-viewportHeight/2.0, 0, float32(ctx.Graphics.Layout.Graphics.Height)-viewportHeight), Width: float32(viewportWidth), Height: -float32(viewportHeight), } } func (s *Sketch) Update(ctx *Env) { if rl.IsMouseButtonDown(rl.MouseRightButton) { // get mouse delta from last frame delta := rl.GetMouseDelta() sourceScale := float32(ctx.Graphics.Layout.Graphics.Width) / float32(ctx.Graphics.Layout.Viewport.Width) // compute the amount to move scaled by the camera zoom delta = rl.Vector2Scale(delta, -sourceScale/s.cam.Zoom) delta.Y = -delta.Y s.cam.LookAt = rl.Vector2Add(s.cam.LookAt, delta) } // clamp LookAt to be somewhere on the texture s.cam.LookAt.X = rl.Clamp(s.cam.LookAt.X, 0, float32(ctx.Graphics.Layout.Graphics.Width)) s.cam.LookAt.Y = rl.Clamp(s.cam.LookAt.Y, 0, float32(ctx.Graphics.Layout.Graphics.Height)) // Zoom based on mouse wheel wheel := rl.GetMouseWheelMove() if wheel != 0 { const zoomIncrement float32 = 0.20 if wheel > 0 { s.cam.Zoom *= 1 + zoomIncrement } else { s.cam.Zoom *= 1 - zoomIncrement } } // clamp zoom to > 1 so we don't ever zoom out more than necessary s.cam.Zoom = rl.Clamp(s.cam.Zoom, 1, math.MaxInt64) } func (s *Sketch) ResetCamera() { s.cam.LookAt = rl.Vector2{X: float32(s.graphics.Layout.Graphics.Width) / 2.0, Y: float32(s.graphics.Layout.Graphics.Height) / 2.0} s.cam.Zoom = 1.0 } type SketchCapture struct { width, height int32 compositeImage *rl.Image layerTools map[string]*LayerTools layerToolsOrdered []*LayerTools } func (s *Sketch) Capture() *SketchCapture { composite := rl.LoadImageFromTexture(s.composite.Texture) rl.ImageFlipVertical(composite) for _, layerTool := range s.layerToolsOrdered { layerTool.capture = rl.LoadImageFromTexture(layerTool.texture.Texture) rl.ImageFlipVertical(layerTool.capture) } return &SketchCapture{ width: s.graphics.GetGraphicsWidth(), height: s.graphics.GetGraphicsHeight(), compositeImage: composite, layerTools: s.layerTools, layerToolsOrdered: s.layerToolsOrdered, } } func NewLayerConfig() LayerConfig { return LayerConfig{ visible: true, a: 255, rVisible: true, r: 255, gVisible: true, g: 255, bVisible: true, b: 255, desaturate: false, kValue: 1.0, } } /** Layer **/ type Layer interface { Update(ctx *Env) Draw(ctx *Env) IsDirty() bool } type ColorLayer struct { color rl.Color dirty bool } func (cl *ColorLayer) Update(ctx *Env) { } func (cl *ColorLayer) Draw(ctx *Env) { rl.ClearBackground(cl.color) cl.dirty = false } func (cl *ColorLayer) IsDirty() bool { return cl.dirty } type ImageLayer struct { texture rl.Texture2D dirty bool } func NewImageLayer(path string) *ImageLayer { image := rl.LoadImage(path) tex := rl.LoadTextureFromImage(image) return &ImageLayer{ texture: tex, dirty: true, } } func (il *ImageLayer) Update(ctx *Env) { } func (il *ImageLayer) Draw(ctx *Env) { rl.Translatef(float32(ctx.GetGraphicsWidth())/2.0-float32(il.texture.Width)/2.0, float32(ctx.GetGraphicsHeight())/2.0-float32(il.texture.Height)/2.0, 0) rl.DrawTexture(il.texture, 0, 0, rl.White) } func (il *ImageLayer) IsDirty() bool { return il.dirty } type TestPattern struct { dirty bool } func (tp *TestPattern) Update(ctx *Env) { } func (tp *TestPattern) Draw(ctx *Env) { rl.ClearBackground(rl.Black) centerX := float32(ctx.GetGraphicsWidth()) / 2 centerY := float32(ctx.GetGraphicsHeight()) / 2 rl.DrawRectangleRec(rl.Rectangle{X: 0, Y: 0, Width: centerX, Height: centerY}, rl.Red) rl.DrawRectangleRec(rl.Rectangle{X: centerX, Y: 0, Width: centerX, Height: centerY}, rl.Green) rl.DrawRectangleRec(rl.Rectangle{X: 0, Y: centerY, Width: centerX, Height: centerY}, rl.Blue) rl.DrawRectangleRec(rl.Rectangle{X: centerX, Y: centerY, Width: centerX, Height: centerY}, rl.White) rl.DrawLine(0, 0, ctx.Graphics.GetGraphicsWidth(), ctx.Graphics.GetGraphicsHeight(), rl.Black) rl.PushMatrix() rl.Translatef(centerX, centerY, 0) rl.SetLineWidth(4.0) rl.DrawLine(-10000, 0, 10000, 0, rl.Red) rl.DrawLine(0, -10000, 0, 10000, rl.Green) rl.DrawRectangleLines(-50, -50, 100, 100, rl.Magenta) rl.PopMatrix() tp.dirty = false } func (tp *TestPattern) IsDirty() bool { return tp.dirty } /** Ports **/ type Ports map[string]Signal func MakePorts() Ports { return make(Ports) } /** * materialize current value for all ports **/ func (p Ports) Eval(t float64) map[string]float64 { out := make(map[string]float64, len(p)) for name, sig := range p { out[name] = sig.Eval(t) } return out }