themes and styles
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
package world
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type bgOffsetScaleTheme struct {
|
||||
img image.Image
|
||||
anchor BackgroundAnchorMode
|
||||
}
|
||||
|
||||
func (t bgOffsetScaleTheme) ID() string { return "bgoffset" }
|
||||
func (t bgOffsetScaleTheme) Name() string { return "bgoffset" }
|
||||
|
||||
func (t bgOffsetScaleTheme) BackgroundColor() color.Color { return color.RGBA{A: 255} }
|
||||
func (t bgOffsetScaleTheme) BackgroundImage() image.Image { return t.img }
|
||||
|
||||
func (t bgOffsetScaleTheme) BackgroundTileMode() BackgroundTileMode { return BackgroundTileRepeat }
|
||||
func (t bgOffsetScaleTheme) BackgroundScaleMode() BackgroundScaleMode { return BackgroundScaleNone }
|
||||
func (t bgOffsetScaleTheme) BackgroundAnchorMode() BackgroundAnchorMode { return t.anchor }
|
||||
|
||||
func (t bgOffsetScaleTheme) PointStyle() Style {
|
||||
return Style{FillColor: color.RGBA{A: 255}, PointRadiusPx: 2}
|
||||
}
|
||||
func (t bgOffsetScaleTheme) LineStyle() Style {
|
||||
return Style{StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1}
|
||||
}
|
||||
func (t bgOffsetScaleTheme) CircleStyle() Style {
|
||||
return Style{FillColor: color.RGBA{A: 255}, StrokeColor: color.RGBA{A: 255}, StrokeWidthPx: 1}
|
||||
}
|
||||
|
||||
func (t bgOffsetScaleTheme) PointClassOverride(PointClassID) (StyleOverride, bool) {
|
||||
return StyleOverride{}, false
|
||||
}
|
||||
func (t bgOffsetScaleTheme) LineClassOverride(LineClassID) (StyleOverride, bool) {
|
||||
return StyleOverride{}, false
|
||||
}
|
||||
func (t bgOffsetScaleTheme) CircleClassOverride(CircleClassID) (StyleOverride, bool) {
|
||||
return StyleOverride{}, false
|
||||
}
|
||||
|
||||
func TestRender_BackgroundTileRepeat_WorldAnchored_ShiftsWithPan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := NewWorld(20, 20)
|
||||
w.resetGrid(2 * SCALE)
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, 4, 4)) // tile 4x4
|
||||
w.SetTheme(bgOffsetScaleTheme{img: img, anchor: BackgroundAnchorWorld})
|
||||
|
||||
params := RenderParams{
|
||||
ViewportWidthPx: 8,
|
||||
ViewportHeightPx: 8,
|
||||
MarginXPx: 0,
|
||||
MarginYPx: 0,
|
||||
CameraXWorldFp: 5 * SCALE,
|
||||
CameraYWorldFp: 5 * SCALE,
|
||||
CameraZoom: 1.0,
|
||||
}
|
||||
|
||||
// First render.
|
||||
d1 := &fakePrimitiveDrawer{}
|
||||
require.NoError(t, w.Render(d1, params))
|
||||
|
||||
minX1, minY1 := minDrawImageXY(t, d1)
|
||||
require.Equal(t, -1, minX1)
|
||||
require.Equal(t, -1, minY1)
|
||||
|
||||
// Pan camera by +1 world unit along both axes (zoom=1 => 1px).
|
||||
params2 := params
|
||||
params2.CameraXWorldFp += 1 * SCALE
|
||||
params2.CameraYWorldFp += 1 * SCALE
|
||||
|
||||
// Force full redraw to make this test independent of incremental pipeline.
|
||||
w.ForceFullRedrawNext()
|
||||
|
||||
d2 := &fakePrimitiveDrawer{}
|
||||
require.NoError(t, w.Render(d2, params2))
|
||||
|
||||
minX2, minY2 := minDrawImageXY(t, d2)
|
||||
|
||||
// With world anchoring, moving camera +1 shifts the tiling origin by -1 (mod tile size).
|
||||
require.Equal(t, -2, minX2)
|
||||
require.Equal(t, -2, minY2)
|
||||
}
|
||||
|
||||
func TestRender_BackgroundTileRepeat_ViewportAnchored_DoesNotShiftWithPan(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
w := NewWorld(20, 20)
|
||||
w.resetGrid(2 * SCALE)
|
||||
|
||||
img := image.NewRGBA(image.Rect(0, 0, 4, 4))
|
||||
w.SetTheme(bgOffsetScaleTheme{img: img, anchor: BackgroundAnchorViewport})
|
||||
|
||||
params := RenderParams{
|
||||
ViewportWidthPx: 8,
|
||||
ViewportHeightPx: 8,
|
||||
MarginXPx: 0,
|
||||
MarginYPx: 0,
|
||||
CameraXWorldFp: 5 * SCALE,
|
||||
CameraYWorldFp: 5 * SCALE,
|
||||
CameraZoom: 1.0,
|
||||
}
|
||||
|
||||
d1 := &fakePrimitiveDrawer{}
|
||||
require.NoError(t, w.Render(d1, params))
|
||||
|
||||
minX1, minY1 := minDrawImageXY(t, d1)
|
||||
|
||||
params2 := params
|
||||
params2.CameraXWorldFp += 1 * SCALE
|
||||
params2.CameraYWorldFp += 1 * SCALE
|
||||
|
||||
w.ForceFullRedrawNext()
|
||||
|
||||
d2 := &fakePrimitiveDrawer{}
|
||||
require.NoError(t, w.Render(d2, params2))
|
||||
|
||||
minX2, minY2 := minDrawImageXY(t, d2)
|
||||
|
||||
// With viewport anchoring, tiling origin is fixed (no camera dependency).
|
||||
require.Equal(t, minX1, minX2)
|
||||
require.Equal(t, minY1, minY2)
|
||||
}
|
||||
|
||||
func minDrawImageXY(t *testing.T, d *fakePrimitiveDrawer) (int, int) {
|
||||
t.Helper()
|
||||
|
||||
cmds := d.CommandsByName("DrawImage")
|
||||
require.NotEmpty(t, cmds, "expected DrawImage calls from background tiling")
|
||||
|
||||
minX := int(cmds[0].Args[0])
|
||||
minY := int(cmds[0].Args[1])
|
||||
|
||||
for _, c := range cmds[1:] {
|
||||
x := int(c.Args[0])
|
||||
y := int(c.Args[1])
|
||||
if x < minX {
|
||||
minX = x
|
||||
}
|
||||
if y < minY {
|
||||
minY = y
|
||||
}
|
||||
}
|
||||
return minX, minY
|
||||
}
|
||||
Reference in New Issue
Block a user