From cec3d5e1eff0120b34c6637a98733fe7aaf7edb0 Mon Sep 17 00:00:00 2001 From: DramaticShape Date: Sun, 26 Jul 2026 21:30:28 -0400 Subject: [PATCH] fixed scaling issue on android --- CHANGELOG.md | 28 ++++++++++++++++++++++++++++ main.lua | 39 ++++++++++++++++++++++++++++++++++----- manifest.json | 2 +- 3 files changed, 63 insertions(+), 6 deletions(-) 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",