213 lines
5.8 KiB
Lua
213 lines
5.8 KiB
Lua
---@class zprite
|
|
---@field private _texture love.Image
|
|
---@field private _instance_vertex_count integer
|
|
---@field private _mesh love.Mesh
|
|
---@field private _instance_buffer love.Buffer
|
|
---@field private _instances {integer: {}[]}
|
|
---@field private _top_layer integer
|
|
---@field private _height_scale number
|
|
---@field private _changed boolean
|
|
---@field private _count integer
|
|
local zprite = {}
|
|
zprite.__index = zprite
|
|
|
|
local ZPRITE_SHADER = love.graphics.newShader([[
|
|
#pragma language glsl4
|
|
|
|
varying vec4 VsColorOut;
|
|
|
|
#ifdef VERTEX
|
|
layout (location = 3) attribute uvec3 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 layer = bitfieldExtract(InstanceData.z, 0, 8);
|
|
|
|
uint r = bitfieldExtract(InstanceData.z, 8, 8);
|
|
uint g = bitfieldExtract(InstanceData.z, 16, 8);
|
|
uint b = bitfieldExtract(InstanceData.z, 24, 8);
|
|
vec3 color = vec3(r, g, b) / vec3(256, 256, 256);
|
|
|
|
vertex_position.xy *= scale;
|
|
vertex_position.xy += position;
|
|
VaryingTexCoord.xy *= scale;
|
|
VaryingTexCoord.xy += uv;
|
|
VsColorOut = vec4(color, 1);
|
|
return (transform_projection * vertex_position) + vec4(0, (layer * HeightScale) / love_ScreenSize.y, 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
|
|
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 tw, th = texture:getPixelDimensions()
|
|
local vertices = {
|
|
{ 0, 0, 0, 0 },
|
|
{ 1, 0, 1 / tw, 0 },
|
|
{ 0, 1, 0, 1 / th },
|
|
{ 1, 1, 1 / tw, 1 / th },
|
|
}
|
|
|
|
local obj = setmetatable({}, zprite)
|
|
|
|
obj._texture = texture
|
|
obj._instance_vertex_count = max_instances or 16384
|
|
obj._mesh = love.graphics.newMesh({
|
|
{ name = "VertexPosition", format = "floatvec2", location = 0 },
|
|
{ name = "VertexTexCoord", format = "floatvec2", location = 1 },
|
|
}, vertices, "strip", "static")
|
|
obj._mesh:setTexture(texture)
|
|
obj._instance_buffer = love.graphics.newBuffer({
|
|
{ name = "InstanceData", format = "uint32vec3", location = 3 },
|
|
}, obj._instance_vertex_count, { vertex = true })
|
|
obj._instances = {}
|
|
obj._height_scale = 4
|
|
obj._changed = false
|
|
|
|
obj._mesh:attachAttribute(3, obj._instance_buffer, "perinstance")
|
|
|
|
obj._top_layer = 0
|
|
obj._count = 0
|
|
|
|
return obj
|
|
end
|
|
|
|
--- Clear all drawn objects
|
|
function zprite:clear()
|
|
-- no need to set _changed to true, as #self._instances will be 0
|
|
self._instances = {}
|
|
self._top_layer = 0
|
|
self._count = 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): integer, integer, integer)?
|
|
---@param base_layer integer?
|
|
function zprite:put(x, y, quad, layer_count, color_map, base_layer)
|
|
if #self._instances + layer_count > self._instance_vertex_count then
|
|
return
|
|
end
|
|
|
|
base_layer = base_layer or 0
|
|
color_map = color_map or ZPRITE_DEFAULT_COLORMAP
|
|
|
|
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 + base_layer] then
|
|
self._instances[i + base_layer] = {}
|
|
if i > self._top_layer then
|
|
self._top_layer = i
|
|
end
|
|
end
|
|
local layer = bit.band(i + base_layer, 0xFF)
|
|
|
|
local r, g, b = 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
|
|
local rb = bit.lshift(bit.band(r * 0xFF, 0xFF), 8)
|
|
local gb = bit.lshift(bit.band(g * 0xFF, 0xFF), 16)
|
|
local bb = bit.lshift(bit.band(b * 0xFF, 0xFF), 24)
|
|
|
|
local layer_rgb = bit.bor(bit.bor(layer, rb), bit.bor(gb, bb))
|
|
|
|
local q = type(quad) == "function" and quad(layer) 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)
|
|
|
|
table.insert(self._instances[i], { pos, uv_scale, layer_rgb })
|
|
self._count = self._count + 1
|
|
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(...)
|
|
love.graphics.setShader(ZPRITE_SHADER)
|
|
ZPRITE_SHADER:send("HeightScale", self._height_scale)
|
|
|
|
if self._changed then
|
|
local current = 1
|
|
for i = 1, self._top_layer do
|
|
if self._instances[i] then
|
|
self._instance_buffer:setArrayData(self._instances[i], 1, current, #self._instances[i])
|
|
current = current + #self._instances[i]
|
|
end
|
|
end
|
|
self._changed = false
|
|
end
|
|
|
|
love.graphics.drawInstanced(self._mesh, self._count, ...)
|
|
end
|
|
|
|
return zprite
|