HUD interpolation, and a POI marker test
This commit is contained in:
parent
ba17b7f195
commit
889aa531ba
7 changed files with 393 additions and 4 deletions
|
|
@ -1,2 +1,3 @@
|
||||||
title = red_glazed_terracotta
|
title = Red Glazed Terracotta
|
||||||
description = Segmentation fault (core dumped).
|
description = Segmentation fault (core dumped).
|
||||||
|
disabled_settings = !enable_damage
|
||||||
335
mods/rgt_hud/init.lua
Normal file
335
mods/rgt_hud/init.lua
Normal file
|
|
@ -0,0 +1,335 @@
|
||||||
|
rgt_hud = {
|
||||||
|
types = {},
|
||||||
|
Element = {
|
||||||
|
animate = function(e, target)
|
||||||
|
if not e.targets then e.targets = {} end
|
||||||
|
local time = minetest.get_us_time()
|
||||||
|
for k, v in pairs(target) do
|
||||||
|
e.targets[k] = {
|
||||||
|
ref = {
|
||||||
|
time = time,
|
||||||
|
value = e[k]
|
||||||
|
},
|
||||||
|
target = {
|
||||||
|
time = time +(v.duration or 1) *1000000,
|
||||||
|
value = v.value
|
||||||
|
},
|
||||||
|
ease_fn = v.ease_fn
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local ns = rgt_hud
|
||||||
|
|
||||||
|
-- Helper function to compute the eased factor (0 to 1) using cubic Bezier interpolation.
|
||||||
|
-- This solves for the parameter u where the x-component matches the input t,
|
||||||
|
-- then returns the corresponding y-component.
|
||||||
|
local function bezier_ease(t, x1, y1, x2, y2)
|
||||||
|
if t <= 0 then return 0 end
|
||||||
|
if t >= 1 then return 1 end
|
||||||
|
|
||||||
|
-- Binary search to find u such that bezier_x(u) ≈ t
|
||||||
|
local low = 0
|
||||||
|
local high = 1
|
||||||
|
local epsilon = 1e-6
|
||||||
|
local iterations = 0
|
||||||
|
while (high - low > epsilon) and (iterations < 100) do -- Safeguard max iterations
|
||||||
|
local mid = (low + high) / 2
|
||||||
|
local x = 3 * mid * (1 - mid) ^ 2 * x1 + 3 * mid ^ 2 * (1 - mid) * x2 + mid ^ 3
|
||||||
|
if x < t then
|
||||||
|
low = mid
|
||||||
|
else
|
||||||
|
high = mid
|
||||||
|
end
|
||||||
|
iterations = iterations + 1
|
||||||
|
end
|
||||||
|
local u = (low + high) / 2
|
||||||
|
|
||||||
|
-- Compute y at u
|
||||||
|
local y = 3 * u * (1 - u) ^ 2 * y1 + 3 * u ^ 2 * (1 - u) * y2 + u ^ 3
|
||||||
|
return y
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main interpolation function.
|
||||||
|
-- Interpolates between ref and target using weight t (0 to 1),
|
||||||
|
-- eased by the specified cubic Bezier control points (x1, y1, x2, y2).
|
||||||
|
local function interpolate(ref, target, t, x1, y1, x2, y2)
|
||||||
|
-- if type(ref) == "table" then
|
||||||
|
-- local out = {}
|
||||||
|
-- for k, v in pairs(ref) do
|
||||||
|
-- out[k] = interpolate(ref[k], target[k], (el[key] -ref[k]) /(target[k] -ref[k]))
|
||||||
|
-- end
|
||||||
|
-- return out
|
||||||
|
-- end
|
||||||
|
local eased_t = bezier_ease(t, x1 or 0, y1 or 0, x2 or 1, y2 or 1)
|
||||||
|
return ref + (target - ref) * eased_t
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Register a data layout and render delegate for a given HUD type name.
|
||||||
|
{
|
||||||
|
name = "", -- The name of the type.
|
||||||
|
required_fields = {"pos"}, -- A list of the names of fields that must be provided for this element to render properly.
|
||||||
|
field_types = { -- A mapping of field names to their types. Used to notify the API which fields should be treated as vectors. If a field is just a number, it need not be listed.
|
||||||
|
pos = "vec2"
|
||||||
|
},
|
||||||
|
defaults = { -- Specifies default values for optional fields.
|
||||||
|
color = "#ffffff"
|
||||||
|
},
|
||||||
|
add = function(self, m) end, -- Called when the element should be added to `m`'s HUD.
|
||||||
|
update = function(self, m, changes) end, -- Called when the element should be visually updated. `changes` is a table containing only the fields that have been changed in the current step, in order to facilitate interaction with hud_change.
|
||||||
|
remove = function(self, m) end, -- Called when the element should be removed from `m`'s HUD.
|
||||||
|
}
|
||||||
|
Types are responsible for managing the actual HUD elements.
|
||||||
|
--]]
|
||||||
|
function ns.register_hud_type(def)
|
||||||
|
ns.types[def.name] = setmetatable(def, {__index = ns.Element})
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.validate_type(elem, type)
|
||||||
|
if not ns.types[elem.type] then
|
||||||
|
warn("Unknown HUD type `"..type.."` for element `"..elem.name.."`; ignoring.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
if ns.types[type].required_fields then
|
||||||
|
for _, field in ipairs(ns.types[type].required_fields) do
|
||||||
|
if elem[field] == nil then return false end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
--[[
|
||||||
|
{
|
||||||
|
name = "name", -- The name of the element. If an element with this name already exists, it will be updated.
|
||||||
|
type = ""|""|"",
|
||||||
|
|
||||||
|
}
|
||||||
|
--]]
|
||||||
|
function ns.hud_add(m, def)
|
||||||
|
if not ns.validate_type(def, def.type) then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local type = ns.types[def.type]
|
||||||
|
if type.defaults then
|
||||||
|
def = extend(table.copy(type.defaults), def)
|
||||||
|
end
|
||||||
|
local el
|
||||||
|
if m.hud[def.name] then
|
||||||
|
el = m.hud[def.name]
|
||||||
|
-- Simply write all modified fields to the existing element.
|
||||||
|
extend(el, def)
|
||||||
|
else
|
||||||
|
el = setmetatable(def, {__index = type})
|
||||||
|
m.hud[def.name] = el
|
||||||
|
el:add(m)
|
||||||
|
end
|
||||||
|
return el
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.update_poi(m)
|
||||||
|
for _, x in pairs(m.poi) do
|
||||||
|
x:remove(m)
|
||||||
|
end
|
||||||
|
m.poi = {}
|
||||||
|
for _, x in ipairs(minetest.find_nodes_in_area(m.pos:offset(-100, -100, -100), m.pos:offset(100,100,100), "group:poi")) do
|
||||||
|
m.poi[#m.poi +1] = ns.hud_add(m, {
|
||||||
|
name = "poi:"..x:to_string(),
|
||||||
|
type = "poi",
|
||||||
|
world_pos = x,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
rgt.register_node("poi", {
|
||||||
|
tiles = {"rgt_oak_planks.png^[colorize:#fff:0.5"},
|
||||||
|
groups = {poi = 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
local default_ease_fn = {0,0,1,1}
|
||||||
|
minetest.register_globalstep(function(dtime)
|
||||||
|
local time = minetest.get_us_time()
|
||||||
|
for _, m in pairs(rgt.players) do
|
||||||
|
for k, el in pairs(m.hud) do
|
||||||
|
if el.remove_after then
|
||||||
|
el.remove_after = el.remove_after -dtime
|
||||||
|
if el.remove_after < 0 then
|
||||||
|
el:remove(m)
|
||||||
|
m.hud[k] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if el.targets and next(el.targets) then
|
||||||
|
local changes = {}
|
||||||
|
for key, target in pairs(el.targets) do
|
||||||
|
local fac = (time -target.ref.time) /(target.target.time -target.ref.time)
|
||||||
|
local ease_fn = target.ease_fn or default_ease_fn
|
||||||
|
local value
|
||||||
|
if el.field_types[key] == "vec2" then
|
||||||
|
value = {
|
||||||
|
x = interpolate(target.ref.value.x, target.target.value.x, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]),
|
||||||
|
y = interpolate(target.ref.value.y, target.target.value.y, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4])
|
||||||
|
}
|
||||||
|
if fac >= 1 then --value.x == target.target.value.x and value.y == target.target.value.y then
|
||||||
|
el.targets[key] = nil
|
||||||
|
end
|
||||||
|
elseif el.field_types[key] == "color" then
|
||||||
|
value = {
|
||||||
|
r = interpolate(target.ref.value.r, target.target.value.r, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]),
|
||||||
|
g = interpolate(target.ref.value.g, target.target.value.g, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]),
|
||||||
|
b = interpolate(target.ref.value.b, target.target.value.b, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]),
|
||||||
|
a = interpolate(target.ref.value.a, target.target.value.a, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4]),
|
||||||
|
}
|
||||||
|
if value.r == target.target.value.r and value.g == target.target.value.g and value.b == target.target.value.b and value.a == target.target.value.a then
|
||||||
|
el.targets[key] = nil
|
||||||
|
end
|
||||||
|
else
|
||||||
|
value = interpolate(target.ref.value, target.target.value, fac, ease_fn[1], ease_fn[2], ease_fn[3], ease_fn[4])
|
||||||
|
if value == target.target.value then
|
||||||
|
el.targets[key] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
el[key] = value
|
||||||
|
-- We could just set this to true, but since we already
|
||||||
|
-- have a new table, we might as well use it.
|
||||||
|
changes[key] = value
|
||||||
|
end
|
||||||
|
el:update(m, changes)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, el in ipairs(m.poi) do
|
||||||
|
if m.dir:distance(m.pos:direction(el.world_pos)) < 0.05 then
|
||||||
|
el.focused = true
|
||||||
|
el:animate {
|
||||||
|
scale = {
|
||||||
|
value = {x=2,y=2},
|
||||||
|
duration = 0.2,
|
||||||
|
-- ease_fn = {0.42, 0, 1, 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif el.focused then
|
||||||
|
el.focused = false
|
||||||
|
el:animate {
|
||||||
|
scale = {
|
||||||
|
value = {x=1,y=1},
|
||||||
|
duration = 0.2,
|
||||||
|
-- ease_fn = {0.42, 0, 1, 1}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
function ns.color_to_number(color)
|
||||||
|
return tonumber(string.format("0x%.2x%.2x%.2x%.2x", color.r, color.g, color.b, color.a))
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Pre-register some basic HUD types
|
||||||
|
|
||||||
|
ns.register_hud_type {
|
||||||
|
name = "text",
|
||||||
|
required_fields = {"pos", "text"},
|
||||||
|
field_types = {
|
||||||
|
offset = "vec2",
|
||||||
|
pos = "vec2",
|
||||||
|
color = "color"
|
||||||
|
},
|
||||||
|
defaults = {
|
||||||
|
dir = 0,
|
||||||
|
align = {x=0, y=0},
|
||||||
|
offset = {x=0, y=0},
|
||||||
|
color = {r = 0xff, g = 0xff, b = 0xff, a = 0xff}
|
||||||
|
},
|
||||||
|
add = function(e, m)
|
||||||
|
e._id = m.object:hud_add {
|
||||||
|
type = "text",
|
||||||
|
position = e.pos,
|
||||||
|
direction = e.dir,
|
||||||
|
alignment = e.align,
|
||||||
|
offset = e.offset,
|
||||||
|
-- What does this even do?
|
||||||
|
scale = {x=100, y=100},
|
||||||
|
text = e.text,
|
||||||
|
number = ns.color_to_number(e.color)
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
update = function(e, m, changes)
|
||||||
|
for k, v in pairs(changes) do
|
||||||
|
if k == "color" then
|
||||||
|
k = "number"
|
||||||
|
v = ns.color_to_number(v)
|
||||||
|
elseif k == "dir" then
|
||||||
|
k = "direction"
|
||||||
|
elseif k == "align" then
|
||||||
|
k = "alignment"
|
||||||
|
end
|
||||||
|
m.object:hud_change(e._id, k, v)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
remove = function(e, m)
|
||||||
|
m.object:hud_remove(e._id)
|
||||||
|
e._id = nil
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
ns.register_hud_type {
|
||||||
|
name = "poi",
|
||||||
|
required_fields = {"world_pos"},
|
||||||
|
field_types = {
|
||||||
|
scale = "vec2"
|
||||||
|
},
|
||||||
|
defaults = {
|
||||||
|
scale = {x=1,y=1}
|
||||||
|
},
|
||||||
|
add = function(e, m)
|
||||||
|
e._id = m.object:hud_add {
|
||||||
|
type = "image_waypoint",
|
||||||
|
scale = e.scale,
|
||||||
|
world_pos = e.world_pos,
|
||||||
|
text = "rgt_acacia_planks.png"
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
update = function(e, m, changes)
|
||||||
|
for k, v in pairs(changes) do
|
||||||
|
if k == "image" then
|
||||||
|
k = "text"
|
||||||
|
end
|
||||||
|
m.object:hud_change(e._id, k, v)
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
remove = function(e, m)
|
||||||
|
m.object:hud_remove(e._id)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
minetest.register_chatcommand("hudtest", {
|
||||||
|
func = function(name)
|
||||||
|
ns.hud_add(rgt.players[name], {
|
||||||
|
name = "test_toast",
|
||||||
|
type = "text",
|
||||||
|
pos = {x=0.5, y=0.9},
|
||||||
|
offset = {x=0, y=9},
|
||||||
|
text = "Hello",
|
||||||
|
remove_after = 5
|
||||||
|
}):animate {
|
||||||
|
offset = {
|
||||||
|
value = {x = 0, y = -100},
|
||||||
|
duration = 1,
|
||||||
|
ease_fn = {0.42, 0, 0.58, 1}
|
||||||
|
},
|
||||||
|
color = {
|
||||||
|
value = minetest.colorspec_to_table("#33aaff00"),
|
||||||
|
duration = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
})
|
||||||
|
|
||||||
|
minetest.register_chatcommand("poi", {
|
||||||
|
func = function(name)
|
||||||
|
ns.update_poi(rgt.players[name])
|
||||||
|
end
|
||||||
|
})
|
||||||
2
mods/rgt_hud/mod.conf
Normal file
2
mods/rgt_hud/mod.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
name = rgt_hud
|
||||||
|
depends = rgt_player
|
||||||
|
|
@ -40,6 +40,9 @@ Player = {
|
||||||
|
|
||||||
e.eye_height = 1.6
|
e.eye_height = 1.6
|
||||||
|
|
||||||
|
e.hud = {}
|
||||||
|
e.poi = {}
|
||||||
|
|
||||||
e:update_hp(p:get_hp())
|
e:update_hp(p:get_hp())
|
||||||
|
|
||||||
return e
|
return e
|
||||||
|
|
@ -76,7 +79,7 @@ Player = {
|
||||||
end
|
end
|
||||||
local pitch = p:get_look_vertical()
|
local pitch = p:get_look_vertical()
|
||||||
local yaw = p:get_look_horizontal()
|
local yaw = p:get_look_horizontal()
|
||||||
local dir = p:get_look_dir()
|
m.dir = p:get_look_dir()
|
||||||
local pos = p:get_pos()
|
local pos = p:get_pos()
|
||||||
pos.y = pos.y +m.eye_height
|
pos.y = pos.y +m.eye_height
|
||||||
|
|
||||||
|
|
@ -223,7 +226,7 @@ Player = {
|
||||||
m.pointed_node = nil
|
m.pointed_node = nil
|
||||||
|
|
||||||
local pointed_found = false
|
local pointed_found = false
|
||||||
for pointed in minetest.raycast(pos, pos +(dir *7)) do -- TODO: Automatic range
|
for pointed in minetest.raycast(pos, pos +(m.dir *7)) do -- TODO: Automatic range
|
||||||
if pointed and pointed.type == "object" then
|
if pointed and pointed.type == "object" then
|
||||||
local e = pointed.ref:get_luaentity()
|
local e = pointed.ref:get_luaentity()
|
||||||
if e then
|
if e then
|
||||||
|
|
|
||||||
46
mods/rgt_ui/init.lua
Normal file
46
mods/rgt_ui/init.lua
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
-- The purpose of this module is to abstract away element theming in formspecs.
|
||||||
|
-- While formspecs themselves are a good UI format, custom theming can become
|
||||||
|
-- troublesome, especially when it needs to be added into every single formspec.
|
||||||
|
-- This module also abstracts away the construction of some more complex widgets.
|
||||||
|
|
||||||
|
ui = {}
|
||||||
|
local ns = ui
|
||||||
|
|
||||||
|
function ns.headers(w, h)
|
||||||
|
return "formspec_version[10]\
|
||||||
|
size["..w..","..h.."]\
|
||||||
|
"
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.container(x --[[Float]], y --[[Float]], body --[[String]]) --> String
|
||||||
|
return string.format("container[%f,%f]\
|
||||||
|
%s\
|
||||||
|
container_end[]\
|
||||||
|
", x, y, body)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Note: This automatically creates the container's associated scrollbar, just in case scrollbar theming is ever added.
|
||||||
|
function ns.scroll_container(x, y, width, height, name, orientation, factor, padding, body)
|
||||||
|
return string.format("scroll_container[%f,%f;%f,%f;%s;%s;%s;%s]\
|
||||||
|
%s\
|
||||||
|
scroll_container_end[]\
|
||||||
|
%s\
|
||||||
|
", x, y, width, height, name or "", orientation or "", factor and tostring(factor) or "", padding or "", ns.scrollbar(x +width -0.25, y, 0.25, height, name, orientation))
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.scrollbar(x, y, width, height, name, value, main, max, smallstep, largestep, thumbsize, arrows)
|
||||||
|
local out = string.format("scrollbar[%f,%f;%f,%f;%s;%s]", x, y, width, height, name, value and tostring(value) or "")
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.label(x --[[FLoat]], y --[[Float]], text --[[String]]) --> String
|
||||||
|
return string.format("label[%f,%f;%s]\n", x, y, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ns.button(x --[[Float]], y --[[Float]], width --[[Float]], height --[[Float]], name --[[String?]], label --[[String?]]) --> String
|
||||||
|
return string.format("button[%f,%f;%f,%f;%s;%s]\n", x, y, name or "", label or "")
|
||||||
|
end
|
||||||
|
|
||||||
|
local function show_test_view()
|
||||||
|
ui.begin()
|
||||||
|
end
|
||||||
2
mods/rgt_ui/mod.conf
Normal file
2
mods/rgt_ui/mod.conf
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
name = rgt_ui
|
||||||
|
depends = rgt_base
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 468 B After Width: | Height: | Size: 399 B |
Loading…
Add table
Add a link
Reference in a new issue