mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 10:00:50 +02:00
fix flowers
This commit is contained in:
@@ -413,6 +413,9 @@ function Structures.forMap(map)
|
||||
-- renderer never draws a neighbour's ring, and standing scenery past a
|
||||
-- map's edge would poke into the map next door ----
|
||||
Structures.buildGrass(S, map, 0, tw - 1, 0, th - 1, data)
|
||||
|
||||
-- ---- flowers: the animated meadow tile stands as a 1px cutout ----
|
||||
Structures.buildFlowers(S, map, tw, th, x0, x1, y0, y1, data)
|
||||
end
|
||||
|
||||
-- unresolved claimed ground (a hull with no art match, headless
|
||||
@@ -1982,6 +1985,202 @@ function Structures.buildGrass(S, map, x0, x1, y0, y1, data)
|
||||
end
|
||||
end
|
||||
|
||||
-- ---- flowers ----
|
||||
|
||||
-- The animated flower tile stands up as a billboard ONE VOXEL deep, cut
|
||||
-- to the drawing's darkest tones PLUS everything they enclose -- the
|
||||
-- round-scenery hull's rule: flood the tile border through every
|
||||
-- non-dark pixel, and what the flood cannot reach is the flower, its
|
||||
-- pale petal insides included. The mesh is static and the flower is
|
||||
-- not, so the geometry spans the UNION of that mask over the base art
|
||||
-- and every animation frame, and TerrainAtlas rewrites the tile's slot
|
||||
-- each step with only the CURRENT frame's mask opaque -- the rest keyed
|
||||
-- to alpha, which the voxel shader discards. The standing silhouette
|
||||
-- trims itself frame by frame in texture space; the sway animates
|
||||
-- without a vertex moving, off the same engine clock as the flat path.
|
||||
--
|
||||
-- The ground beneath is synthesized from the commonest flat neighbour,
|
||||
-- like the ground under a detected prop: the tile's own slot no longer
|
||||
-- holds art anyone can draw flat.
|
||||
local FLOWER_THICK = 1
|
||||
|
||||
local function flowerFrames(tileset, tileId)
|
||||
local out = {}
|
||||
local ok, declared = pcall(function()
|
||||
if tileset.animatedTiles then return tileset.animatedTiles end
|
||||
local TileRenderer = require("src.render.TileRenderer")
|
||||
return TileRenderer.defaultAnimatedTiles(tileset)
|
||||
end)
|
||||
if not ok then return out end
|
||||
for _, spec in ipairs(type(declared) == "table" and declared or {}) do
|
||||
if spec.kind == "frames" and spec.tile == tileId then
|
||||
for _, path in pairs(spec.images or {}) do
|
||||
local okF, frame = pcall(Assets.imageData, path)
|
||||
if okF and frame then out[#out + 1] = frame end
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function flowerTemplate(map, data, tileId)
|
||||
local tileset = map.tileset
|
||||
local perRow = tileset.tilesPerRow or 16
|
||||
local atlasW = tileset.imageWidth or 128
|
||||
local atlasH = tileset.imageHeight or 48
|
||||
local ax0 = (tileId % perRow) * 8
|
||||
local ay0 = math.floor(tileId / perRow) * 8
|
||||
|
||||
-- per image: dark tones, then the border flood that finds what they
|
||||
-- enclose. Each image closes over ITS OWN outline before the union --
|
||||
-- a pocket two frames only enclose together is not part of either.
|
||||
local dark = {}
|
||||
local function markMask(img, ox, oy)
|
||||
local d, reach, stack = {}, {}, {}
|
||||
for py = 0, 7 do
|
||||
for px = 0, 7 do
|
||||
local r, g, b, a = img:getPixel(ox + px, oy + py)
|
||||
if a > 0 and math.min(r, g, b) <= 0.5 then
|
||||
d[py * 8 + px] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for i = 0, 7 do
|
||||
for _, s in ipairs({ i, 56 + i, i * 8, i * 8 + 7 }) do
|
||||
if not d[s] and not reach[s] then
|
||||
reach[s] = true
|
||||
stack[#stack + 1] = s
|
||||
end
|
||||
end
|
||||
end
|
||||
while #stack > 0 do
|
||||
local p = table.remove(stack)
|
||||
local px, py = p % 8, math.floor(p / 8)
|
||||
for _, dir in ipairs(DIRS4) do
|
||||
local nx, ny = px + dir[1], py + dir[2]
|
||||
if nx >= 0 and nx < 8 and ny >= 0 and ny < 8 then
|
||||
local ni = ny * 8 + nx
|
||||
if not d[ni] and not reach[ni] then
|
||||
reach[ni] = true
|
||||
stack[#stack + 1] = ni
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for i = 0, 63 do
|
||||
if d[i] or not reach[i] then dark[i] = true end
|
||||
end
|
||||
end
|
||||
markMask(data, ax0, ay0)
|
||||
for _, frame in ipairs(flowerFrames(tileset, tileId)) do
|
||||
pcall(markMask, frame, 0, 0)
|
||||
end
|
||||
|
||||
local function on(px, py)
|
||||
if px < 0 or px > 7 or py < 0 or py > 7 then return false end
|
||||
return dark[py * 8 + px] == true
|
||||
end
|
||||
|
||||
local quads = {}
|
||||
local zB = 4 - FLOWER_THICK / 2 -- one slab at the tile's middle
|
||||
local zF = zB + FLOWER_THICK
|
||||
for py = 0, 7 do
|
||||
Budget.tick()
|
||||
local yTop, yBot = 8 - py, 7 - py
|
||||
local ix = 0
|
||||
while ix < 8 do
|
||||
if on(ix, py) then
|
||||
local ix2 = ix
|
||||
while ix2 + 1 < 8 and on(ix2 + 1, py) do ix2 = ix2 + 1 end
|
||||
local u0 = (ax0 + ix + 0.05) / atlasW
|
||||
local u1 = (ax0 + ix2 + 0.95) / atlasW
|
||||
local v0 = (ay0 + py + 0.05) / atlasH
|
||||
local v1 = (ay0 + py + 0.95) / atlasH
|
||||
quads[#quads + 1] = { -- front
|
||||
{ ix, yBot, zF }, { ix2 + 1, yBot, zF },
|
||||
{ ix2 + 1, yTop, zF }, { ix, yTop, zF },
|
||||
uv = { { u0, v1 }, { u1, v1 }, { u1, v0 }, { u0, v0 } },
|
||||
shade = OBJ_SHADE.front,
|
||||
}
|
||||
quads[#quads + 1] = { -- back
|
||||
{ ix2 + 1, yBot, zB }, { ix, yBot, zB },
|
||||
{ ix, yTop, zB }, { ix2 + 1, yTop, zB },
|
||||
uv = { { u1, v1 }, { u0, v1 }, { u0, v0 }, { u1, v0 } },
|
||||
shade = OBJ_SHADE.back,
|
||||
}
|
||||
-- petal tips: a top strip where the row above is clear. The
|
||||
-- strip samples its own row's texel, so a tip that is not in
|
||||
-- the current frame discards with the face beneath it
|
||||
if not on(ix, py - 1) then
|
||||
quads[#quads + 1] = {
|
||||
{ ix, yTop, zB }, { ix2 + 1, yTop, zB },
|
||||
{ ix2 + 1, yTop, zF }, { ix, yTop, zF },
|
||||
uv = { { u0, v0 }, { u1, v0 }, { u1, v0 }, { u0, v0 } },
|
||||
shade = OBJ_SHADE.top,
|
||||
}
|
||||
end
|
||||
ix = ix2 + 1
|
||||
else
|
||||
ix = ix + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
return quads
|
||||
end
|
||||
|
||||
function Structures.buildFlowers(S, map, tw, th, x0, x1, y0, y1, data)
|
||||
local templates = {}
|
||||
local quads = S.objectQuads
|
||||
for ty = y0, y1 do
|
||||
for tx = x0, x1 do
|
||||
Budget.tick()
|
||||
local k = keyOf(tx, ty)
|
||||
local s = S.shapeAt[k]
|
||||
if s and s.art == "flower" then
|
||||
-- the tile's atlas slot carries only the standing cutout now, so
|
||||
-- EVERY flower position -- ring included -- paints synthesized
|
||||
-- ground instead of its own art: the commonest flat neighbour
|
||||
-- that is not itself a flower, else the map's commonest ground
|
||||
-- (forMap's end-of-build vote resolves the `false`)
|
||||
S.skip[k] = true
|
||||
local votes, best, bestN = {}, nil, 0
|
||||
for _, d in ipairs(DIRS4) do
|
||||
local nk = keyOf(tx + d[1], ty + d[2])
|
||||
local ns = S.shapeAt[nk]
|
||||
if ns and ns.flat and ns.class ~= "void"
|
||||
and ns.class ~= "flower" then
|
||||
local t = S.tileAt[nk]
|
||||
votes[t] = (votes[t] or 0) + 1
|
||||
if votes[t] > bestN then best, bestN = t, votes[t] end
|
||||
end
|
||||
end
|
||||
S.ground[k] = best or false
|
||||
|
||||
-- standee BODY only, like grass: standing scenery past a map's
|
||||
-- edge would poke into the map next door
|
||||
if tx >= 0 and ty >= 0 and tx < tw and ty < th then
|
||||
local tileId = S.tileAt[k]
|
||||
local tpl = templates[tileId]
|
||||
if not tpl then
|
||||
tpl = flowerTemplate(map, data, tileId)
|
||||
templates[tileId] = tpl
|
||||
end
|
||||
local wx, wz = tx * 8, ty * 8
|
||||
for _, q in ipairs(tpl) do
|
||||
quads[#quads + 1] = {
|
||||
{ q[1][1] + wx, q[1][2], q[1][3] + wz },
|
||||
{ q[2][1] + wx, q[2][2], q[2][3] + wz },
|
||||
{ q[3][1] + wx, q[3][2], q[3][3] + wz },
|
||||
{ q[4][1] + wx, q[4][2], q[4][3] + wz },
|
||||
uv = q.uv, shade = q.shade,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Drop one map's analysis (Cut changed the block layer) or everything.
|
||||
-- Hull templates key on art content (tileset + tiles), which a block edit
|
||||
-- cannot change, so only the full drop clears them (atlas reload).
|
||||
|
||||
+77
-3
@@ -27,6 +27,9 @@
|
||||
-- the first place (home/vcopy.asm rewrites the tile's VRAM bytes); the 2D
|
||||
-- overdraw is the port's workaround, not the original.
|
||||
|
||||
-- the mod namespace (see main.lua): V.require loads a sibling module
|
||||
local V = ...
|
||||
|
||||
local Assets = require("src.render.Assets")
|
||||
local TileRenderer = require("src.render.TileRenderer")
|
||||
local PaletteFX = require("src.render.PaletteFX")
|
||||
@@ -146,6 +149,8 @@ local function learnShades(raw, baked, tile, perRow)
|
||||
return map
|
||||
end
|
||||
|
||||
local DIRS4 = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } }
|
||||
|
||||
-- One animated entry's tile slot, written into `out` at step `step`.
|
||||
local function patch(out, entry, spec, step)
|
||||
local perRow, tile = entry.perRow, spec.tile
|
||||
@@ -167,12 +172,59 @@ local function patch(out, entry, spec, step)
|
||||
local ok, frame = pcall(Assets.imageData, path)
|
||||
if not ok or not frame then return end
|
||||
local shades = entry.shades and entry.shades[tile]
|
||||
-- a `cut` tile is the flower billboard's slot (Structures'
|
||||
-- buildFlowers): only the frame's darkest tones AND what they
|
||||
-- enclose stay opaque -- the round-scenery hull's rule, flooding
|
||||
-- the frame border through every non-dark pixel so the pale petal
|
||||
-- insides survive with the outline. The true background is keyed
|
||||
-- to alpha; nothing else samples this slot (the ground under a
|
||||
-- flower is synthesized), and the shader's discard is what lets
|
||||
-- the billboard's silhouette change per frame under geometry that
|
||||
-- never moves.
|
||||
local mask = nil
|
||||
if entry.cut and entry.cut[tile] then
|
||||
local dark, reach, stack = {}, {}, {}
|
||||
for y = 0, 7 do
|
||||
for x = 0, 7 do
|
||||
local r, _, _, a = frame:getPixel(x, y)
|
||||
if a > 0 and shadeOf(r) >= 3 then dark[y * 8 + x] = true end
|
||||
end
|
||||
end
|
||||
for i = 0, 7 do
|
||||
for _, s in ipairs({ i, 56 + i, i * 8, i * 8 + 7 }) do
|
||||
if not dark[s] and not reach[s] then
|
||||
reach[s] = true
|
||||
stack[#stack + 1] = s
|
||||
end
|
||||
end
|
||||
end
|
||||
while #stack > 0 do
|
||||
local p = table.remove(stack)
|
||||
local px, py = p % 8, math.floor(p / 8)
|
||||
for _, d in ipairs(DIRS4) do
|
||||
local nx, ny = px + d[1], py + d[2]
|
||||
if nx >= 0 and nx < 8 and ny >= 0 and ny < 8 then
|
||||
local ni = ny * 8 + nx
|
||||
if not dark[ni] and not reach[ni] then
|
||||
reach[ni] = true
|
||||
stack[#stack + 1] = ni
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
mask = {}
|
||||
for i = 0, 63 do mask[i] = dark[i] or not reach[i] end
|
||||
end
|
||||
for y = 0, 7 do
|
||||
for x = 0, 7 do
|
||||
local r, g, b, a = frame:getPixel(x, y)
|
||||
local col = shades and shades[shadeOf(r)]
|
||||
if col and a > 0 then r, g, b = col[1], col[2], col[3] end
|
||||
out:setPixel(dx + x, dy + y, r, g, b, a)
|
||||
if mask and not mask[y * 8 + x] then
|
||||
out:setPixel(dx + x, dy + y, 0, 0, 0, 0)
|
||||
else
|
||||
local col = shades and shades[shadeOf(r)]
|
||||
if col and a > 0 then r, g, b = col[1], col[2], col[3] end
|
||||
out:setPixel(dx + x, dy + y, r, g, b, a)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -427,6 +479,28 @@ local function newEntry(map, base, baked)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- a frame-animated tile that resolved to the `flower` class stands as
|
||||
-- a 1px billboard (Structures.buildFlowers), and its slot in THIS
|
||||
-- copy carries only each frame's dark tones with the rest keyed to
|
||||
-- alpha -- see patch(). Gated on the resolved shape so a profile that
|
||||
-- pins the tile to something solid keeps a fully opaque slot, and on
|
||||
-- the tileset art being readable: without pixels Structures cannot
|
||||
-- have stood the billboard up (or synthesized the ground), and an
|
||||
-- alpha-keyed slot under an ordinary flat quad is a hole in the world.
|
||||
for _, spec in ipairs(specs) do
|
||||
if spec.kind == "frames" then
|
||||
local okCut, isFlower = pcall(function()
|
||||
local okA, art = pcall(Assets.imageData, tileset.image)
|
||||
if not (okA and art and art.getPixel) then return false end
|
||||
local sh = V.require("TileShape").forMap(map)[spec.tile]
|
||||
return sh ~= nil and sh.class == "flower"
|
||||
end)
|
||||
if okCut and isFlower then
|
||||
e.cut = e.cut or {}
|
||||
e.cut[spec.tile] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return e
|
||||
end)
|
||||
return (ok and entry) or false
|
||||
|
||||
+35
-3
@@ -60,6 +60,7 @@ local FALLBACK_HEIGHTS = {
|
||||
signpost = 16,
|
||||
post = 16,
|
||||
grass = 0,
|
||||
flower = 0,
|
||||
-- interior furniture: face-on drawings the detector would otherwise
|
||||
-- raise to wall height (or merge into the wall). A bed is drawn from
|
||||
-- above and lies low; tables and desks are boxes at their real height;
|
||||
@@ -110,6 +111,11 @@ local ART = {
|
||||
signpost = "billboard",
|
||||
post = "post",
|
||||
grass = "grass",
|
||||
-- animated flowers: flat synthesized ground PLUS a standing cutout of
|
||||
-- the drawing's darkest tones, one voxel deep (see Structures'
|
||||
-- buildFlowers). Height 0 so a build with no pixel access degrades to
|
||||
-- the flat tile it always drew, not a box
|
||||
flower = "flower",
|
||||
-- furniture: a bed's art depicts its top surface; tables and desks are
|
||||
-- boxes whose fronts fold up (the mesher's authored-fold rule).
|
||||
-- Stools, `prop` and `cutout` are standee pools alongside `billboard`
|
||||
@@ -188,9 +194,11 @@ end
|
||||
local function shapeFor(class, heights, authored)
|
||||
return { class = class, h = heights[class] or 0,
|
||||
art = ART[class] or "upright",
|
||||
-- grass draws its flat ground base like any walkable tile; the
|
||||
-- standing tuft rows are additive geometry from Structures
|
||||
flat = ART[class] == "flat" or class == "grass",
|
||||
-- grass and flowers draw a flat ground base like any walkable
|
||||
-- tile; the standing tufts and cutouts are additive geometry
|
||||
-- from Structures
|
||||
flat = ART[class] == "flat" or class == "grass"
|
||||
or class == "flower",
|
||||
authored = authored or false }
|
||||
end
|
||||
|
||||
@@ -210,6 +218,28 @@ function TileShape.forMap(map)
|
||||
local count = math.floor((tileset.imageWidth or 128) / 8)
|
||||
* math.floor((tileset.imageHeight or 48) / 8)
|
||||
|
||||
-- derived pin: a tile the tileset animates by FRAME REWRITE (the
|
||||
-- overworld's flower) is already named by its animation spec, so like
|
||||
-- tall grass it needs no profile entry anywhere. Hand-authoring still
|
||||
-- wins -- a mod animating a wall tile this way keeps its wall by
|
||||
-- listing it. Guarded because the spec seam is engine data a stub map
|
||||
-- may not carry.
|
||||
local flowerTiles = {}
|
||||
do
|
||||
local ok, declared = pcall(function()
|
||||
if tileset.animatedTiles then return tileset.animatedTiles end
|
||||
local TileRenderer = require("src.render.TileRenderer")
|
||||
return TileRenderer.defaultAnimatedTiles(tileset)
|
||||
end)
|
||||
if ok then
|
||||
for _, spec in ipairs(type(declared) == "table" and declared or {}) do
|
||||
if spec.kind == "frames" and spec.tile then
|
||||
flowerTiles[spec.tile] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local shapes = { classes = {} }
|
||||
for class in pairs(FALLBACK_HEIGHTS) do
|
||||
shapes.classes[class] = shapeFor(class, heights)
|
||||
@@ -222,6 +252,8 @@ function TileShape.forMap(map)
|
||||
-- derived pin: every tileset already names its tall-grass tile, so
|
||||
-- the standing-tuft treatment needs no profile entry anywhere
|
||||
shapes[t] = shapeFor("grass", heights, true)
|
||||
elseif flowerTiles[t] then
|
||||
shapes[t] = shapeFor("flower", heights, true)
|
||||
elseif map.waterTiles and map.waterTiles[t] then
|
||||
shapes[t] = shapes.classes.water
|
||||
elseif map.walkable and map.walkable[t] then
|
||||
|
||||
@@ -420,6 +420,98 @@ T.check(okClock and type(clockVal) == "number",
|
||||
"a clock that throws falls back to a working one rather than propagating")
|
||||
TileRenderer.animFrame = nil
|
||||
|
||||
-- ------- the flower's slot carries an animated SILHOUETTE, not a tile
|
||||
--
|
||||
-- The flower tile stands in voxel mode as a billboard one voxel deep
|
||||
-- (Structures.buildFlowers), cut to the drawing's darkest tones. Meshes
|
||||
-- are static, so the geometry spans the union of every frame's dark
|
||||
-- pixels and the animation lives in the atlas: patch() keys everything
|
||||
-- lighter to alpha 0, the shader discards it, and the silhouette trims
|
||||
-- itself frame by frame. Two halves to pin down: the CLASS is derived
|
||||
-- (any frames-animated tile resolves `flower` with no profile entry),
|
||||
-- and the PATCH writes alpha where the frame is not dark.
|
||||
|
||||
local TileShape = run.loader.exports.DRAMATIC_SHAPE.lib.require("TileShape")
|
||||
|
||||
local flowerSet = {
|
||||
id = "T_FLOWER_PIN", image = "assets/tilesets/stub.png",
|
||||
tilesPerRow = 16, imageWidth = 128, imageHeight = 48,
|
||||
animatedTiles = { { tile = 0x03, kind = "frames", period = 20,
|
||||
images = { "stub_flowerframe.png" },
|
||||
sequence = { 1 } } },
|
||||
}
|
||||
local flowerShapes = TileShape.forMap({ tileset = flowerSet })
|
||||
T.eq(flowerShapes[0x03].class, "flower",
|
||||
"a frames-animated tile is pinned `flower` with no profile entry, like grass")
|
||||
T.check(flowerShapes[0x03].flat,
|
||||
"the flower cell still counts as flat ground for its neighbours")
|
||||
T.eq(flowerShapes[0x03].h, 0,
|
||||
"and carries no height, so a build with no pixel access degrades to the flat tile")
|
||||
T.check(flowerShapes[0x03].authored,
|
||||
"the pin is authored-strength: cell walkability cannot re-file it as plain ground")
|
||||
|
||||
-- the patch, observed through a recording atlas copy: a crafted frame
|
||||
-- whose dark pixels form a diamond ring around one light pixel -- the
|
||||
-- billboard must keep the ring AND the pale pixel it encloses, and key
|
||||
-- the reachable background (light or transparent) to alpha
|
||||
local slotPx = {}
|
||||
local sectionNewImageData = love.image.newImageData
|
||||
do
|
||||
local crafted = fakePixels()
|
||||
local ring = { ["1,0"] = true, ["0,1"] = true,
|
||||
["2,1"] = true, ["1,2"] = true }
|
||||
local frame = fakePixels()
|
||||
function frame:getPixel(x, y)
|
||||
if ring[x .. "," .. y] then return 0.3, 0.3, 0.3, 1 end -- dark outline
|
||||
if x == 7 and y == 0 then return 0.9, 0.9, 0.9, 0 end -- transparent
|
||||
return 1, 1, 1, 1 -- light: petal inside at (1,1),
|
||||
end -- background everywhere else
|
||||
love.image.newImageData = function(a, b)
|
||||
if type(a) == "number" then
|
||||
local d = fakePixels(a, b)
|
||||
function d:setPixel(x, y, r, g, b2, al)
|
||||
slotPx[x .. "," .. y] = { r, g, b2, al }
|
||||
end
|
||||
return d
|
||||
end
|
||||
if tostring(a):find("flowerframe") then return frame end
|
||||
return crafted
|
||||
end
|
||||
end
|
||||
|
||||
TerrainAtlas.invalidate()
|
||||
local fmap = {
|
||||
id = "T_FLOWER_PATCH_MAP",
|
||||
tileset = {
|
||||
id = "T_FLOWER_PATCH", image = "assets/tilesets/stub2.png",
|
||||
tilesPerRow = 16, imageWidth = 128, imageHeight = 48,
|
||||
animatedTiles = { { tile = 0x03, kind = "frames", period = 20,
|
||||
images = { "stub_flowerframe.png" },
|
||||
sequence = { 1 } } },
|
||||
},
|
||||
renderer = { image = base },
|
||||
}
|
||||
local okFlower, flowerImg = pcall(TerrainAtlas.animate, fmap, nil, base, false)
|
||||
T.check(okFlower and flowerImg ~= nil,
|
||||
"a flower map animates: " .. tostring(flowerImg))
|
||||
|
||||
local fdx = (0x03 % 16) * 8
|
||||
local function slotAlpha(x, y)
|
||||
local p = slotPx[(fdx + x) .. "," .. y]
|
||||
return p and p[4]
|
||||
end
|
||||
T.eq(slotAlpha(1, 0), 1,
|
||||
"a dark frame pixel lands opaque -- it is the standing cutout")
|
||||
T.eq(slotAlpha(1, 1), 1,
|
||||
"and so does the pale pixel the outline encloses -- the petal's inside "
|
||||
.. "rides the billboard, not just its outline")
|
||||
T.eq(slotAlpha(5, 5), 0,
|
||||
"a background pixel is keyed to alpha, so the billboard's shader discards it")
|
||||
T.eq(slotAlpha(7, 0), 0,
|
||||
"a transparent frame pixel stays clear rather than painting black")
|
||||
|
||||
love.image.newImageData = sectionNewImageData
|
||||
|
||||
TerrainAtlas.invalidate()
|
||||
love.image, love.graphics.newImage = realImage, realNewImage
|
||||
|
||||
|
||||
@@ -117,12 +117,26 @@ return function(game)
|
||||
end
|
||||
|
||||
-- one animation period is 20 logic frames at 60Hz, and TileRenderer.tick
|
||||
-- runs on wall-clock steps, so ~22 rendered frames lands the next step
|
||||
-- runs on wall-clock steps, so ~22 rendered frames lands the next step.
|
||||
-- animFrame is an OPTIONAL engine seam this build may not export; the
|
||||
-- mod's own clock (TerrainAtlas._animFrame) reads the same counter by
|
||||
-- whatever route exists, so ask it first.
|
||||
local function clock()
|
||||
if TerrainAtlas and TerrainAtlas._animFrame then
|
||||
local ok, f = pcall(TerrainAtlas._animFrame)
|
||||
if ok and type(f) == "number" then return f end
|
||||
end
|
||||
if TileRenderer.animFrame then
|
||||
local ok, f = pcall(TileRenderer.animFrame)
|
||||
if ok and type(f) == "number" then return f end
|
||||
end
|
||||
return -1
|
||||
end
|
||||
for i = 1, shots do
|
||||
game.capturePath = ("%s/anim_%s_L%d_%02d.png"):format(DIR, mapId, level, i)
|
||||
wait(3)
|
||||
print(("[anim] shot %d at animFrame %d step %d"):format(
|
||||
i, TileRenderer.animFrame(), math.floor(TileRenderer.animFrame() / 20) % 8))
|
||||
i, clock(), math.floor(clock() / 20) % 8))
|
||||
dumpAtlas(tostring(i))
|
||||
wait(22)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user