room changes

This commit is contained in:
Your Name 2026-08-12 03:02:07 -04:00
parent 8fae34b803
commit 38eaba2fa6
7 changed files with 2333 additions and 2108 deletions

View file

@ -306,6 +306,10 @@ function _initCanvases()
gameCanvas = love.graphics.newCanvas() gameCanvas = love.graphics.newCanvas()
finalCanvas = love.graphics.newCanvas() finalCanvas = love.graphics.newCanvas()
presentationCanvas = love.graphics.newCanvas() presentationCanvas = love.graphics.newCanvas()
-- Reused for each cardinal neighbour's alternate real-colour view at the
-- porous room boundary. Full-screen dimensions keep it pixel-aligned with
-- the gameplay canvas during shader compositing.
porousRoomCanvas = love.graphics.newCanvas()
sideBarCanvas = love.graphics.newCanvas(math.max(1, sideBarBox.w), math.max(1, sideBarBox.h)) sideBarCanvas = love.graphics.newCanvas(math.max(1, sideBarBox.w), math.max(1, sideBarBox.h))
sideBarCanvas:setFilter("nearest", "nearest") -- never average when sampled sideBarCanvas:setFilter("nearest", "nearest") -- never average when sampled
end end

View file

@ -130,17 +130,30 @@ function Room:update(dt)
end end
end end
function Room:draw() -- A room's view is defined by the colour player copies that are in that room,
-- independent of which of those copies is currently being controlled. A caller
-- may supply another set for World's porous boundary without altering sprites
-- or colour blending.
function Room:getRenderChannels()
if gameWorld and gameWorld.roomBrowseMode then return {"red", "green", "blue"} end
local channels = {}
for _, color in ipairs({"red", "green", "blue"}) do
if self.player[color] then table.insert(channels, color) end
end
return channels
end
function Room:draw(drawChannels)
if gameWorld and gameWorld.levels and gameWorld.levels[self.name] == self and if gameWorld and gameWorld.levels and gameWorld.levels[self.name] == self and
not gameWorld.drawingRoomNeighborhood then not gameWorld.drawingRoomNeighborhood then
return gameWorld:drawRoomNeighborhood(self) return gameWorld:drawRoomNeighborhood(self)
end end
self:drawWiresAt(0, 0, width / gridWidth, nil, false) drawChannels = drawChannels or self:getRenderChannels()
self:drawWiresAt(0, 0, width / gridWidth, nil, false, drawChannels)
-- draw only the channels the player has. Each object just exposes sprites per -- Draw only the channels active in this view. Each object just exposes sprites per
-- channel via Entity:draw(channel); a door registered on all three channels -- channel via Entity:draw(channel); a door registered on all three channels
-- naturally draws its red sprite on the red pass, etc. No per-object logic. -- naturally draws its red sprite on the red pass, etc. No per-object logic.
local drawChannels = gameWorld and gameWorld.roomBrowseMode and {"red", "green", "blue"} or self.colorsPlayerHas
for _, channel in ipairs(drawChannels) do for _, channel in ipairs(drawChannels) do
if self.player[channel] then self.player[channel]:draw(channel) end if self.player[channel] then self.player[channel]:draw(channel) end
for _, entity in pairs(self.collideables[channel]) do entity:draw(channel) end for _, entity in pairs(self.collideables[channel]) do entity:draw(channel) end
@ -190,7 +203,7 @@ function Room:removeWire(cell, circuitId)
return true return true
end end
function Room:drawWiresAt(originX, originY, cellSize, selectedCircuit, editorColors) function Room:drawWiresAt(originX, originY, cellSize, selectedCircuit, editorColors, renderChannels)
local ids = {} local ids = {}
for _, row in pairs(self.wires) do for _, row in pairs(self.wires) do
for _, tracks in pairs(row) do for _, tracks in pairs(row) do
@ -205,7 +218,7 @@ function Room:drawWiresAt(originX, originY, cellSize, selectedCircuit, editorCol
local showAllChannels = gameWorld and gameWorld.roomBrowseMode local showAllChannels = gameWorld and gameWorld.roomBrowseMode
local visibleColors = {red = showAllChannels, green = showAllChannels, blue = showAllChannels} local visibleColors = {red = showAllChannels, green = showAllChannels, blue = showAllChannels}
if not showAllChannels then if not showAllChannels then
for _, color in ipairs(self.colorsPlayerHas or {}) do visibleColors[color] = true end for _, color in ipairs(renderChannels or self:getRenderChannels()) do visibleColors[color] = true end
end end
local editorIsOn = editorView ~= nil and editorView ~= "off" local editorIsOn = editorView ~= nil and editorView ~= "off"
for _, circuitId in ipairs(ordered) do for _, circuitId in ipairs(ordered) do

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -128,6 +128,44 @@ wireNeonShader = love.graphics.newShader[[
} }
]] ]]
-- World renders a neighbouring room twice for the porous seam: once through
-- its own active channels and once through the current room's channels. This
-- shader only controls the alpha of that second, already-authored render, so
-- it never approximates the game's literal RGB colour treatment.
porousRoomBoundaryShader = love.graphics.newShader[[
extern number porousTime;
extern vec4 porousRoomRect;
extern vec2 porousDirection;
extern number porousDepth;
float porousWave(vec2 point) {
float broad = sin(point.x * 0.095 + point.y * 0.071 + porousTime * 1.45);
float fine = sin(point.x * 0.231 - point.y * 0.187 - porousTime * 2.20);
return broad * 0.105 + fine * 0.050;
}
vec4 effect(vec4 color, Image texture, vec2 textureCoords, vec2 screenCoords) {
vec4 alternateView = Texel(texture, textureCoords) * color;
vec2 local = screenCoords - porousRoomRect.xy;
vec2 size = porousRoomRect.zw;
if (local.x < 0.0 || local.y < 0.0 || local.x > size.x || local.y > size.y)
return vec4(0.0);
float depth;
if (porousDirection.x > 0.5) depth = local.x;
else if (porousDirection.x < -0.5) depth = size.x - local.x;
else if (porousDirection.y > 0.5) depth = local.y;
else depth = size.y - local.y;
float towardSeam = 1.0 - smoothstep(0.0, porousDepth, depth);
// Animated fingers soften the fifty-fifty transition across the visible
// overlap while preserving the pixels of the real alternate room render.
float mask = smoothstep(0.18, 0.82, towardSeam + porousWave(screenCoords));
alternateView.a *= mask;
return alternateView;
}
]]
function drawGoopySprite(sprite, x, y, scale) function drawGoopySprite(sprite, x, y, scale)
local w, h = sprite:getWidth(), sprite:getHeight() local w, h = sprite:getWidth(), sprite:getHeight()
local time = love.timer.getTime() local time = love.timer.getTime()

View file

@ -963,20 +963,61 @@ function World:drawRoomNeighborhood(room)
local transform = self:roomNeighborhoodTransform(room) local transform = self:roomNeighborhoodTransform(room)
if not transform then return end if not transform then return end
self.drawingRoomNeighborhood = true self.drawingRoomNeighborhood = true
local currentChannels = room:getRenderChannels()
local roomWidth, roomHeight = width * transform.zoom, height * transform.zoom
local function drawAt(offsetX, offsetY, level, channels)
love.graphics.push()
love.graphics.translate(transform.originX + (offsetX + transform.cameraX) * roomWidth,
transform.originY + (offsetY + transform.cameraY) * roomHeight)
love.graphics.scale(transform.zoom, transform.zoom)
level:draw(channels)
love.graphics.pop()
end
for roomY = -1, 1 do for roomY = -1, 1 do
for roomX = -1, 1 do for roomX = -1, 1 do
local name = self:roomAt(transform.location.x + roomX, transform.location.y + roomY) local name = self:roomAt(transform.location.x + roomX, transform.location.y + roomY)
local adjacent = name and self.levels[name] local adjacent = name and self.levels[name]
if adjacent then if adjacent then
love.graphics.push() drawAt(roomX, roomY, adjacent)
love.graphics.translate(transform.originX + (roomX + transform.cameraX) * width * transform.zoom,
transform.originY + (roomY + transform.cameraY) * height * transform.zoom)
love.graphics.scale(transform.zoom, transform.zoom)
adjacent:draw()
love.graphics.pop()
end end
end end
end end
-- Each cardinal neighbour receives a second, real render through the active
-- colours in the current room. The shader only chooses a wispy portion of
-- the shared visible margin, preserving all literal RGB passes and sprites.
if porousRoomCanvas and porousRoomBoundaryShader and #currentChannels > 0 then
local targetCanvas = love.graphics.getCanvas()
local directions = {
{ x = -1, y = 0 }, { x = 1, y = 0 }, { x = 0, y = -1 }, { x = 0, y = 1 },
}
for _, direction in ipairs(directions) do
local name = self:roomAt(transform.location.x + direction.x, transform.location.y + direction.y)
local adjacent = name and self.levels[name]
if adjacent then
love.graphics.setCanvas(porousRoomCanvas)
love.graphics.clear(0, 0, 0, 0)
drawAt(direction.x, direction.y, adjacent, currentChannels)
love.graphics.setCanvas(targetCanvas)
local roomLeft = transform.originX + (direction.x + transform.cameraX) * roomWidth
local roomTop = transform.originY + (direction.y + transform.cameraY) * roomHeight
porousRoomBoundaryShader:send("porousTime", love.timer.getTime())
porousRoomBoundaryShader:send("porousRoomRect", {roomLeft, roomTop, roomWidth, roomHeight})
porousRoomBoundaryShader:send("porousDirection", {direction.x, direction.y})
-- Exactly the amount of the neighbour exposed by the zoom-out.
porousRoomBoundaryShader:send("porousDepth", (1 - transform.zoom) * width / 2)
love.graphics.setBlendMode("alpha")
love.graphics.setColor(1, 1, 1, 1)
love.graphics.setShader(porousRoomBoundaryShader)
love.graphics.draw(porousRoomCanvas, 0, 0)
love.graphics.setShader()
end
end
love.graphics.setBlendMode("screen")
end
self.drawingRoomNeighborhood = false self.drawingRoomNeighborhood = false
end end