Skip to content

Commit

Permalink
Test shaders
Browse files Browse the repository at this point in the history
  • Loading branch information
Insality committed Dec 3, 2024
1 parent 917a84f commit 9a1cd79
Show file tree
Hide file tree
Showing 11 changed files with 668 additions and 1 deletion.
37 changes: 37 additions & 0 deletions druid/druid.script
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- Place this script nearby with the gui component to able make requests
-- To the go namespace from GUI with events systems (cross context)

local event_queue = require("druid.event_queue")

---Usage: event_queue.request("druid.get_atlas_path", callback, gui.get_texture(self.node), msg.url())
---Pass texture name to get atlas info and sender url to check if the request is valid
local MESSAGE_GET_ATLAS_PATH = "druid.get_atlas_path"


---@param texture_name hash The name from gui.get_texture(node)
---@param sender hash Just msg.url from the caller
local function get_atlas_path(texture_name, sender)
local my_url = msg.url()
my_url.fragment = nil

local copy_url = msg.url(sender)
copy_url.fragment = nil

-- This check should works well
local is_my_url = my_url == copy_url
if not is_my_url then
return nil
end

return go.get(sender, "textures", { key = texture_name })
end


function init(self)
event_queue.subscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path)
end


function final(self)
event_queue.unsubscribe(MESSAGE_GET_ATLAS_PATH, get_atlas_path)
end
84 changes: 84 additions & 0 deletions druid/event_queue.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
local event = require("event.event")

---@class event.queue
local M = {}

local event_handlers = {}
local pending_callbacks = {}


---Request to handle a specified event and processes the queue of callbacks associated with it.
---If event has already been triggered, the callback will be executed immediately.
---If event not triggered yet, callback will be executed when event will be triggered.
---It triggered only once and then removed from the queue.
---@param event_name string The name of the event to trigger.
---@param callback fun() The callback function to execute upon triggering.
---@param ... any Additional arguments for the callback.
function M.request(event_name, callback, ...)
pending_callbacks[event_name] = pending_callbacks[event_name] or {}
table.insert(pending_callbacks[event_name], { event.create(callback), ... })

M.process_pending_callbacks(event_name)
end


---Subscribes to a specified event and executes a callback when the event is triggered.
-- If the event has already been triggered, the callback will be executed immediately.
---@param event_name string The name of the event to subscribe to.
---@param callback fun() The function to call when the event is triggered.
function M.subscribe(event_name, callback)
event_handlers[event_name] = event_handlers[event_name] or event.create()

if event_handlers[event_name] then
event_handlers[event_name]:subscribe(callback)
end

M.process_pending_callbacks(event_name)
end


---Unsubscribes a callback function from a specified event.
---@param event_name string The name of the event to unsubscribe from.
---@param callback fun() The function to remove from the event's subscription list.
function M.unsubscribe(event_name, callback)
if event_handlers[event_name] then
event_handlers[event_name]:unsubscribe(callback)
end
end


---Processes the queue for a given event name, executing callbacks and handling results.
---Processed callbacks are removed from the queue.
---@param event_name string The name of the event for which to process the queue.
function M.process_pending_callbacks(event_name)
local callbacks_to_process = pending_callbacks[event_name]
local event_handler = event_handlers[event_name]

if not callbacks_to_process or not event_handler then
return
end

-- Loop through the queue in reverse to prevent index errors during removal
for i = #callbacks_to_process, 1, -1 do
local callback_entry = callbacks_to_process[i]
-- Better to figure out how to make it without 2 unpacks, but ok for all our cases now
local args = { unpack(callback_entry, 2) }

-- Safely call the event handler and handle errors
local success, result = pcall(event_handler.trigger, event_handler, unpack(args))

if success and result then
local callback_function = callback_entry[1]
pcall(callback_function, result) -- Safely invoke the callback, catching any errors
table.remove(callbacks_to_process, i) -- Remove the processed callback from the queue
end
end

-- Clean up if the callback queue is empty
if #callbacks_to_process == 0 then
pending_callbacks[event_name] = nil
end
end


return M
122 changes: 122 additions & 0 deletions druid/helper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ local POSITION_X = hash("position.x")
local SCALE_X = hash("scale.x")
local SIZE_X = hash("size.x")

M.PROP_SIZE_X = hash("size.x")
M.PROP_SIZE_Y = hash("size.y")
M.PROP_SCALE_X = hash("scale.x")
M.PROP_SCALE_Y = hash("scale.y")

local function get_text_width(text_node)
if text_node then
local text_metrics = M.get_text_metrics_from_node(text_node)
Expand Down Expand Up @@ -540,4 +545,121 @@ function M.get_full_position(node, root)
end


---@class druid.animation_data
---@field frames table<number, table<string, number>> @List of frames with uv coordinates and size
---@field width number @Width of the animation
---@field height number @Height of the animation
---@field fps number @Frames per second
---@field current_frame number @Current frame
---@field node node @Node with flipbook animation
---@field v vector4 @Vector with UV coordinates and size

---@param node node
---@param atlas_path string @Path to the atlas
---@return druid.animation_data
function M.get_animation_data_from_node(node, atlas_path)
local atlas_data = resource.get_atlas(atlas_path)
local tex_info = resource.get_texture_info(atlas_data.texture)
local tex_w = tex_info.width
local tex_h = tex_info.height

local animation_data

local sprite_image_id = gui.get_flipbook(node)
for _, animation in ipairs(atlas_data.animations) do
if hash(animation.id) == sprite_image_id then
animation_data = animation
break
end
end
assert(animation_data, "Unable to find image " .. sprite_image_id)

local frames = {}
for index = animation_data.frame_start, animation_data.frame_end - 1 do
local uvs = atlas_data.geometries[index].uvs
assert(#uvs == 8, "Sprite trim mode should be disabled for the images.")

-- UV texture coordinates
-- 1
-- ^ V
-- |
-- |
-- | U
-- 0-------> 1

-- uvs = {
-- 0, 0,
-- 0, height,
-- width, height,
-- width, 0
-- },
-- Point indeces (Point number {uv_index_x, uv_index_y})
-- geometries.indices = {0 (1,2), 1(3,4), 2(5,6), 0(1,2), 2(5,6), 3(7,8)}
-- 1------2
-- | / |
-- | A / |
-- | / B |
-- | / |
-- 0------3

local width = uvs[5] - uvs[1] -- Width of sprite region
local height = uvs[2] - uvs[4] -- Height of sprite region
local is_rotated = height < 0 -- In case of rotated sprite

local x_left = uvs[1]
local y_bottom = uvs[2]
local x_right = uvs[5]
local y_top = uvs[6]

-- Okay now it's correct for non rotated
local uv_coord = vmath.vector4(
x_left / tex_w,
(tex_h - y_bottom) / tex_h,
x_right / tex_w,
(tex_h - y_top) / tex_h
)

if is_rotated then
-- In case the atlas has clockwise rotated sprite.
-- 0---------------1
-- | \ A |
-- | \ |
-- | \ |
-- | B \ |
-- 3---------------2
height = -height

uv_coord.x, uv_coord.y, uv_coord.z, uv_coord.w = uv_coord.y, uv_coord.z, uv_coord.w, uv_coord.x

-- Update uv_coord
--uv_coord = vmath.vector4(
-- u1 / tex_w,
-- (tex_h - v2) / tex_h,
-- u2 / tex_w,
-- (tex_h - v1) / tex_h
--)
end

local frame = {
uv_coord = uv_coord,
w = width,
h = height,
uv_rotated = is_rotated and vmath.vector4(0, 1, 0, 0) or vmath.vector4(1, 0, 0, 0)
}

table.insert(frames, frame)
end

return {
frames = frames,
width = animation_data.width,
height = animation_data.height,
fps = animation_data.fps,
v = vmath.vector4(1, 1, animation_data.width, animation_data.height),
current_frame = 1,
node = node,
}
end


return M
84 changes: 84 additions & 0 deletions druid/materials/gui_repeat/gui_repeat.fp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#version 140

uniform sampler2D texture_sampler;

in vec2 var_texcoord0;
in vec4 var_color;
in vec4 var_uv;
in vec4 var_repeat; // [repeat_x, repeat_y, anchor_x, anchor_y]
in vec4 var_params; // [margin_x, margin_y, offset_x, offset_y]
in vec4 var_perspective;
in vec4 var_uv_rotated;

out vec4 color_out;

void main() {
vec2 pivot = var_repeat.zw;
// Margin is a value between 0 and 1 that means offset/padding from the one image to another
vec2 margin = var_params.xy;
vec2 offset = var_params.zw;
vec2 repeat = var_repeat.xy;

// Atlas UV to local UV [0, 1]
float u = (var_texcoord0.x - var_uv.x) / (var_uv.z - var_uv.x);
float v = (var_texcoord0.y - var_uv.y) / (var_uv.w - var_uv.y);

// Adjust local UV by the pivot point. So 0:0 will be at the pivot point of node
u = u - (0.5 + pivot.x);
v = v - (0.5 - pivot.y);

// If rotated, swap UV
if (var_uv_rotated.y < 0.5) {
float temp = u;
u = v;
v = temp;
}

// Adjust repeat by the margin
repeat.x = repeat.x / (1.0 + margin.x);
repeat.y = repeat.y / (1.0 + margin.y);

// Repeat is a value between 0 and 1 that represents the number of times the texture is repeated in the atlas.
float tile_u = fract(u * repeat.x);
float tile_v = fract(v * repeat.y);

float tile_width = 1.0 / repeat.x;
float tile_height = 1.0 / repeat.y;

// Adjust tile UV by the pivot point.
// Not center is left top corner, need to adjust it to pivot point
tile_u = fract(tile_u + pivot.x + 0.5);
tile_v = fract(tile_v - pivot.y + 0.5);

// Apply offset
tile_u = fract(tile_u + offset.x);
tile_v = fract(tile_v + offset.y);

// Extend margins
margin = margin * 0.5;
tile_u = mix(0.0 - margin.x, 1.0 + margin.x, tile_u);
tile_v = mix(0.0 - margin.y, 1.0 + margin.y, tile_v);
float alpha = 0.0;
// If the tile is outside the margins, make it transparent, without IF
alpha = step(0.0, tile_u) * step(tile_u, 1.0) * step(0.0, tile_v) * step(tile_v, 1.0);

tile_u = clamp(tile_u, 0.0, 1.0); // Keep borders in the range 0-1
tile_v = clamp(tile_v, 0.0, 1.0); // Keep borders in the range 0-1

if (var_uv_rotated.y < 0.5) {
float temp = tile_u;
tile_u = tile_v;
tile_v = temp;
}

// Remap local UV to the atlas UV
vec2 uv = vec2(
mix(var_uv.x, var_uv.z, tile_u), // Get texture coordinate from the atlas
mix(var_uv.y, var_uv.w, tile_v) // Get texture coordinate from the atlas
//mix(var_uv.x, var_uv.z, tile_u * var_uv_rotated.x + tile_v * var_uv_rotated.z),
//mix(var_uv.y, var_uv.w, 1.0 - (tile_u * var_uv_rotated.y + tile_v * var_uv_rotated.x))
);

lowp vec4 tex = texture(texture_sampler, uv);
color_out = tex * var_color;
}
43 changes: 43 additions & 0 deletions druid/materials/gui_repeat/gui_repeat.material
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: "repeat"
tags: "gui"
vertex_program: "/druid/materials/gui_repeat/gui_repeat.vp"
fragment_program: "/druid/materials/gui_repeat/gui_repeat.fp"
vertex_constants {
name: "view_proj"
type: CONSTANT_TYPE_VIEWPROJ
}
vertex_constants {
name: "uv_coord"
type: CONSTANT_TYPE_USER
value {
z: 1.0
w: 1.0
}
}
vertex_constants {
name: "uv_repeat"
type: CONSTANT_TYPE_USER
value {
x: 1.0
y: 1.0
}
}
vertex_constants {
name: "params"
type: CONSTANT_TYPE_USER
value {
}
}
vertex_constants {
name: "perspective"
type: CONSTANT_TYPE_USER
value {
}
}
vertex_constants {
name: "uv_rotated"
type: CONSTANT_TYPE_USER
value {
x: 1.0
}
}
Loading

0 comments on commit 9a1cd79

Please sign in to comment.