doodle/pkg/uix/canvas_wallpaper.go

317 lines
8.4 KiB
Go

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
}