fixed scaling issue on android
DramaticShape · 2026-07-26 ·
parent cc134a2a84
fixed scaling issue on android
Changed files
CHANGELOG.md | 28 ++++++++++++++++++++++++++++
main.lua | 39 ++++++++++++++++++++++++++++++++++-----
manifest.json | 2 +-
3 files changed, 63 insertions(+), 6 deletions(-)
Diff
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 35bf56d..dea4d22 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,33 @@
# Changelog
+## 1.0.2
+
+### Fixed
+
+- On Android the diorama drew into the top-left corner at a fraction of the
+ screen -- about a third of the width and height on a 420dpi panel -- with
+ the field effects (dust, emotes, the cut-tree shudder) correspondingly
+ oversized against the world they sat on. Desktop was unaffected.
+
+ The pipeline ctx hands over `width`/`height` measured in LOVE UNITS
+ (`love.graphics.getDimensions`), but the engine composites a pipeline's
+ returned canvas with `draw(canvas, 0, 0, 0, 1/dpiX, 1/dpiY)` -- a scale
+ that only covers the window if the canvas is at PIXEL resolution. Sizing
+ the scene canvas from the ctx therefore paid the DPI scale twice: the
+ canvas came out that much smaller, and was then drawn that much smaller
+ again. On desktop the two units are the same number and nothing shows;
+ Android's DPI scale is the display density (2.625 at 420dpi), so that is
+ where it surfaced.
+
+ The scene canvas is now sized from `love.graphics.getPixelDimensions`
+ directly rather than from the ctx. That is the number a fixed engine would
+ hand over, so this does not double-correct if the ctx is ever changed to
+ agree with the compositor. It also squares the FX pass for free:
+ `ctx.scale` was ALREADY in pixels per world pixel (`Zoom.scale` over
+ `Renderer:fitScale`, which measures the drawable), so the closures were
+ being scaled for a canvas 2.6x bigger than the one they were drawing into
+ -- one wrong number, not two.
+
## 1.0.1
### Fixed
diff --git a/main.lua b/main.lua
index fc91248..88f775e 100644
--- a/main.lua
+++ b/main.lua
@@ -79,6 +79,33 @@ local VoxelGrid = V.require("VoxelGrid")
local WorldCurve = V.require("WorldCurve")
-- The last VOID FILL the terrain was meshed under; see the update hook.
+-- The scene canvas's size, in FRAMEBUFFER PIXELS.
+--
+-- `ctx.width/height` are the window measured in LOVE UNITS
+-- (love.graphics.getDimensions), but the engine composites a pipeline's
+-- returned canvas with `draw(canvas, 0, 0, 0, 1/dpiX, 1/dpiY)` -- a scale
+-- that only covers the window when the canvas is at PIXEL resolution.
+-- Sizing it in units costs the DPI scale TWICE: the canvas is that much
+-- smaller, then it is drawn that much smaller again, so the diorama lands
+-- in the top-left corner at 1/dpi of the screen. Desktop never sees it --
+-- units and pixels are the same thing there -- but on Android the DPI scale
+-- is the display density (2.625 on a 420dpi panel), and the world came out
+-- a third of the size in each direction.
+--
+-- So ask for the pixel dimensions rather than trusting the ctx. That is
+-- the number a fixed engine would hand over, so this keeps working either
+-- way instead of double-correcting. It also squares the FX pass: ctx.scale
+-- is ALREADY in pixels per world pixel (Zoom.scale over Renderer:fitScale,
+-- which measures the drawable), so the closures ctx.drawFx runs were being
+-- scaled for a canvas 2.6x bigger than the one they drew into.
+local function sceneSize(ctx)
+ if love.graphics and love.graphics.getPixelDimensions then
+ local pw, ph = love.graphics.getPixelDimensions()
+ if pw and ph and pw > 0 and ph > 0 then return pw, ph end
+ end
+ return ctx.width, ctx.height
+end
+
local voidFill = { last = nil }
function voidFill.check()
local TileRenderer = require("src.render.TileRenderer")
@@ -141,10 +168,12 @@ mod.content.render_pipelines:register("voxel", {
drawWorld = function(ctx)
-- Terrain and characters are geometry; the field FX stay ordinary 2D
-- draws composited on top, anchored through the same camera the 3D
- -- pass used (ctx.drawFx below). The scene renders at window
- -- resolution so the 3D pass is crisp rather than a magnified low-res
- -- image, while the FX closures keep drawing in world-pixel units.
- local canvas = VoxelScene.render(ctx.state, ctx.width, ctx.height,
+ -- pass used (ctx.drawFx below). The scene renders at the window's
+ -- PIXEL resolution (see sceneSize) so the 3D pass is crisp rather than
+ -- a magnified low-res image, while the FX closures keep drawing in
+ -- world-pixel units.
+ local sw, sh = sceneSize(ctx)
+ local canvas = VoxelScene.render(ctx.state, sw, sh,
ctx.vw, ctx.vh, ctx.paletteFor)
if not canvas then return nil end -- fall back to the 2D path
if Voxel3D.beginOverlay() then
@@ -343,7 +372,7 @@ mod.events:on("map.reloaded", function(payload)
if mapId then ChunkMesher.invalidate(mapId) end
end)
-mod.exports.version = "1.0.1"
+mod.exports.version = "1.0.2"
-- exposed so a companion mod can pin its own tiles' shapes or read the
-- camera without reaching into this mod's file layout
mod.exports.lib = V
diff --git a/manifest.json b/manifest.json
index c7ec378..f10771e 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,7 +1,7 @@
{
"id": "DRAMATIC_SHAPE",
"name": "Dramatic Shape Voxel Mod",
- "version": "1.0.1",
+ "version": "1.0.2",
"api": 2,
"entry": "main.lua",
"profile": "content",