doodle/pkg/uix/canvas_wallpaper.go
Noah Petherbridge ed492a4451 Progress on the Zoom In/Out Feature
* Got the level chunks AND the wallpaper to both scale UP and DOWN
  consistently together.
* Trying to draw new pixels while zoomed in/out ends up offsetting the
  pixels by 2X still. Still seems an issue between screen coordinates
  and world coordinates. Zoom in 2X and try and draw a line 64px from
  the corners of the screen? The committed line appropriately lands at
  the 64px coord on the level data but, zoomed in, it appears 2X to the
  right on the screen from where I dropped the cursor!
* When zooming OUT, the limit on number of chunks the viewport will try
  and render is not increased, leaving dead space in the screen; more
  chunks should render when there's room.
2021-07-11 21:54:28 -07:00

311 lines
7.9 KiB
Go

package uix
import (
"git.kirsle.net/apps/doodle/pkg/level"
"git.kirsle.net/apps/doodle/pkg/wallpaper"
"git.kirsle.net/go/render"
)
// Wallpaper configures the wallpaper in a Canvas.
type Wallpaper struct {
pageType level.PageType
maxWidth int64
maxHeight int64
corner render.Texturer
top render.Texturer
left render.Texturer
repeat render.Texturer
}
// Valid returns whether the Wallpaper is configured. Only Levels should
// have wallpapers and Doodads will have nil ones.
func (wp *Wallpaper) Valid() bool {
return wp.repeat != nil
}
// Canvas Loop() task that keeps mobile actors constrained inside the borders
// of the world for bounded map types.
func (w *Canvas) loopContainActorsInsideLevel(a *Actor) {
// Infinite maps do not need to constrain the actors.
if w.wallpaper.pageType == level.Unbounded {
return
}
var (
orig = a.Position() // Actor's World Position
moveBy render.Point
size = a.Size()
)
// Bound it on the top left edges.
if orig.X < 0 {
moveBy.X = -orig.X
}
if orig.Y < 0 {
moveBy.Y = -orig.Y
}
// Bound it on the right bottom edges. XXX: downcast from int64!
if w.wallpaper.pageType >= level.Bounded {
if w.wallpaper.maxWidth > 0 {
if int64(orig.X+size.W) > w.wallpaper.maxWidth {
var delta = w.wallpaper.maxWidth - int64(orig.X+size.W)
moveBy.X = int(delta)
}
}
if w.wallpaper.maxHeight > 0 {
if int64(orig.Y+size.H) > w.wallpaper.maxHeight {
var delta = w.wallpaper.maxHeight - int64(orig.Y+size.H)
moveBy.Y = int(delta)
// Allow them to jump from the bottom by marking them as grounded.
a.SetGrounded(true)
}
}
}
if !moveBy.IsZero() {
a.MoveBy(moveBy)
}
}
// PresentWallpaper draws the wallpaper.
// Point p is the one given to Canvas.Present(), i.e., the position of the
// top-left corner of the Canvas widget relative to the application window.
func (w *Canvas) PresentWallpaper(e render.Engine, p render.Point) error {
var (
wp = w.wallpaper
S = w.Size()
size = wp.corner.Size()
sizeOrig = wp.corner.Size()
// Get the relative viewport of world coordinates looked at by the canvas.
// The X,Y values are the negative Scroll value
// The W,H values are the Canvas size same as var S above.
Viewport = w.ViewportRelative()
// origin and limit seem to be the boundaries of where on screen
// we are rendering inside.
origin = render.Point{
X: p.X + w.Scroll.X, // + w.BoxThickness(1),
Y: p.Y + w.Scroll.Y, // + w.BoxThickness(1),
}
limit render.Point // TBD later
)
// Grow or shrink the render limit if we're zoomed.
if w.Zoom != 0 {
// I was surprised to discover that just zooming the texture
// quadrant size handled most of the problem! For reference, the
// Blueprint wallpaper has a size of 120x120 for the tiling pattern.
size.H = w.ZoomMultiply(size.H)
size.W = w.ZoomMultiply(size.W)
}
// SCRATCH
// at bootup, scroll position 0,0:
// origin=44,20 p=44,20 p=relative to application window
// scroll right and down to -60,-60:
// origin=-16,-40 p=44,20 and looks good in that direction
// scroll left and up to 60,60:
// origin=104,80 p=44,20
// becomes origin=44,20 p=44,20 d=-16,-40
// the latter case is handled below. walking thru:
// if o(104) > p(44):
// while o(104) > p(44):
// o -= size(120) of texture block
// o is now -16,-40
// while o(-16) > p(44): it's not; break
// dx = o(-16)
// origin.X = p.X
// (becomes origin=44,20 p=44,20 d=-16,-40)
//
// The visual bug is: if you scroll left or up on an Unbounded level from
// the origin (0, 0), the tiling of the wallpaper jumps to the right and
// down by an offset of 44x20 pixels.
//
// what is meant to happen:
// -
// For tiled textures, compute the offset amount. If we are scrolled away
// from the Origin (0,0) we find out by how far (subtract full tile sizes)
// and use the remainder as an offset for drawing the tiles.
// p = position on screen of the Canvas widget
// origin = p.X + Scroll.X, p.Y + scroll.Y
// note: negative Scroll values means to the right and down
var dx, dy int
if origin.X > p.X {
// View is scrolled leftward (into negative world coordinates)
dx = origin.X
for dx > p.X {
dx -= size.W
}
origin.X = 0 // note: origin 0,0 will be the corner of the app window
}
if origin.Y > p.Y {
// View is scrolled upward (into negative world coordinates)
dy = origin.Y
for dy > p.Y {
dy -= size.H
}
origin.Y = 0
}
limit = render.Point{
// NOTE: we add + the texture size so we would actually draw one
// full extra texture out-of-bounds for the repeating backgrounds.
// This is cuz for scrolling we offset the draw spot on a loop.
X: origin.X + S.W + size.W,
Y: origin.Y + S.H + size.H,
}
// And capping the scroll delta in the other direction. Always draw
// pixels until the Canvas size is covered.
if limit.X < S.W {
limit.X = S.W
}
if limit.Y < S.H {
// TODO: slight flicker on bottom edge when scrolling down
limit.Y = S.H
}
// TODO: was still getting some slight flicker on the right and bottom
// when scrolling.. add a bit extra margin.
limit.X += size.W
limit.Y += size.H
// Tile the repeat texture. Start from 1 full wallpaper tile out of bounds
for x := origin.X - size.W; x < limit.X; x += size.W {
for y := origin.Y - size.H; y < limit.Y; y += size.H {
src := render.Rect{
W: size.W,
H: size.H,
}
dst := render.Rect{
X: x + dx,
Y: y + dy,
W: src.W,
H: src.H,
}
// Trim the edges of the destination box, like in canvas.go#Present
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
// When zooming OUT, make sure the source rect is at least the
// full size of the chunk texture; otherwise the ZoomMultiplies
// above do correctly scale e.g. 128x128 to 64x64, but it only
// samples the top-left 64x64 then and not the full texture so
// it more crops it than scales it, but does fit it neatly with
// its neighbors.
if w.Zoom < 0 {
src.W = sizeOrig.W
src.H = sizeOrig.H
}
e.Copy(wp.repeat, src, dst)
}
}
// The left edge corner tiled along the left edge.
if wp.pageType > level.Unbounded {
for y := origin.Y; y < limit.Y; y += size.H {
src := render.Rect{
W: size.W,
H: size.H,
}
dst := render.Rect{
X: origin.X,
Y: y + dy,
W: src.W,
H: src.H,
}
// Zoom-out min size constraint.
if w.Zoom < 0 {
src.W = sizeOrig.W
src.H = sizeOrig.H
}
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
e.Copy(wp.left, src, dst)
}
// The top edge tiled along the top edge.
for x := origin.X; x < limit.X; x += size.W {
src := render.Rect{
W: size.W,
H: size.H,
}
dst := render.Rect{
X: x,
Y: origin.Y,
W: src.W,
H: src.H,
}
// Zoom-out min size constraint.
if w.Zoom < 0 {
src.W = sizeOrig.W
src.H = sizeOrig.H
}
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
e.Copy(wp.top, src, dst)
}
// The top left corner for all page types except Unbounded.
if Viewport.Intersects(size) {
src := render.Rect{
W: size.W,
H: size.H,
}
dst := render.Rect{
X: origin.X,
Y: origin.Y,
W: src.W,
H: src.H,
}
// Zoom out min size constraint.
if w.Zoom < 0 {
src.W = sizeOrig.W
src.H = sizeOrig.H
}
render.TrimBox(&src, &dst, p, S, w.BoxThickness(1))
e.Copy(wp.corner, src, dst)
}
}
return nil
}
// Load the wallpaper settings from a level.
func (wp *Wallpaper) Load(e render.Engine, pageType level.PageType, v *wallpaper.Wallpaper) error {
wp.pageType = pageType
if tex, err := v.CornerTexture(e); err == nil {
wp.corner = tex
} else {
return err
}
if tex, err := v.TopTexture(e); err == nil {
wp.top = tex
} else {
return err
}
if tex, err := v.LeftTexture(e); err == nil {
wp.left = tex
} else {
return err
}
if tex, err := v.RepeatTexture(e); err == nil {
wp.repeat = tex
} else {
return err
}
return nil
}