Fix coordinate systems & Prepare for entity output

- More clearly describe which coordinate system is used
- Fix screen coordinates for CAPTURE_PIXEL_SIZE != 1
- Prepare entity output file
- Fix format of comments
This commit is contained in:
David Vogel 2022-07-17 14:39:18 +02:00
parent 48a152a219
commit 4551948460
2 changed files with 57 additions and 37 deletions

View File

@ -1,7 +1,7 @@
# Capture areas # Capture areas
A list of available capture areas. A list of available capture areas.
Coordinates are in in-game "virtual" pixels. Coordinates are in in-game "virtual" or "world" pixels.
`Right` and `Bottom` coordinates are not included in the rectangle. `Right` and `Bottom` coordinates are not included in the rectangle.
The dimensions of the capture rectangle are exactly: The dimensions of the capture rectangle are exactly:

View File

@ -1,34 +1,34 @@
-- Copyright (c) 2019-2020 David Vogel -- Copyright (c) 2019-2022 David Vogel
-- --
-- This software is released under the MIT License. -- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT -- https://opensource.org/licenses/MIT
CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio CAPTURE_PIXEL_SIZE = 1 -- Screen to virtual pixel ratio.
CAPTURE_GRID_SIZE = 512 -- in ingame pixels. There will always be exactly 4 images overlapping if the virtual resolution is 1024x1024 CAPTURE_GRID_SIZE = 512 -- in virtual (world) pixels. There will always be exactly 4 images overlapping if the virtual resolution is 1024x1024.
CAPTURE_FORCE_HP = 4 -- * 25HP CAPTURE_FORCE_HP = 4 -- * 25HP
-- "Base layout" (Base layout. Every part outside this is based on a similar layout, but uses different materials/seeds) -- "Base layout" (Base layout. Every part outside this is based on a similar layout, but uses different materials/seeds)
CAPTURE_AREA_BASE_LAYOUT = { CAPTURE_AREA_BASE_LAYOUT = {
Left = -17920, -- in ingame pixels. Left = -17920, -- in virtual (world) pixels.
Top = -7168, -- in ingame pixels. Top = -7168, -- in virtual (world) pixels.
Right = 17920, -- in ingame pixels. (Coordinate is not included in the rectangle) Right = 17920, -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
Bottom = 17408 -- in ingame pixels. (Coordinate is not included in the rectangle) Bottom = 17408 -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
} }
-- "Main world" (The main world with 3 parts: sky, normal and hell) -- "Main world" (The main world with 3 parts: sky, normal and hell)
CAPTURE_AREA_MAIN_WORLD = { CAPTURE_AREA_MAIN_WORLD = {
Left = -17920, -- in ingame pixels. Left = -17920, -- in virtual (world) pixels.
Top = -31744, -- in ingame pixels. Top = -31744, -- in virtual (world) pixels.
Right = 17920, -- in ingame pixels. (Coordinate is not included in the rectangle) Right = 17920, -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
Bottom = 41984 -- in ingame pixels. (Coordinate is not included in the rectangle) Bottom = 41984 -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
} }
-- "Extended" (Main world + a fraction of the parallel worlds to the left and right) -- "Extended" (Main world + a fraction of the parallel worlds to the left and right)
CAPTURE_AREA_EXTENDED = { CAPTURE_AREA_EXTENDED = {
Left = -25600, -- in ingame pixels. Left = -25600, -- in virtual (world) pixels.
Top = -31744, -- in ingame pixels. Top = -31744, -- in virtual (world) pixels.
Right = 25600, -- in ingame pixels. (Coordinate is not included in the rectangle) Right = 25600, -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
Bottom = 41984 -- in ingame pixels. (Coordinate is not included in the rectangle) Bottom = 41984 -- in virtual (world) pixels. (Coordinate is not included in the rectangle)
} }
local function preparePlayer() local function preparePlayer()
@ -43,7 +43,15 @@ local function preparePlayer()
setPlayerHP(CAPTURE_FORCE_HP) setPlayerHP(CAPTURE_FORCE_HP)
end end
local function captureScreenshot(x, y, rx, ry) --- Captures a screenshot at the given coordinates.
--- This will block until all chunks in the given area are loaded.
---
--- @param x number -- Virtual x coordinate (World pixels) of the screen center.
--- @param y number -- Virtual y coordinate (World pixels) of the screen center.
--- @param rx number -- Screen x coordinate of the top left corner of the screenshot rectangle.
--- @param ry number -- Screen y coordinate of the top left corner of the screenshot rectangle.
--- @param entityFile file*
local function captureScreenshot(x, y, rx, ry, entityFile)
local virtualWidth, virtualHeight = local virtualWidth, virtualHeight =
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")), tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_X")),
tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y")) tonumber(MagicNumbersGetValue("VIRTUAL_RESOLUTION_Y"))
@ -56,7 +64,7 @@ local function captureScreenshot(x, y, rx, ry)
GameSetCameraPos(x, y) GameSetCameraPos(x, y)
repeat repeat
if UiCaptureDelay > 100 then if UiCaptureDelay > 100 then
-- Wiggle the screen a bit, as chunks sometimes don't want to load -- Wiggle the screen a bit, as chunks sometimes don't want to load.
GameSetCameraPos(x + math.random(-100, 100), y + math.random(-100, 100)) GameSetCameraPos(x + math.random(-100, 100), y + math.random(-100, 100))
DrawUI() DrawUI()
wait(0) wait(0)
@ -67,21 +75,33 @@ local function captureScreenshot(x, y, rx, ry)
DrawUI() DrawUI()
wait(0) wait(0)
UiCaptureDelay = UiCaptureDelay + 1 UiCaptureDelay = UiCaptureDelay + 1
until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame until DoesWorldExistAt(xMin, yMin, xMax, yMax) -- Chunks will be drawn on the *next* frame.
wait(0) -- Without this line empty chunks may still appear, also it's needed for the UI to disappear wait(0) -- Without this line empty chunks may still appear, also it's needed for the UI to disappear.
if not TriggerCapture(rx, ry) then if not TriggerCapture(rx, ry) then
UiCaptureProblem = "Screen capture failed. Please restart Noita." UiCaptureProblem = "Screen capture failed. Please restart Noita."
end end
-- Reset monitor and PC standby each screenshot -- Capture entities right after capturing the screenshot.
if entityFile then
local radius = math.sqrt(virtualHalfWidth^2 + virtualHalfHeight^2) + 1
local entities = EntityGetInRadius(x, y, radius)
for i, v in ipairs(entities) do
entityFile:write(tostring(v) .. "\n")
end
entityFile:flush()
end
-- Reset monitor and PC standby each screenshot.
ResetStandbyTimer() ResetStandbyTimer()
end end
function startCapturingSpiral() function startCapturingSpiral()
local ox, oy = GameGetCameraPos() --local entityFile = io.open("mods/noita-mapcap/output/entities.txt", "a+")
local ox, oy = GameGetCameraPos() -- Returns the virtual coordinates of the screen center.
ox, oy = math.floor(ox / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE, math.floor(oy / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE ox, oy = math.floor(ox / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE, math.floor(oy / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE
ox, oy = ox + 256, oy + 256 -- Align screen with ingame chunk grid that is 512x512 ox, oy = ox + 256, oy + 256 -- Align screen with ingame chunk grid that is 512x512.
local x, y = ox, oy local x, y = ox, oy
local virtualWidth, virtualHeight = local virtualWidth, virtualHeight =
@ -94,13 +114,13 @@ function startCapturingSpiral()
GameSetCameraFree(true) GameSetCameraFree(true)
-- Coroutine to calculate next coordinate, and trigger screenshots -- Coroutine to calculate next coordinate, and trigger screenshots.
local i = 1 local i = 1
async_loop( async_loop(
function() function()
-- +x -- +x
for i = 1, i, 1 do for i = 1, i, 1 do
local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry) captureScreenshot(x, y, rx, ry)
end end
@ -108,7 +128,7 @@ function startCapturingSpiral()
end end
-- +y -- +y
for i = 1, i, 1 do for i = 1, i, 1 do
local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry) captureScreenshot(x, y, rx, ry)
end end
@ -117,7 +137,7 @@ function startCapturingSpiral()
i = i + 1 i = i + 1
-- -x -- -x
for i = 1, i, 1 do for i = 1, i, 1 do
local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry) captureScreenshot(x, y, rx, ry)
end end
@ -125,7 +145,7 @@ function startCapturingSpiral()
end end
-- -y -- -y
for i = 1, i, 1 do for i = 1, i, 1 do
local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry) captureScreenshot(x, y, rx, ry)
end end
@ -145,11 +165,11 @@ function startCapturingHilbert(area)
local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2) local virtualHalfWidth, virtualHalfHeight = math.floor(virtualWidth / 2), math.floor(virtualHeight / 2)
-- Get size of the rectangle in grid/chunk coordinates -- Get size of the rectangle in grid/chunk coordinates.
local gridLeft = math.floor(area.Left / CAPTURE_GRID_SIZE) local gridLeft = math.floor(area.Left / CAPTURE_GRID_SIZE)
local gridTop = math.floor(area.Top / CAPTURE_GRID_SIZE) local gridTop = math.floor(area.Top / CAPTURE_GRID_SIZE)
local gridRight = math.ceil(area.Right / CAPTURE_GRID_SIZE) -- This grid coordinate is not included local gridRight = math.ceil(area.Right / CAPTURE_GRID_SIZE) -- This grid coordinate is not included.
local gridBottom = math.ceil(area.Bottom / CAPTURE_GRID_SIZE) -- This grid coordinate is not included local gridBottom = math.ceil(area.Bottom / CAPTURE_GRID_SIZE) -- This grid coordinate is not included.
-- Edge case -- Edge case
if area.Left == area.Right then if area.Left == area.Right then
@ -159,13 +179,13 @@ function startCapturingHilbert(area)
gridBottom = gridTop gridBottom = gridTop
end end
-- Size of the grid in chunks -- Size of the grid in chunks.
local gridWidth = gridRight - gridLeft local gridWidth = gridRight - gridLeft
local gridHeight = gridBottom - gridTop local gridHeight = gridBottom - gridTop
-- Hilbert curve can only fit into a square, so get the longest side -- Hilbert curve can only fit into a square, so get the longest side.
local gridPOTSize = math.ceil(math.log(math.max(gridWidth, gridHeight)) / math.log(2)) local gridPOTSize = math.ceil(math.log(math.max(gridWidth, gridHeight)) / math.log(2))
-- Max size (Already rounded up to the next power of two) -- Max size (Already rounded up to the next power of two).
local gridMaxSize = math.pow(2, gridPOTSize) local gridMaxSize = math.pow(2, gridPOTSize)
local t, tLimit = 0, gridMaxSize * gridMaxSize local t, tLimit = 0, gridMaxSize * gridMaxSize
@ -176,15 +196,15 @@ function startCapturingHilbert(area)
GameSetCameraFree(true) GameSetCameraFree(true)
-- Coroutine to calculate next coordinate, and trigger screenshots -- Coroutine to calculate next coordinate, and trigger screenshots.
async( async(
function() function()
while t < tLimit do while t < tLimit do
local hx, hy = mapHilbert(t, gridPOTSize) local hx, hy = mapHilbert(t, gridPOTSize)
if hx < gridWidth and hy < gridHeight then if hx < gridWidth and hy < gridHeight then
local x, y = (hx + gridLeft) * CAPTURE_GRID_SIZE, (hy + gridTop) * CAPTURE_GRID_SIZE local x, y = (hx + gridLeft) * CAPTURE_GRID_SIZE, (hy + gridTop) * CAPTURE_GRID_SIZE
x, y = x + 256, y + 256 -- Align screen with ingame chunk grid that is 512x512 x, y = x + 256, y + 256 -- Align screen with ingame chunk grid that is 512x512.
local rx, ry = x * CAPTURE_PIXEL_SIZE - virtualHalfWidth, y * CAPTURE_PIXEL_SIZE - virtualHalfHeight local rx, ry = (x - virtualHalfWidth) * CAPTURE_PIXEL_SIZE, (y - virtualHalfHeight) * CAPTURE_PIXEL_SIZE
if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then if not fileExists(string.format("mods/noita-mapcap/output/%d,%d.png", rx, ry)) then
captureScreenshot(x, y, rx, ry) captureScreenshot(x, y, rx, ry)
end end