Initial commit

This commit is contained in:
shylie 2025-07-14 12:56:11 -04:00
commit c9e1982d1c
3 changed files with 202 additions and 0 deletions

20
LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright (c) 2025 shylie
This software is provided as-is, without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

32
main.lua Normal file
View File

@ -0,0 +1,32 @@
local tex = love.graphics.newImage("mrmo3x.png")
tex:setFilter("nearest", "nearest")
local function color(layer)
if layer < 27 then
return layer / 31 / 2.5, layer / 62 / 2.5, 0.1
else
return 0.05, (layer - 27) / 10 + 0.3, 0.05
end
end
local BIG_BLOCK = love.graphics.newQuad(24 * 9, 24 * 9, 24, 24, tex)
local z = require("zprite").new(tex, 65536)
z._height_scale = 6
for i = 1, 32 do
for j = 1, 32 do
local layers = love.math.noise(i * 0.1, j * 0.1) * 20 + 12
z:put(i * 24, j * 24, BIG_BLOCK, math.floor(layers) + 1, color)
end
end
local angle = 0
function love.update(dt)
angle = math.fmod(angle + dt * 1.2, 2 * math.pi)
end
function love.draw()
z:draw(love.graphics.getWidth() / 2, love.graphics.getHeight() / 2 + 32, angle, 0.6, 0.6, 24 * 32 / 2, 24 * 32 / 2)
end

150
zprite/init.lua Normal file
View File

@ -0,0 +1,150 @@
---@class zprite
---@field _texture love.Image
---@field _instance_vertex_count integer
---@field _mesh love.Mesh
---@field _instance_mesh love.Mesh
---@field _instances {}[]
---@field _height_scale number
---@field _changed boolean
local zprite = {}
zprite.__index = zprite
local ZPRITE_SHADER = love.graphics.newShader([[
varying vec4 VsColorOut;
#ifdef VERTEX
attribute vec2 InstancePosition;
attribute vec2 InstanceTextureOffset;
attribute vec2 InstanceScale;
attribute float InstanceLayer;
attribute vec4 InstanceColor;
uniform float HeightScale;
vec4 position(mat4 transform_projection, vec4 vertex_position)
{
vertex_position.xy *= InstanceScale;
vertex_position.xy += InstancePosition.xy;
vertex_position.z = InstanceLayer / 1024;
VaryingTexCoord.xy *= InstanceScale;
VaryingTexCoord.xy += InstanceTextureOffset;
VsColorOut = InstanceColor;
return (transform_projection * vertex_position) + vec4(0, (InstanceLayer * 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, 1
end
---@param texture love.Image
---@param max_instances integer?
function zprite.new(texture, max_instances)
if not texture then
error("texture cannot be nil")
end
if max_instances and max_instances < 1 then
error("max_instances cannot be less than 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(
{ { "VertexPosition", "float", 2 }, { "VertexTexCoord", "float", 2 } },
vertices,
"strip",
"static"
)
obj._mesh:setTexture(texture)
obj._instance_mesh = love.graphics.newMesh({
{ "InstancePosition", "float", 2 },
{ "InstanceTextureOffset", "float", 2 },
{ "InstanceScale", "float", 2 },
{ "InstanceLayer", "float", 1 },
{ "InstanceColor", "float", 4 },
}, obj._instance_vertex_count, nil, "dynamic")
obj._instances = {}
obj._height_scale = 4
obj._changed = false
obj._mesh:attachAttribute("InstancePosition", obj._instance_mesh, "perinstance")
obj._mesh:attachAttribute("InstanceTextureOffset", obj._instance_mesh, "perinstance")
obj._mesh:attachAttribute("InstanceScale", obj._instance_mesh, "perinstance")
obj._mesh:attachAttribute("InstanceLayer", obj._instance_mesh, "perinstance")
obj._mesh:attachAttribute("InstanceColor", obj._instance_mesh, "perinstance")
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 = {}
end
--- Add an 'entity' to be rendered
---@param x number
---@param y number
---@param quad love.Quad
---@param layer_count integer
---@param color_map function?
---@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 qx, qy, qw, qh = quad:getViewport()
local tw, th = self._texture:getPixelDimensions()
for i = 1, layer_count do
local r, g, b, a = color_map(i)
if not a then
a = 1
end
table.insert(self._instances, { x, y, qx / tw, qy / th, qw, qh, i + base_layer, r, g, b, a })
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.setDepthMode("less", true)
love.graphics.setShader(ZPRITE_SHADER)
ZPRITE_SHADER:send("HeightScale", self._height_scale)
if self._changed then
self._instance_mesh:setVertices(self._instances)
self._changed = false
end
love.graphics.drawInstanced(self._mesh, #self._instances, ...)
end
return zprite