From aeb596379bbf1bec84efb294ff5bbbee922364ba Mon Sep 17 00:00:00 2001 From: cursed22bc Date: Sat, 7 Mar 2026 23:43:32 +0200 Subject: basic water render and physics --- liquidSurface.lua | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 liquidSurface.lua (limited to 'liquidSurface.lua') diff --git a/liquidSurface.lua b/liquidSurface.lua new file mode 100644 index 0000000..6d9b24e --- /dev/null +++ b/liquidSurface.lua @@ -0,0 +1,169 @@ +local LiquidSurface = {} +LiquidSurface.__index = LiquidSurface + +local DEFAULT_COLORS = { + water = { 0.35, 0.65, 1, 0.75 }, + lava = { 1, 0.35, 0.1, 0.8 } +} + +local function toNumber(value, fallback) + local number = tonumber(value) + if number == nil then + return fallback + end + return number +end + +local function getSurfaceBounds(entity) + local polygon = entity and entity.polygon or nil + if not polygon or #polygon == 0 then + return entity.x or 0, entity.y or 0, entity.width or 0, entity.height or 0 + end + + local minX, minY = math.huge, math.huge + local maxX, maxY = -math.huge, -math.huge + for _, point in ipairs(polygon) do + local px = (entity.x or 0) + (point.x or 0) + local py = (entity.y or 0) + (point.y or 0) + minX = math.min(minX, px) + minY = math.min(minY, py) + maxX = math.max(maxX, px) + maxY = math.max(maxY, py) + end + + return minX, minY, maxX - minX, maxY - minY +end + +function LiquidSurface:new(source) + local self = setmetatable({}, LiquidSurface) + local properties = source.properties or {} + + local x, y, width, height = getSurfaceBounds(source) + local configuredHeight = toNumber(properties.height, height) + self.x = x + self.y = y + self.width = math.max(2, width) + self.height = math.max(1, configuredHeight) + self.bottomY = self.y + self.height + + self.type = "water" + self.liquidType = properties.liquid_type or "water" + self.color = DEFAULT_COLORS[self.liquidType] or DEFAULT_COLORS.water + + self.tension = toNumber(properties.tension, 0.015) + self.dampening = toNumber(properties.dampening, 0.001) + self.spread = toNumber(properties.spread, 0.1) + self.propagationPasses = math.max(1, math.floor(toNumber(properties.passes, 8))) + self.maxSpeed = toNumber(properties.max_speed, 8) + self.maxWaveAmplitude = toNumber(properties.max_wave_amplitude, self.height * 0.4) + + self.columnsLength = math.max(2, math.floor(self.width)) + self.columns = {} + for _ = 1, self.columnsLength do + table.insert(self.columns, self:createColumn()) + end + + return self +end + +function LiquidSurface:createColumn() + return { + height = self.height, + targetHeight = self.height, + speed = 0 + } +end + +function LiquidSurface:isFixedColumn(index) + return index <= 2 or index >= self.columnsLength - 1 +end + +function LiquidSurface:updateColumn(index, dtScale) + if self:isFixedColumn(index) then return end + + local column = self.columns[index] + local heightDiff = column.targetHeight - column.height + local accel = self.tension * heightDiff - column.speed * self.dampening + + column.speed = column.speed + accel * dtScale + column.speed = math.max(-self.maxSpeed, math.min(self.maxSpeed, column.speed)) + + column.height = column.height + column.speed * dtScale + local lo = column.targetHeight - self.maxWaveAmplitude + local hi = column.targetHeight + self.maxWaveAmplitude + column.height = math.max(lo, math.min(hi, column.height)) +end + +function LiquidSurface:splash(worldX, speed) + if worldX < self.x or worldX >= (self.x + self.columnsLength) then + return + end + + local index = math.min(self.columnsLength, math.max(1, math.floor(worldX - self.x) + 1)) + if self:isFixedColumn(index) then + return + end + + self.columns[index].speed = self.columns[index].speed - (speed or 2) +end + +function LiquidSurface:isTouched(x, y, dx, dy) + return math.abs(y - self.y) < math.abs(dy) + and x >= self.x + and x < (self.x + self.columnsLength) +end + +function LiquidSurface:update(dt) + local dtScale = math.max(0, dt or 0) * 60 + + for i = 1, self.columnsLength do + self:updateColumn(i, dtScale) + end + + local leftDeltas = {} + local rightDeltas = {} + + for _ = 1, self.propagationPasses do + for i = 1, self.columnsLength do + if i > 1 and not self:isFixedColumn(i - 1) then + leftDeltas[i] = self.spread * (self.columns[i].height - self.columns[i - 1].height) * dtScale + self.columns[i - 1].speed = self.columns[i - 1].speed + leftDeltas[i] + end + if i < self.columnsLength and not self:isFixedColumn(i + 1) then + rightDeltas[i] = self.spread * (self.columns[i].height - self.columns[i + 1].height) * dtScale + self.columns[i + 1].speed = self.columns[i + 1].speed + rightDeltas[i] + end + end + + for i = 1, self.columnsLength do + if i > 1 and not self:isFixedColumn(i - 1) and leftDeltas[i] then + self.columns[i - 1].height = self.columns[i - 1].height + leftDeltas[i] + end + if i < self.columnsLength and not self:isFixedColumn(i + 1) and rightDeltas[i] then + self.columns[i + 1].height = self.columns[i + 1].height + rightDeltas[i] + end + end + end +end + +function LiquidSurface:draw() + love.graphics.setColor(self.color) + + for i = 2, self.columnsLength do + local p1x = self.x + (i - 2) + local p1y = self.bottomY - self.columns[i - 1].height + local p2x = self.x + (i - 1) + local p2y = self.bottomY - self.columns[i].height + local p3x, p3y = p2x, self.bottomY + local p4x, p4y = p1x, self.bottomY + + love.graphics.polygon("fill", + p1x, p1y, p3x, p3y, p2x, p2y, + p3x, p3y, p4x, p4y, p2x, p2y + ) + end + + love.graphics.setColor(1, 1, 1, 1) +end + +return LiquidSurface -- cgit v1.2.3