mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-09-28 06:49:29 -04:00
Merge pull request #2438 from colsonrice/fix/world-pipeline-field-fx-mirror
This commit is contained in:
+11
-2
@@ -53,6 +53,16 @@ end
|
||||
-- endFrame composites the padded canvas back with a matching offset.
|
||||
Renderer.UPRIGHT_MARGIN = 160
|
||||
|
||||
-- Whether endFrame composites a world override MIRRORED. A pipeline owns its
|
||||
-- own clip convention and the ones in the wild pre-flip Y for LOVE 11, which
|
||||
-- LOVE 12 turns into one flip too many. Named because it is not only the
|
||||
-- blit's business: see ctx.drawFx in src/world/OverworldController.lua.
|
||||
function Renderer.mirrorsWorldOverride()
|
||||
local sys = love.system
|
||||
if not (sys and sys.getOS and sys.getOS() == "iOS") then return false end
|
||||
return (love.getVersion()) >= 12
|
||||
end
|
||||
|
||||
-- LOVE units + framebuffer pixels + per-axis unit→pixel ratios.
|
||||
-- Android's DisplayMetrics.density is often non-integer (1.5, 2.75, …).
|
||||
-- Integer scaling in units then maps each GB pixel to a fractional number of
|
||||
@@ -1056,8 +1066,7 @@ function Renderer:endFrame(zones, worldZones)
|
||||
-- runs, so dialogs, menus and the HUD sit on top as usual.
|
||||
love.graphics.setColor(1, 1, 1, 1)
|
||||
love.graphics.setScissor(vux, vuy, vuw, vuh)
|
||||
local loveMajor = love.getVersion()
|
||||
if love.system and love.system.getOS and love.system.getOS() == "iOS" and loveMajor >= 12 then
|
||||
if Renderer.mirrorsWorldOverride() then
|
||||
love.graphics.draw(self.worldOverride, vux, vuy + vuh, 0, 1 / dpiX, -1 / dpiY)
|
||||
else
|
||||
love.graphics.draw(self.worldOverride, vux, vuy, 0, 1 / dpiX, 1 / dpiY)
|
||||
|
||||
@@ -18,6 +18,7 @@ local NPC = require("src.world.NPC")
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
local Pipelines = require("src.render.Pipelines")
|
||||
local Player = require("src.world.Player")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Runtime = require("src.mods.Runtime")
|
||||
local Screens = require("src.ui.Screens")
|
||||
local ScriptRunner = require("src.script.ScriptRunner")
|
||||
@@ -33,6 +34,33 @@ local Strings = require("src.core.Strings")
|
||||
-- isOverworld marks the live world state for WorldAPI's stack scan
|
||||
local OverworldState = { isOpaque = true, isOverworld = true }
|
||||
|
||||
-- The pipeline's own target, not the playfield: a pipeline with a render
|
||||
-- scale draws smaller and lets endFrame scale it up.
|
||||
local function overrideCanvasHeight()
|
||||
local bound = love.graphics.getCanvas()
|
||||
if bound and not bound.getHeight then bound = bound[1] end -- several bound
|
||||
if bound and not bound.getHeight then bound = bound.canvas end -- one face of one
|
||||
return bound and bound.getHeight and bound:getHeight()
|
||||
end
|
||||
|
||||
-- Run a block of field FX the way the composite expects them. Where endFrame
|
||||
-- mirrors the override, these are the one part of that canvas the mirror would
|
||||
-- invert rather than right: the pipeline's pass went in pre-flipped, these are
|
||||
-- ordinary 2D. project() already answers in screen rows, so mirroring the
|
||||
-- block about the bound canvas is the whole correction, and project is left
|
||||
-- alone -- fixing it too would move each anchor twice. Exposed for tests.
|
||||
local function withOverrideMirror(draw)
|
||||
local height = Renderer.mirrorsWorldOverride() and overrideCanvasHeight()
|
||||
if not height then return draw() end
|
||||
love.graphics.push()
|
||||
love.graphics.translate(0, height)
|
||||
love.graphics.scale(1, -1)
|
||||
draw()
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
OverworldState.withOverrideMirror = withOverrideMirror
|
||||
|
||||
local Game -- set on enter (avoids circular require at load time)
|
||||
|
||||
local mapScripts -- registry of hand-ported map scripts
|
||||
@@ -6214,27 +6242,29 @@ function OverworldState:drawWorld()
|
||||
love.graphics.pop()
|
||||
if shader then love.graphics.setShader() end
|
||||
end
|
||||
-- ground-hugging effects sit on the cell they belong to
|
||||
if self.dustAnim then
|
||||
local da = self.dustAnim
|
||||
at(fxDust, da.x * 16 + 8 + (da.ox or 0), da.y * 16 + 8 + (da.oy or 0))
|
||||
end
|
||||
if self.cutAnim then
|
||||
at(fxCutTree, self.cutAnim.x * 16 + 8, self.cutAnim.y * 16 + 16)
|
||||
end
|
||||
if self.healAnim then
|
||||
at(fxHeal, self.healAnim.px + 8, self.healAnim.py + 16)
|
||||
end
|
||||
-- standing effects anchor at the foot of whoever they belong to
|
||||
if self.emote and self.emote.npc then
|
||||
at(fxEmote, self.emote.npc.px + 8, self.emote.npc.py + 16)
|
||||
end
|
||||
if self.flyAnim then
|
||||
at(fxBird, self.player.px + 8, self.player.py + 16)
|
||||
end
|
||||
if self.fishing then
|
||||
at(fxRod, self.player.px + 8, self.player.py + 16)
|
||||
end
|
||||
withOverrideMirror(function()
|
||||
-- ground-hugging effects sit on the cell they belong to
|
||||
if self.dustAnim then
|
||||
local da = self.dustAnim
|
||||
at(fxDust, da.x * 16 + 8 + (da.ox or 0), da.y * 16 + 8 + (da.oy or 0))
|
||||
end
|
||||
if self.cutAnim then
|
||||
at(fxCutTree, self.cutAnim.x * 16 + 8, self.cutAnim.y * 16 + 16)
|
||||
end
|
||||
if self.healAnim then
|
||||
at(fxHeal, self.healAnim.px + 8, self.healAnim.py + 16)
|
||||
end
|
||||
-- standing effects anchor at the foot of whoever they belong to
|
||||
if self.emote and self.emote.npc then
|
||||
at(fxEmote, self.emote.npc.px + 8, self.emote.npc.py + 16)
|
||||
end
|
||||
if self.flyAnim then
|
||||
at(fxBird, self.player.px + 8, self.player.py + 16)
|
||||
end
|
||||
if self.fishing then
|
||||
at(fxRod, self.player.px + 8, self.player.py + 16)
|
||||
end
|
||||
end)
|
||||
end
|
||||
override = Pipelines.drawWorld(pipelineId, ctx)
|
||||
-- world post-processes (a miniature-diorama blur, a colour grade) fold
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
-- The field FX a render pipeline composites, where endFrame mirrors the
|
||||
-- override. The pipeline's pass goes in pre-flipped and comes back level;
|
||||
-- ctx.drawFx puts these in as ordinary 2D, so without the matching mirror the
|
||||
-- trainer "!" lands upside down and below its feet.
|
||||
--
|
||||
-- The checks ask where a pixel is SEEN: they run the arithmetic `at` does and
|
||||
-- then apply the composite by hand, so a mirror about the wrong axis, the
|
||||
-- wrong height or in the wrong order fails here.
|
||||
-- luajit tests/engine/world_pipeline_fx_mirror.lua
|
||||
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
|
||||
local T = require("tests.modkit")
|
||||
local Renderer = require("src.render.Renderer")
|
||||
local Overworld = require("src.world.OverworldController")
|
||||
|
||||
-- ------------------------------------------------- a transform we can read
|
||||
|
||||
local g = love.graphics
|
||||
local real = { push = g.push, pop = g.pop, translate = g.translate,
|
||||
scale = g.scale, getCanvas = g.getCanvas }
|
||||
local stack
|
||||
|
||||
local function reset() stack = { { oy = 0, sy = 1 } } end
|
||||
local function top() return stack[#stack] end
|
||||
|
||||
g.push = function() local t = top(); stack[#stack + 1] = { oy = t.oy, sy = t.sy } end
|
||||
g.pop = function() if #stack > 1 then stack[#stack] = nil end end
|
||||
g.translate = function(_, dy) local t = top(); t.oy = t.oy + t.sy * dy end
|
||||
g.scale = function(_, ky) local t = top(); t.sy = t.sy * (ky or 1) end
|
||||
|
||||
local CANVAS_H = 400
|
||||
g.getCanvas = function() return { getHeight = function() return CANVAS_H end } end
|
||||
|
||||
local osName, major = "iOS", 12
|
||||
love.system = love.system or {}
|
||||
local realOS, realVersion = love.system.getOS, love.getVersion
|
||||
love.system.getOS = function() return osName end
|
||||
love.getVersion = function() return major, 0, 0, "" end
|
||||
|
||||
reset()
|
||||
|
||||
-- ------------------------------------------------- which hosts mirror
|
||||
|
||||
T.check(Renderer.mirrorsWorldOverride(), "LOVE 12 on iOS mirrors the override")
|
||||
major = 11
|
||||
T.check(not Renderer.mirrorsWorldOverride(), "LOVE 11 stores a canvas the old way")
|
||||
major, osName = 12, "Android"
|
||||
T.check(not Renderer.mirrorsWorldOverride(), "and no other host reaches that branch")
|
||||
osName = "iOS"
|
||||
|
||||
-- ------------------------------------------------- the geometry
|
||||
|
||||
local CAM_X, CAM_Y = 100, 200
|
||||
local NPC_X, NPC_Y = 148, 264 -- the foot `at` anchors to
|
||||
local ROW, SCALE = 120, 2 -- where the pipeline projects that foot
|
||||
local function project() return 260, ROW end
|
||||
|
||||
-- src/world/OverworldController.lua's `at`, for the row a drawn point reaches
|
||||
local function at(dy)
|
||||
local sx, sy = project()
|
||||
local fy = NPC_Y - CAM_Y
|
||||
g.push()
|
||||
g.scale(SCALE, SCALE)
|
||||
g.translate(sx / SCALE, sy / SCALE - fy)
|
||||
local t = top()
|
||||
local row = t.oy + t.sy * (fy + dy)
|
||||
g.pop()
|
||||
return row
|
||||
end
|
||||
|
||||
-- endFrame draws the override from the bottom edge with a negative Y scale
|
||||
local function seen(row) return CANVAS_H - row end
|
||||
|
||||
-- fxEmote draws the bubble 36 world pixels above the foot it anchors to
|
||||
reset()
|
||||
T.eq(seen(at(0)), CANVAS_H - ROW, "unmirrored, the anchor is seen at the mirrored row")
|
||||
T.check(seen(at(-36)) > seen(at(0)), "and the bubble that belongs above is seen below")
|
||||
|
||||
reset()
|
||||
Overworld.withOverrideMirror(function()
|
||||
T.eq(seen(at(0)), ROW, "mirrored, the anchor is seen where the pipeline put it")
|
||||
T.eq(seen(at(-36)), ROW - 36 * SCALE, "and the bubble sits above it, upright")
|
||||
end)
|
||||
T.eq(#stack, 1, "the transform stack is left balanced")
|
||||
|
||||
-- ------------------------------------------------- what it leaves alone
|
||||
|
||||
-- no mirror in the composite either, so the drawn row IS the row seen
|
||||
osName = "Android"
|
||||
reset()
|
||||
Overworld.withOverrideMirror(function()
|
||||
T.eq(at(0), ROW, "a host that composites straight draws them untouched")
|
||||
end)
|
||||
osName = "iOS"
|
||||
|
||||
-- a pipeline at a reduced render scale mirrors about ITS canvas, not the playfield
|
||||
CANVAS_H = 240
|
||||
reset()
|
||||
Overworld.withOverrideMirror(function()
|
||||
T.eq(seen(at(0)), ROW, "a reduced render scale mirrors about its own canvas")
|
||||
end)
|
||||
CANVAS_H = 400
|
||||
|
||||
reset()
|
||||
g.getCanvas = function() return nil end
|
||||
Overworld.withOverrideMirror(function()
|
||||
T.eq(seen(at(0)), CANVAS_H - ROW, "with nothing bound the effects still draw")
|
||||
end)
|
||||
T.eq(#stack, 1, "and no transform is pushed for them")
|
||||
|
||||
for name, fn in pairs(real) do g[name] = fn end
|
||||
love.system.getOS, love.getVersion = realOS, realVersion
|
||||
|
||||
T.finish("world pipeline fx mirror")
|
||||
Reference in New Issue
Block a user