package main import ( "math" "github.com/gen2brain/raylib-go/raylib" ) type Sketch struct { sourceWidth int32 sourceHeight int32 cam *TextureCam layerTools map[string]LayerTools layerToolsOrdered []LayerTools composite rl.RenderTexture2D } type TextureCam struct { LookAt rl.Vector2 Zoom float32 } func NewSketch(sourceWidth, sourceHeight int32) Sketch { // point at source center // put source center at center of screen var camera = TextureCam { LookAt: rl.Vector2{X: float32(sourceWidth) / 2.0, Y: float32(sourceHeight) / 2.0}, Zoom: 1.0, } return Sketch { sourceWidth: sourceWidth, sourceHeight: sourceHeight, layerTools: make(map[string]LayerTools), layerToolsOrdered: []LayerTools {}, composite: rl.LoadRenderTexture(sourceWidth, sourceHeight), cam: &camera, } } func (s *Sketch) CreateLayer(name string, layer Layer) { texture := rl.LoadRenderTexture(s.sourceWidth, s.sourceHeight) layerTools := LayerTools { name: name, texture: &texture, layer: layer, } s.layerToolsOrdered = append(s.layerToolsOrdered, layerTools) s.layerTools[name] = layerTools } func (s *Sketch) Draw(ctx *RenderCtx) { // render onto all layer textures for _, instance := range s.layerToolsOrdered { instance.layer.Update(ctx) layer := instance.layer if instance.layer.IsDirty() { rl.BeginTextureMode(*instance.texture) layer.Draw(ctx) rl.EndTextureMode() } } // composite all layers to screen outputRect := s.calcOutputRectKeepingAspectRatio(ctx) // copy from full texture for compositing, with vertical flipping src := rl.Rectangle { X: 0, Y: 0, Width: float32(ctx.SourceWidth), Height: -float32(ctx.SourceHeight), } dst := rl.Rectangle { X: 0, Y: 0, Width: float32(ctx.SourceWidth), Height: float32(ctx.SourceHeight), } viewport := s.CalcViewport(ctx) rl.BeginTextureMode(s.composite) rl.ClearBackground(rl.Black) for _, instance := range s.layerToolsOrdered { rl.DrawTexturePro(instance.texture.Texture, src, dst, rl.Vector2{}, 0, rl.White) } rl.EndTextureMode() rl.DrawTexturePro(s.composite.Texture, viewport, outputRect, rl.Vector2{}, 0, rl.White) outlineRect := outputRect.ToInt32() rl.DrawRectangleLines(outlineRect.X, outlineRect.Y, outlineRect.Width, outlineRect.Height, rl.Gray) } func (s *Sketch) calcOutputRectKeepingAspectRatio(ctx *RenderCtx) rl.Rectangle { sourceAspect := float32(ctx.SourceWidth) / float32(ctx.SourceHeight) targetAspect := ctx.TargetBounds.Width / ctx.TargetBounds.Height outputWidth := ctx.TargetBounds.Width outputHeight := ctx.TargetBounds.Height if sourceAspect < targetAspect { // source is relatively taller than the target // so we set the output height to the target height // and calculate the width based on source aspect and center outputWidth = outputHeight * sourceAspect } else { // source is relatively wider than the target // so we set the output width to the target width // and calculate the height based on source aspect and center outputHeight = outputWidth / sourceAspect } // output width and height are correct -- center within TargetBounds x := ctx.TargetBounds.X + ctx.TargetBounds.Width / 2.0 - outputWidth / 2.0 y := ctx.TargetBounds.Y + ctx.TargetBounds.Height / 2.0 - outputHeight / 2.0 return rl.Rectangle { X: x, Y: y, Width: outputWidth, Height: outputHeight, } } func (s *Sketch) CalcViewport(ctx *RenderCtx) rl.Rectangle { viewportWidth := rl.Clamp(float32(ctx.SourceWidth) / s.cam.Zoom, 0, float32(ctx.SourceWidth)) viewportHeight := rl.Clamp(float32(ctx.SourceHeight) / s.cam.Zoom, 0, float32(ctx.SourceHeight)) return rl.Rectangle{ X: rl.Clamp(s.cam.LookAt.X - viewportWidth/2.0, 0, float32(ctx.SourceWidth)-viewportWidth), Y: rl.Clamp(s.cam.LookAt.Y - viewportHeight/2.0, 0, float32(ctx.SourceHeight)-viewportHeight), Width: float32(viewportWidth), Height: -float32(viewportHeight), } } func (s *Sketch) Update(ctx *RenderCtx) { if rl.IsMouseButtonDown(rl.MouseRightButton) { // get mouse delta from last frame delta := rl.GetMouseDelta() sourceScale := float32(ctx.SourceWidth) / ctx.TargetBounds.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.SourceWidth-1)) s.cam.LookAt.Y = rl.Clamp(s.cam.LookAt.Y, 0, float32(ctx.SourceHeight-1)) // Zoom based on mouse wheel wheel := rl.GetMouseWheelMove() if wheel != 0 { const zoomIncrement float32 = 0.05 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.sourceWidth) / 2.0, Y: float32(s.sourceHeight) / 2.0} s.cam.Zoom = 1.0 } type SketchCapture struct { compositeImage *rl.Image layerImages []*rl.Image } func (s *Sketch) Capture() *SketchCapture { composite := rl.LoadImageFromTexture(s.composite.Texture) rl.ImageFlipVertical(composite) layerImages := make([]*rl.Image, len(s.layerToolsOrdered)) for i, layerTool := range s.layerToolsOrdered { layerImages[i] = rl.LoadImageFromTexture(layerTool.texture.Texture) rl.ImageFlipVertical(layerImages[i]) } return &SketchCapture { compositeImage: composite, layerImages: layerImages, } } type LayerTools struct { name string layer Layer texture *rl.RenderTexture2D } /** Layer **/ type Layer interface { Update(ctx *RenderCtx) Draw(ctx *RenderCtx) IsDirty() bool } type TestPattern struct{ dirty bool } func (tp *TestPattern) Update(ctx *RenderCtx) { ; } func (tp *TestPattern) Draw(ctx *RenderCtx) { rl.ClearBackground(rl.Black) centerX := float32(ctx.SourceWidth) / 2 centerY := float32(ctx.SourceHeight) / 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.SourceWidth, ctx.SourceHeight, 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 } /** RenderCtx **/ type RenderCtx struct { TargetBounds rl.Rectangle SourceWidth int32 SourceHeight int32 Time float64 Ports map[string]float64 }