Add powered neon wire rendering
This commit is contained in:
parent
cb1b5006b9
commit
17140fe39f
4 changed files with 71 additions and 0 deletions
9
room.lua
9
room.lua
|
|
@ -239,12 +239,21 @@ function Room:drawWiresAt(originX, originY, cellSize, selectedCircuit, editorCol
|
||||||
for _, line in ipairs(lines) do love.graphics.line(line) end
|
for _, line in ipairs(lines) do love.graphics.line(line) end
|
||||||
local wireBlend = (editorColors or not switchColors) and "alpha" or "screen"
|
local wireBlend = (editorColors or not switchColors) and "alpha" or "screen"
|
||||||
love.graphics.setBlendMode(wireBlend)
|
love.graphics.setBlendMode(wireBlend)
|
||||||
|
local previousShader = love.graphics.getShader()
|
||||||
|
if not editorColors and switchColors and wireNeonShader then
|
||||||
|
local powered = gameWorld:wirePoweredAt(self, localCell, circuitId)
|
||||||
|
wireNeonShader:send("wirePower", powered and 1 or 0)
|
||||||
|
wireNeonShader:send("wireTime", love.timer.getTime())
|
||||||
|
wireNeonShader:send("wirePhase", (#circuitId * 0.137) + x * 0.071 + y * 0.113)
|
||||||
|
love.graphics.setShader(wireNeonShader)
|
||||||
|
end
|
||||||
love.graphics.setLineWidth(math.max(1, cellSize * 0.045))
|
love.graphics.setLineWidth(math.max(1, cellSize * 0.045))
|
||||||
for _, color in ipairs(drawColors) do
|
for _, color in ipairs(drawColors) do
|
||||||
love.graphics.setColor(color[1], color[2], color[3], selected and 1 or 0.32)
|
love.graphics.setColor(color[1], color[2], color[3], selected and 1 or 0.32)
|
||||||
for _, line in ipairs(lines) do love.graphics.line(line) end
|
for _, line in ipairs(lines) do love.graphics.line(line) end
|
||||||
love.graphics.circle("fill", cx, cy, math.max(0.75, cellSize * 0.0275))
|
love.graphics.circle("fill", cx, cy, math.max(0.75, cellSize * 0.0275))
|
||||||
end
|
end
|
||||||
|
love.graphics.setShader(previousShader)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
39
shaders.lua
39
shaders.lua
|
|
@ -89,6 +89,45 @@ goopShader = love.graphics.newShader[[
|
||||||
}
|
}
|
||||||
]]
|
]]
|
||||||
|
|
||||||
|
-- Wires are drawn as primitives rather than textured sprites, but LÖVE still
|
||||||
|
-- runs this shader over its default white texture. Inactive runs stay visibly
|
||||||
|
-- connected at half brightness; powered runs get a travelling neon shimmer,
|
||||||
|
-- occasional tube-like noise, and a sub-pixel bend rather than a global fade.
|
||||||
|
wireNeonShader = love.graphics.newShader[[
|
||||||
|
extern number wirePower;
|
||||||
|
extern number wireTime;
|
||||||
|
extern number wirePhase;
|
||||||
|
|
||||||
|
float wireHash(vec2 point) {
|
||||||
|
return fract(sin(dot(point, vec2(127.1, 311.7))) * 43758.5453123);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 position(mat4 transformProjection, vec4 vertexPosition) {
|
||||||
|
if (wirePower < 0.5) return transformProjection * vertexPosition;
|
||||||
|
|
||||||
|
// Every segment sharing an endpoint receives the same offset, so corners
|
||||||
|
// stay connected while the live current gives the tube a gentle movement.
|
||||||
|
float wave = sin(vertexPosition.x * 0.085 + vertexPosition.y * 0.13 +
|
||||||
|
wireTime * 4.4 + wirePhase * 9.0);
|
||||||
|
float wobble = wave * 0.32;
|
||||||
|
vertexPosition.xy += vec2(wobble, -wobble * 0.55);
|
||||||
|
return transformProjection * vertexPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 effect(vec4 color, Image texture, vec2 textureCoords, vec2 screenCoords) {
|
||||||
|
if (wirePower < 0.5) return vec4(color.rgb * 0.50, color.a * 0.72);
|
||||||
|
|
||||||
|
float travel = dot(screenCoords, vec2(0.070, 0.043)) - wireTime * 6.8 + wirePhase * 13.0;
|
||||||
|
float shimmer = sin(travel) * 0.075 + sin(travel * 2.37 + 1.7) * 0.035;
|
||||||
|
vec2 noiseCell = floor(screenCoords * 0.34) + floor(wireTime * 15.0) * vec2(1.0, -1.0);
|
||||||
|
float noise = wireHash(noiseCell + wirePhase);
|
||||||
|
float flicker = max(0.0, noise - 0.82) * 0.32;
|
||||||
|
float brightness = 0.92 + shimmer + flicker;
|
||||||
|
float alpha = 0.84 + shimmer * 0.80 + flicker * 0.55;
|
||||||
|
return vec4(color.rgb * brightness, color.a * alpha);
|
||||||
|
}
|
||||||
|
]]
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,15 @@ test("a switchless wire component has no gameplay color", function()
|
||||||
assertTrue(world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a") == nil)
|
assertTrue(world:wireColorAt(roomA, {x = 1, y = 1}, "circuit_a") == nil)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
test("wire power follows its terminal switch", function()
|
||||||
|
local world, roomA = makeWorld()
|
||||||
|
world:resolveCircuits()
|
||||||
|
assertFalse(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a"))
|
||||||
|
world.collidableMatrices.blue[1] = {[4] = {}}
|
||||||
|
world:resolveCircuits()
|
||||||
|
assertTrue(world:wirePoweredAt(roomA, {x = 1, y = 1}, "circuit_a"))
|
||||||
|
end)
|
||||||
|
|
||||||
test("a door requires every independent switch run at its terminal", function()
|
test("a door requires every independent switch run at its terminal", function()
|
||||||
local greenSwitch = switch("green", 1, 2)
|
local greenSwitch = switch("green", 1, 2)
|
||||||
local world, roomA, _, wiredDoor = makeWorld()
|
local world, roomA, _, wiredDoor = makeWorld()
|
||||||
|
|
|
||||||
14
world.lua
14
world.lua
|
|
@ -494,6 +494,20 @@ function World:wireSwitchColorsAt(room, localCell, circuitId)
|
||||||
return colors
|
return colors
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- A valid wire is bright only while every switch at its sole terminal cell is
|
||||||
|
-- pressed. Rendering reads this derived state rather than entity sprite flags
|
||||||
|
-- so every segment of a cross-room component agrees in the same frame.
|
||||||
|
function World:wirePoweredAt(room, localCell, circuitId)
|
||||||
|
local worldCell = self:worldCell(room, localCell)
|
||||||
|
local component = worldCell and self.circuitComponents and
|
||||||
|
self.circuitComponents[circuitId] and self.circuitComponents[circuitId][cellKey(worldCell)]
|
||||||
|
if not component or not component.valid then return false end
|
||||||
|
for switch in pairs(component.switches) do
|
||||||
|
if not (self.circuitState and self.circuitState.switchPressed[switch]) then return false end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
function World:wireColorAt(room, localCell, circuitId, visibleColors)
|
function World:wireColorAt(room, localCell, circuitId, visibleColors)
|
||||||
local colors = self:wireSwitchColorsAt(room, localCell, circuitId)
|
local colors = self:wireSwitchColorsAt(room, localCell, circuitId)
|
||||||
if not colors then return nil end
|
if not colors then return nil end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue