zprite/zprite.lua

247 lines
6.8 KiB
Lua

---@class zprite
---@field private _texture love.Image
---@field private _instance_vertex_count integer
---@field private _meshes love.Mesh[]
---@field private _instance_buffers love.Buffer[]
---@field private _instances {integer: {}[]}
---@field private _top_layer integer
---@field private _height_scale number
---@field private _changed boolean
local zprite = {}
zprite.__index = zprite
local ZPRITE_SHADER = love.graphics.newShader([[
#pragma language glsl4
varying vec4 VsColorOut;
#ifdef VERTEX
layout (location = 3) attribute uvec4 InstanceData;
uniform float HeightScale;
vec4 position(mat4 transform_projection, vec4 vertex_position)
{
uint position_x = bitfieldExtract(InstanceData.x, 0, 16);
uint position_y = bitfieldExtract(InstanceData.x, 16, 16);
vec2 position = vec2(position_x, position_y) - vec2(32768, 32768);
uint uv_x = bitfieldExtract(InstanceData.y, 0, 10);
uint uv_y = bitfieldExtract(InstanceData.y, 10, 10);
vec2 uv = vec2(uv_x, uv_y) / vec2(1024, 1024);
uint scale_x = bitfieldExtract(InstanceData.y, 20, 6);
uint scale_y = bitfieldExtract(InstanceData.y, 26, 6);
vec2 scale = vec2(scale_x, scale_y);
uint r = bitfieldExtract(InstanceData.z, 0, 8);
uint g = bitfieldExtract(InstanceData.z, 8, 8);
uint b = bitfieldExtract(InstanceData.z, 16, 8);
uint a = bitfieldExtract(InstanceData.z, 24, 8);
vec4 color = vec4(r, g, b, a) / vec4(256, 256, 256, 256);
uint layer = bitfieldExtract(InstanceData.w, 0, 8);
uint offset_x = bitfieldExtract(InstanceData.w, 8, 4);
uint offset_y = bitfieldExtract(InstanceData.w, 12, 4);
vec2 offset = vec2(offset_x, offset_y) - vec2(8, 8);
vec2 pixel_offset = offset * vec2(layer * HeightScale, layer * HeightScale) / love_ScreenSize.xy;
vertex_position.xy *= scale;
vertex_position.xy += position;
VaryingTexCoord.xy *= scale;
VaryingTexCoord.xy += uv;
VsColorOut = color;
return (transform_projection * vertex_position) + vec4(pixel_offset, 0, 0);
}
#endif
#ifdef PIXEL
vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
vec4 texture_color = Texel(tex, texture_coords);
return texture_color * color * VsColorOut;
}
#endif
]])
local function ZPRITE_DEFAULT_COLORMAP(_)
return 1, 1, 1, 1
end
local function ZPRITE_DEFAULT_OFFSETMAP(_)
return 0, 2
end
---@param texture love.Image
---@param max_instances integer?
function zprite.new(texture, max_instances)
if not texture then
error("must have a texture")
end
if max_instances and max_instances < 1 then
error("max_instances must be at least 1")
end
local obj = setmetatable({}, zprite)
obj._texture = texture
obj._instance_vertex_count = max_instances or 1024
obj._meshes = {}
obj._instances = {}
obj._instance_buffers = {}
obj._height_scale = 4
obj._changed = false
obj._top_layer = 0
return obj
end
--- Clear all drawn objects
function zprite:clear()
self._changed = true
self._instances = {}
self._top_layer = 0
end
--- Add an 'entity' to be rendered
---@param x number
---@param y number
---@param quad love.Quad|fun(layer: integer): love.Quad
---@param layer_count integer
---@param color_map (fun(layer: integer): number, number, number, number)?
---@param offset_map (fun(layer: integer): integer, integer)?
function zprite:put(x, y, quad, layer_count, color_map, offset_map)
if #self._instances + layer_count > self._instance_vertex_count then
return
end
color_map = color_map and color_map or ZPRITE_DEFAULT_COLORMAP
offset_map = offset_map and offset_map or ZPRITE_DEFAULT_OFFSETMAP
self._changed = true
local pos_x = bit.band(x + 32768, 0xFFFF)
local pos_y = bit.lshift(bit.band(y + 32768, 0xFFFF), 16)
local pos = bit.bor(pos_x, pos_y)
for i = 1, layer_count do
if not self._instances[i] then
self._instances[i] = {}
if i > self._top_layer then
self._top_layer = i
end
end
local r, g, b, a = color_map(i)
if r > 1 then
r = 1
end
if r < 0 then
r = 0
end
if g > 1 then
g = 1
end
if g < 0 then
g = 0
end
if b > 1 then
b = 1
end
if b < 0 then
b = 0
end
if a > 1 then
a = 1
end
if a < 0 then
a = 0
end
local rb = bit.lshift(bit.band(r * 0xFF, 0xFF), 0)
local gb = bit.lshift(bit.band(g * 0xFF, 0xFF), 8)
local bb = bit.lshift(bit.band(b * 0xFF, 0xFF), 16)
local ab = bit.lshift(bit.band(a * 0xFF, 0xFF), 24)
local rgba = bit.bor(rb, gb, bb, ab)
local q = type(quad) == "function" and quad(i) or quad
local qx, qy, qw, qh = q:getViewport()
local tw, th = self._texture:getPixelDimensions()
local uv_x = bit.band(1024 * qx / tw, 0x3FF)
local uv_y = bit.lshift(bit.band(1024 * qy / th, 0x3FF), 10)
local uv = bit.bor(uv_x, uv_y)
local scale_x = bit.lshift(bit.band(qw, 0x3F), 20)
local scale_y = bit.lshift(bit.band(qh, 0x3F), 26)
local scale = bit.bor(scale_x, scale_y)
local uv_scale = bit.bor(uv, scale)
local offset_x, offset_y = offset_map(i)
local offset_x_b = bit.lshift(bit.band(offset_x - 8, 0xF), 8)
local offset_y_b = bit.lshift(bit.band(offset_y - 8, 0xF), 12)
local layer = bit.band(i, 0xFF)
local layer_offset = bit.bor(layer, offset_x_b, offset_y_b)
table.insert(self._instances[i], pos)
table.insert(self._instances[i], uv_scale)
table.insert(self._instances[i], rgba)
table.insert(self._instances[i], layer_offset)
end
end
--- Draw to the screen / current canvas
---@overload fun(self, x: number, y: number, r: number?, sx: number?, sy: number?, ox: number?, oy: number?, kx: number?, ky: number?)
---@overload fun(self, transform: love.Transform)
function zprite:draw(...)
zprite._set_shader(self._height_scale)
self:_upload()
for i = 1, self._top_layer do
if self._instances[i] then
love.graphics.drawInstanced(self._meshes[i], #self._instances[i] / 4, ...)
end
end
end
function zprite._set_shader(height_scale)
love.graphics.setShader(ZPRITE_SHADER)
ZPRITE_SHADER:send("HeightScale", height_scale)
end
function zprite:_upload()
if self._changed then
for i = 1, self._top_layer do
if self._instances[i] then
if not self._instance_buffers[i] then
local tw, th = self._texture:getPixelDimensions()
local vertices = {
{ 0, 0, 0, 0 },
{ 1, 0, 1 / tw, 0 },
{ 0, 1, 0, 1 / th },
{ 1, 1, 1 / tw, 1 / th },
}
self._meshes[i] = love.graphics.newMesh({
{ name = "VertexPosition", format = "floatvec2", location = 0 },
{ name = "VertexTexCoord", format = "floatvec2", location = 1 },
}, vertices, "strip", "static")
self._meshes[i]:setTexture(self._texture)
self._instance_buffers[i] = love.graphics.newBuffer({
{ name = "InstanceData", format = "uint32vec4", location = 3 },
}, self._instance_vertex_count, { vertex = true })
self._meshes[i]:attachAttribute(3, self._instance_buffers[i], "perinstance")
end
self._instance_buffers[i]:setArrayData(self._instances[i], 1, 1, #self._instances[i] / 4)
end
end
self._changed = false
end
end
return zprite