Files
galaxy-game/client/world/renderer_background_offset_scale_test.go
T
2026-03-08 15:31:17 +02:00

152 lines
4.0 KiB
Go

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
}