Files
FlexLove/modules/Transform.lua
2025-11-18 19:51:05 -05:00

155 lines
4.5 KiB
Lua

--- Transform module for 2D transformations (rotate, scale, translate, skew)
---@class Transform
---@field rotate number? Rotation in radians (default: 0)
---@field scaleX number? X-axis scale (default: 1)
---@field scaleY number? Y-axis scale (default: 1)
---@field translateX number? X translation in pixels (default: 0)
---@field translateY number? Y translation in pixels (default: 0)
---@field skewX number? X-axis skew in radians (default: 0)
---@field skewY number? Y-axis skew in radians (default: 0)
---@field originX number? Transform origin X (0-1, default: 0.5)
---@field originY number? Transform origin Y (0-1, default: 0.5)
local Transform = {}
Transform.__index = Transform
--- Create a new transform instance
---@param props TransformProps?
---@return Transform transform
function Transform.new(props)
props = props or {}
local self = setmetatable({}, Transform)
self.rotate = props.rotate or 0
self.scaleX = props.scaleX or 1
self.scaleY = props.scaleY or 1
self.translateX = props.translateX or 0
self.translateY = props.translateY or 0
self.skewX = props.skewX or 0
self.skewY = props.skewY or 0
self.originX = props.originX or 0.5
self.originY = props.originY or 0.5
return self
end
--- Apply transform to LÖVE graphics context
---@param transform Transform Transform instance
---@param x number Element x position
---@param y number Element y position
---@param width number Element width
---@param height number Element height
function Transform.apply(transform, x, y, width, height)
if not transform then
return
end
-- Calculate transform origin
local ox = x + width * transform.originX
local oy = y + height * transform.originY
-- Apply transform in correct order: translate → rotate → scale → skew
love.graphics.push()
love.graphics.translate(ox, oy)
if transform.rotate ~= 0 then
love.graphics.rotate(transform.rotate)
end
if transform.scaleX ~= 1 or transform.scaleY ~= 1 then
love.graphics.scale(transform.scaleX, transform.scaleY)
end
if transform.skewX ~= 0 or transform.skewY ~= 0 then
love.graphics.shear(transform.skewX, transform.skewY)
end
love.graphics.translate(-ox, -oy)
love.graphics.translate(transform.translateX, transform.translateY)
end
--- Remove transform from LÖVE graphics context
function Transform.unapply()
love.graphics.pop()
end
--- Interpolate between two transforms
---@param from Transform Starting transform
---@param to Transform Ending transform
---@param t number Interpolation factor (0-1)
---@return Transform interpolated
function Transform.lerp(from, to, t)
-- Sanitize inputs
if type(from) ~= "table" then
from = Transform.new()
end
if type(to) ~= "table" then
to = Transform.new()
end
if type(t) ~= "number" or t ~= t then
-- NaN or invalid type
t = 0
elseif t == math.huge then
-- Positive infinity
t = 1
elseif t == -math.huge then
-- Negative infinity
t = 0
else
-- Clamp t to 0-1 range
t = math.max(0, math.min(1, t))
end
return Transform.new({
rotate = (from.rotate or 0) * (1 - t) + (to.rotate or 0) * t,
scaleX = (from.scaleX or 1) * (1 - t) + (to.scaleX or 1) * t,
scaleY = (from.scaleY or 1) * (1 - t) + (to.scaleY or 1) * t,
translateX = (from.translateX or 0) * (1 - t) + (to.translateX or 0) * t,
translateY = (from.translateY or 0) * (1 - t) + (to.translateY or 0) * t,
skewX = (from.skewX or 0) * (1 - t) + (to.skewX or 0) * t,
skewY = (from.skewY or 0) * (1 - t) + (to.skewY or 0) * t,
originX = (from.originX or 0.5) * (1 - t) + (to.originX or 0.5) * t,
originY = (from.originY or 0.5) * (1 - t) + (to.originY or 0.5) * t,
})
end
--- Check if transform is identity (no transformation)
---@param transform Transform
---@return boolean isIdentity
function Transform.isIdentity(transform)
if not transform then
return true
end
return transform.rotate == 0
and transform.scaleX == 1
and transform.scaleY == 1
and transform.translateX == 0
and transform.translateY == 0
and transform.skewX == 0
and transform.skewY == 0
end
--- Clone a transform
---@param transform Transform
---@return Transform clone
function Transform.clone(transform)
if not transform then
return Transform.new()
end
return Transform.new({
rotate = transform.rotate,
scaleX = transform.scaleX,
scaleY = transform.scaleY,
translateX = transform.translateX,
translateY = transform.translateY,
skewX = transform.skewX,
skewY = transform.skewY,
originX = transform.originX,
originY = transform.originY,
})
end
return Transform