local ns = artifact ns.hud_types = {} ns.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 function bezier_ease(t, x1, y1, x2, y2) if t <= 0 then return 0 end if t >= 1 then return 1 end local low = 0 local high = 1 local epsilon = 1e-6 local iterations = 0 while (high - low > epsilon) and (iterations < 100) do 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 local y = 3 * u * (1 - u) ^ 2 * y1 + 3 * u ^ 2 * (1 - u) * y2 + u ^ 3 return y end local function interpolate(ref, target, t, x1, y1, x2, y2) 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 function ns.register_hud_type(def) ns.hud_types[def.name] = setmetatable(def, {__index = ns.Element}) end function ns.validate_type(elem, type) if not ns.hud_types[elem.type] then warn("Unknown HUD type `"..type.."` for element `"..elem.name.."`; ignoring.") return false end if ns.hud_types[type].required_fields then for _, field in ipairs(ns.hud_types[type].required_fields) do if elem[field] == nil then return false end end end return true end function ns.hud_add(m, def) if not ns.validate_type(def, def.type) then return false end local type = ns.hud_types[def.type] if type.defaults then def = extend(table.copy(type.defaults), def) end -- Create a random name if none is given, since the -- assumption is that the user doesn't care about the name. if not def.name then def.name = ""..math.random() 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 local default_ease_fn = {0,0,1,1} minetest.register_globalstep(function(dtime) local time = minetest.get_us_time() for _, m in pairs(artifact.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 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, } } elseif el.focused then el.focused = false el:animate { scale = { value = {x=1,y=1}, duration = 0.2, } } 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 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, 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.hud[e.name] = nil m.object:hud_remove(e._id) e._id = nil -- Prevent ongoing animations from attempting to change a nonexistent element. e.update = function() end end } ns.register_hud_type { name = "image", required_fields = {"pos", "image"}, field_types = { offset = "vec2", scale = "vec2", pos = "vec2" }, defaults = { dir = 0, align = {x=0, y=0}, offset = {x=0, y=0}, scale = {x=1, y=1}, opacity = 256 }, add = function(e, m) e._id = m.object:hud_add { type = "image", position = e.pos, direction = e.dir, alignment = e.align, offset = e.offset, scale = e.scale, text = e.image..string.format("^[opacity:%i", e.opacity) } end, update = function(e, m, changes) for k, v in pairs(changes) do if k == "align" then k = "alignment" elseif k == "pos" then k = "position" elseif k == "opacity" then k = "text" v = e.image..string.format("^[opacity:%i", e.opacity) end m.object:hud_change(e._id, k, v) end end, remove = function(e, m) m.hud[e.name] = nil m.object:hud_remove(e._id) e._id = nil -- Prevent ongoing animations from attempting to change a nonexistent element. e.update = function() end end }