summaryrefslogtreecommitdiff
path: root/liquidSurface.lua
diff options
context:
space:
mode:
Diffstat (limited to 'liquidSurface.lua')
-rw-r--r--liquidSurface.lua169
1 files changed, 169 insertions, 0 deletions
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