package uix import ( "git.kirsle.net/SketchyMaze/doodle/pkg/balance" "git.kirsle.net/SketchyMaze/doodle/pkg/level" "git.kirsle.net/SketchyMaze/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 // Pointer to the Wallpaper datum. WP *wallpaper.Wallpaper } // 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.WP != nil && wp.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() playerOOB bool // player character is out of bounds ) // Bound it on the top left edges. if orig.X < 0 { if orig.X > -balance.OutOfBoundsMargin { moveBy.X = -orig.X } else if a.IsPlayer() { playerOOB = true } } if orig.Y < 0 { if orig.Y > -balance.OutOfBoundsMargin { moveBy.Y = -orig.Y } else if a.IsPlayer() { playerOOB = true } } // 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) if delta > int64(-balance.OutOfBoundsMargin) { moveBy.X = int(delta) } else if a.IsPlayer() { playerOOB = true } } } if w.wallpaper.maxHeight > 0 { if int64(orig.Y+size.H) > w.wallpaper.maxHeight { var delta = w.wallpaper.maxHeight - int64(orig.Y+size.H) if delta > int64(-balance.OutOfBoundsMargin) { moveBy.Y = int(delta) // Allow them to jump from the bottom by marking them as grounded. a.SetGrounded(true) } else if a.IsPlayer() { playerOOB = true } } } } if !moveBy.IsZero() && !(a.IsPlayer() && playerOOB) { a.MoveBy(moveBy) } // If the player doodad is far out of bounds, tag it as such and // the canvas will allow scrolling OOB to see the player. w.scrollOutOfBounds = playerOOB } // 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.WP.QuarterRect() sizeOrig = wp.WP.QuarterRect() // 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 } if tex, err := wp.WP.RepeatTexture(e); err == nil { e.Copy(tex, 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)) if tex, err := wp.WP.LeftTexture(e); err == nil { e.Copy(tex, 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)) if tex, err := wp.WP.TopTexture(e); err == nil { e.Copy(tex, 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)) if tex, err := wp.WP.CornerTexture(e); err == nil { e.Copy(tex, src, dst) } } } return nil } // Load the wallpaper settings from a level. func (wp *Wallpaper) Load(pageType level.PageType, v *wallpaper.Wallpaper) error { wp.pageType = pageType wp.WP = v return nil }