commit c9e1982d1c17009e899e13b0805745074075a4f7 Author: shylie Date: Mon Jul 14 12:56:11 2025 -0400 Initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..53dde2b --- /dev/null +++ b/LICENSE @@ -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. diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..b07c0fd --- /dev/null +++ b/main.lua @@ -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 diff --git a/zprite/init.lua b/zprite/init.lua new file mode 100644 index 0000000..ad5e7fa --- /dev/null +++ b/zprite/init.lua @@ -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