better nameplate
This commit is contained in:
parent
16f8f244ab
commit
b9754e0d81
4 changed files with 591 additions and 19 deletions
|
@ -4,7 +4,8 @@
|
||||||
"authors": [
|
"authors": [
|
||||||
"reidlab",
|
"reidlab",
|
||||||
"mrsirsquishy: Squishy's API",
|
"mrsirsquishy: Squishy's API",
|
||||||
"purpledeni + Manuel_: Gradient Scroll Nickname",
|
"Agapurnis: Gradient and nameplate script",
|
||||||
|
"skyevg: Oklab script",
|
||||||
"adristel: Wet Clothes/Fur Script"
|
"adristel: Wet Clothes/Fur Script"
|
||||||
],
|
],
|
||||||
"color": "#d87b5a",
|
"color": "#d87b5a",
|
||||||
|
|
370
scripts/libs/gradient.lua
Normal file
370
scripts/libs/gradient.lua
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
-- work in pewgross
|
||||||
|
-- do not share !!
|
||||||
|
|
||||||
|
---@class UnitRGBA: Vector4 [r, g, b, a]; all values are 0-1.
|
||||||
|
local UnitRGBA = {};
|
||||||
|
|
||||||
|
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
|
||||||
|
---@return UnitRGBA? output nil the vector with the new metatable if valid, otherwise nil
|
||||||
|
---@return string? error a string describing the error if invalid, otherwise nil
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromUnitStrict to throw an error instead of returning nil
|
||||||
|
function UnitRGBA.fromUnit(vector)
|
||||||
|
for i, v in ipairs({vector:unpack()}) do
|
||||||
|
if type(v) ~= "number" then
|
||||||
|
return nil, "Vector component " .. i .. " is not a number";
|
||||||
|
end
|
||||||
|
if v < 0 or v > 1 then
|
||||||
|
return nil, "Vector component " .. i .. " is not in the range 0-1; got " .. v;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return setmetatable({ vector = vector }, UnitRGBA), nil;
|
||||||
|
end
|
||||||
|
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
|
||||||
|
---@return UnitRGBA vector the vector with the new metatable if valid. otherwise, an error is thrown
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromUnit to return nil instead of throwing an error
|
||||||
|
function UnitRGBA.fromUnitStrict(vector)
|
||||||
|
local result, why = UnitRGBA.fromUnit(vector);
|
||||||
|
if result == nil then
|
||||||
|
error("Invalid vector: " .. why);
|
||||||
|
end
|
||||||
|
return result;
|
||||||
|
end
|
||||||
|
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-255)
|
||||||
|
---@return UnitRGBA? output nil the vector with the new metatable if valid, otherwise nil
|
||||||
|
---@return string? error a string describing the error if invalid, otherwise nil
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromU8Strict to throw an error instead of returning nil
|
||||||
|
function UnitRGBA.fromU8(vector)
|
||||||
|
for i, v in ipairs(vector) do
|
||||||
|
if type(v) ~= "number" then
|
||||||
|
return nil, "Vector component " .. i .. " is not a number";
|
||||||
|
end
|
||||||
|
if v < 0 or v > 255 then
|
||||||
|
return nil, "Vector component " .. i .. " is not in the range 0-255";
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return setmetatable({ vector = vector / 255 }, UnitRGBA), nil;
|
||||||
|
end
|
||||||
|
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-255)
|
||||||
|
---@return UnitRGBA vector the vector with the new metatable if valid. otherwise, an error is thrown
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromU8 to return nil instead of throwing an error
|
||||||
|
function UnitRGBA.fromU8Strict(vector)
|
||||||
|
local result, why = UnitRGBA.fromU8(vector);
|
||||||
|
if result == nil then
|
||||||
|
error("Invalid vector: " .. why);
|
||||||
|
end
|
||||||
|
return result;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param vector Vector4 a Vector4 matching the format of UnitRGBA ([r, g, b, a]; all values are 0-1)
|
||||||
|
---@return UnitRGBA vector the vector with the new metatable
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA.fromUnitClamping(vector)
|
||||||
|
vector[1] = math.clamp(vector[1], 0, 1);
|
||||||
|
vector[2] = math.clamp(vector[2], 0, 1);
|
||||||
|
vector[3] = math.clamp(vector[3], 0, 1);
|
||||||
|
vector[4] = math.clamp(vector[4], 0, 1);
|
||||||
|
return setmetatable({ vector = vector }, UnitRGBA);
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param hex string a color in hexadecimal format (e.g. "#FF00FF")
|
||||||
|
---@return UnitRGBA? output the color with the new metatable if valid, otherwise nil
|
||||||
|
---@return string? error a string describing the error if invalid, otherwise nil
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromHexStrict to throw an error instead of returning nil
|
||||||
|
function UnitRGBA.fromHex(hex)
|
||||||
|
if type(hex) ~= "string" then
|
||||||
|
return nil, "not a string";
|
||||||
|
end
|
||||||
|
if hex:sub(1, 1) == "#" then
|
||||||
|
hex = hex:sub(2);
|
||||||
|
end
|
||||||
|
if hex:len() ~= 6 and hex:len() ~= 8 then
|
||||||
|
return nil, "not 6 or 8 characters long";
|
||||||
|
end
|
||||||
|
local vector = vec(0, 0, 0, 1);
|
||||||
|
for i = 1, 3 do
|
||||||
|
local value = tonumber(hex:sub(i * 2 - 1, i * 2), 16);
|
||||||
|
if value == nil then
|
||||||
|
return nil, "contains non-hex characters";
|
||||||
|
end
|
||||||
|
vector[i] = value / 255;
|
||||||
|
end
|
||||||
|
if hex:len() == 8 then
|
||||||
|
local value = tonumber(hex:sub(7, 8), 16);
|
||||||
|
if value == nil then
|
||||||
|
return nil, "contains non-hex characters";
|
||||||
|
end
|
||||||
|
vector[4] = value / 255;
|
||||||
|
else
|
||||||
|
vector[4] = 1;
|
||||||
|
end
|
||||||
|
return setmetatable({ vector = vector }, UnitRGBA), nil;
|
||||||
|
end
|
||||||
|
---@param hex string a color in hexadecimal format (e.g. "#FF00FF")
|
||||||
|
---@return UnitRGBA color the color with the new metatable if valid. otherwise, an error is thrown
|
||||||
|
---@nodiscard
|
||||||
|
---@see UnitRGBA.fromHex to return nil instead of throwing an error
|
||||||
|
function UnitRGBA.fromHexStrict(hex)
|
||||||
|
local result, why = UnitRGBA.fromHex(hex);
|
||||||
|
if result == nil then
|
||||||
|
error("Invalid hex color: " .. why);
|
||||||
|
end
|
||||||
|
return result;
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean opaque whether the color is *fully opaque* (as in, a == 1, not just a > 0).
|
||||||
|
---@see UnitRGBA:isTranslucent to see if the color is partially or fully transparent.
|
||||||
|
---@see UnitRGBA:isTransparent to see if the color is fully transparent.
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:isOpaque()
|
||||||
|
return self.vector[4] == 1;
|
||||||
|
end
|
||||||
|
---@return boolean transparent whether the color is *fully transparent* (as in, a == 0, not just a < 1).
|
||||||
|
---@see UnitRGBA:isTranslucent to see if the color is partially transparent.
|
||||||
|
---@see UnitRGBA:isOpaque to see if the color is fully opaque.
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:isTransparent()
|
||||||
|
return self.vector[4] == 0;
|
||||||
|
end
|
||||||
|
---@return boolean translucent whether the color is *translucent* (as in, a < 1).
|
||||||
|
---@see UnitRGBA:isTransparent to see if the color is fully transparent.
|
||||||
|
---@see UnitRGBA:isOpaque to see if the color is fully opaque.
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:isTranslucent()
|
||||||
|
return self.vector[4] < 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnitRGBA:toVec3()
|
||||||
|
return vec(self.vector[1], self.vector[2], self.vector[3]);
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@return Vector4 vector Vector4 with format ([r, g, b, a]; all values are 0-255)
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:toU8vec4()
|
||||||
|
return vec(self.vector[1] * 255, self.vector[2] * 255, self.vector[3] * 255, self.vector[4] * 255);
|
||||||
|
end
|
||||||
|
---@return Vector3 vector Vector3 with format ([r, g, b]; all values are 0-255)
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:toU8vec3()
|
||||||
|
local a = vec(self.vector[1] * 255, self.vector[2] * 255, self.vector[3] * 255)
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@param hash? boolean? whether to include the hash symbol (#) in the output. defaults to true
|
||||||
|
---@param alpha? boolean? whether to include the alpha channel in the output. defaults to true
|
||||||
|
---@return string hex the color in RGBA hexadecimal format (e.g. "#FF00FF")
|
||||||
|
---@nodiscard
|
||||||
|
function UnitRGBA:toHex(hash, alpha)
|
||||||
|
local hex = (hash == nil or hash) and "#" or "";
|
||||||
|
local iter = (alpha == nil or alpha) and 4 or 3;
|
||||||
|
for i = 1, iter do
|
||||||
|
local value = math.round(self.vector[i] * 255);
|
||||||
|
hex = hex .. string.format("%02X", value);
|
||||||
|
end
|
||||||
|
return hex;
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnitRGBA:__index(key)
|
||||||
|
return rawget(UnitRGBA, key) or self.vector[key];
|
||||||
|
end
|
||||||
|
function UnitRGBA:__tostring()
|
||||||
|
return "UnitRGBA(" .. table.concat(self.vector, ", ") .. ")";
|
||||||
|
end
|
||||||
|
|
||||||
|
function UnitRGBA:__sub(other)
|
||||||
|
return UnitRGBA.fromUnitClamping(vec(
|
||||||
|
self.vector[1] - other.vector[1],
|
||||||
|
self.vector[2] - other.vector[2],
|
||||||
|
self.vector[3] - other.vector[3],
|
||||||
|
self.vector[4] - other.vector[4]
|
||||||
|
));
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Transparent white.
|
||||||
|
UnitRGBA.TRANSPARENT_WHITE = UnitRGBA.fromUnitStrict(vec(1, 1, 1, 0));
|
||||||
|
--- Transparent black.
|
||||||
|
UnitRGBA.TRANSPARENT_BLACK = UnitRGBA.fromUnitStrict(vec(0, 0, 0, 0));
|
||||||
|
--- Opaque white.
|
||||||
|
UnitRGBA.WHITE = UnitRGBA.fromUnitStrict(vec(1, 1, 1, 1));
|
||||||
|
--- Opaque black.
|
||||||
|
UnitRGBA.BLACK = UnitRGBA.fromUnitStrict(vec(0, 0, 0, 1));
|
||||||
|
|
||||||
|
---@class Keypoint - A keypoint (interpolation point definition) in a gradient.
|
||||||
|
---@field public color UnitRGBA color of the keypoint
|
||||||
|
---@field public time number a number between 0 and 1 (inclusive on both ends)
|
||||||
|
local Keypoint = {};
|
||||||
|
|
||||||
|
---Returns a new Keypoint object.
|
||||||
|
---@param color UnitRGBA color of the keypoint
|
||||||
|
---@param time number a number between 0 and 1 (inclusive on both ends)
|
||||||
|
---@return Keypoint
|
||||||
|
---@nodiscard
|
||||||
|
function Keypoint.new(color, time)
|
||||||
|
return setmetatable({
|
||||||
|
color = color,
|
||||||
|
time = time
|
||||||
|
}, Keypoint);
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class Gradient A gradient definition.
|
||||||
|
---@field public keypoints Keypoint[] a list of keypoint objects
|
||||||
|
---@field public loop boolean whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown.
|
||||||
|
local Gradient = {};
|
||||||
|
|
||||||
|
---Returns a new Gradient object.
|
||||||
|
---@param keypoints Keypoint[] a list of keypoint objects
|
||||||
|
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
|
||||||
|
---@return Gradient
|
||||||
|
---@nodiscard
|
||||||
|
function Gradient.new(keypoints, loop)
|
||||||
|
return setmetatable({
|
||||||
|
keypoints = keypoints,
|
||||||
|
loop = loop == nil or loop
|
||||||
|
}, Gradient);
|
||||||
|
end
|
||||||
|
|
||||||
|
local function easeInOutQuad(t)
|
||||||
|
return t < 0.5 and 2 * t * t or 1 - (-2 * t + 2)^2 / 2
|
||||||
|
end
|
||||||
|
|
||||||
|
local oklab = require("scripts.libs.oklab")
|
||||||
|
---Returns the color of the gradient at a given time.
|
||||||
|
---@param time number a number between 0 and 1 (inclusive on both ends) (can extend beyond this range if Gradient.loop is true)
|
||||||
|
---@return UnitRGBA? color the color of the gradient at the given time, or nil if the time is out of bounds and Gradient.loop is false
|
||||||
|
function Gradient:at(time)
|
||||||
|
if time < 0 or time > 1 then
|
||||||
|
if not self.loop then
|
||||||
|
return nil;
|
||||||
|
end
|
||||||
|
time = time % 1;
|
||||||
|
end
|
||||||
|
|
||||||
|
if time == 0 then return self.keypoints[1].color
|
||||||
|
elseif time == 1 then return self.keypoints[#self.keypoints].color
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, #self.keypoints - 1 do
|
||||||
|
local this = self.keypoints[i]
|
||||||
|
local next = self.keypoints[i + 1]
|
||||||
|
if time >= this.time and time < next.time then
|
||||||
|
local t = easeInOutQuad((time - this.time) / (next.time - this.time));
|
||||||
|
|
||||||
|
local alpha = math.lerp(this.color.a, next.color.a, t)
|
||||||
|
local La, Aa, Ba = oklab.srgbToLinear(this.color.xyz):unpack()
|
||||||
|
local Lb, Ab, Bb = oklab.srgbToLinear(next.color.xyz):unpack()
|
||||||
|
local mixed = vec(
|
||||||
|
math.lerp(La, Lb, easeInOutQuad(t)),
|
||||||
|
math.lerp(Aa, Ab, t),
|
||||||
|
math.lerp(Ba, Bb, t)
|
||||||
|
)
|
||||||
|
|
||||||
|
mixed = oklab.linearToSrgb(mixed);
|
||||||
|
mixed = vec(mixed.x, mixed.y, mixed.z, alpha)
|
||||||
|
return UnitRGBA.fromUnitClamping(mixed);
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if #self.keypoints == 1 then
|
||||||
|
return self.keypoints[1].color;
|
||||||
|
end
|
||||||
|
|
||||||
|
error("Gradient.at: time " .. time .. " is out of bounds");
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param colors string[] a list of colors in hexadecimal format (e.g. "#FF00FF")
|
||||||
|
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
|
||||||
|
---@return Gradient gradient the gradient with the new metatable if valid. otherwise, an error is thrown
|
||||||
|
function Gradient.distributedHex(colors, loop)
|
||||||
|
local keypoints = {};
|
||||||
|
for i, hex in ipairs(colors) do
|
||||||
|
local time = (i - 1) / (#colors - 1);
|
||||||
|
local color = UnitRGBA.fromHexStrict(hex);
|
||||||
|
table.insert(keypoints, Keypoint.new(color, time));
|
||||||
|
end
|
||||||
|
return Gradient.new(keypoints, loop);
|
||||||
|
end
|
||||||
|
|
||||||
|
function Gradient:__index(key)
|
||||||
|
return rawget(Gradient, key) or self[key];
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
---@class SimpleGradientBuilder
|
||||||
|
---@field public colors UnitRGBA[] a list of colors
|
||||||
|
local SimpleGradientBuilder = {};
|
||||||
|
---@return SimpleGradientBuilder builder a simple gradient builder
|
||||||
|
function SimpleGradientBuilder.new()
|
||||||
|
return setmetatable({ colors = {} }, { __index = SimpleGradientBuilder });
|
||||||
|
end
|
||||||
|
---Reflect the colors of the gradient; this makes it nicer when looping.
|
||||||
|
---@param reflect_last boolean? whether to also reflect the final color, such that it would appear twice in a row at the middle. if false, it'll only be present once
|
||||||
|
---@return SimpleGradientBuilder self builder for chaining
|
||||||
|
function SimpleGradientBuilder:reflect(reflect_last)
|
||||||
|
---@type UnitRGBA[]
|
||||||
|
local reflected = {};
|
||||||
|
for i = 1, #self.colors - 1 do
|
||||||
|
table.insert(reflected, self.colors[i]);
|
||||||
|
end
|
||||||
|
table.insert(reflected, self.colors[#self.colors])
|
||||||
|
if reflect_last then
|
||||||
|
table.insert(reflected, self.colors[#self.colors])
|
||||||
|
end
|
||||||
|
for i = #self.colors - 1, 1, -1 do
|
||||||
|
table.insert(reflected, self.colors[i])
|
||||||
|
end
|
||||||
|
self.colors = reflected
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
---"Thicken" the gradient by duplicating every keypoint. This means that the actual colors themselves will have as much presence as the transitions.
|
||||||
|
---@param amount? integer how many times to duplicate each keypoint. defaults to 1.
|
||||||
|
---@return SimpleGradientBuilder self builder for chaining
|
||||||
|
function SimpleGradientBuilder:thicken(amount)
|
||||||
|
amount = amount or 1;
|
||||||
|
local thickened = {};
|
||||||
|
for i = 1, #self.colors do
|
||||||
|
for _ = 0, amount do
|
||||||
|
table.insert(thickened, self.colors[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
self.colors = thickened;
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
---@param loop boolean? whether the gradient should loop when given out-of-bounds time values. if false, an error is instead thrown. defaults to true.
|
||||||
|
---@return Gradient gradient the constructed gradient
|
||||||
|
function SimpleGradientBuilder:build(loop)
|
||||||
|
loop = loop == nil or loop;
|
||||||
|
|
||||||
|
---@type Keypoint[]
|
||||||
|
local keypoints = {};
|
||||||
|
for i, color in ipairs(self.colors) do
|
||||||
|
table.insert(keypoints, Keypoint.new(color, (i - 1) / (#self.colors - 1)));
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable({
|
||||||
|
keypoints = keypoints,
|
||||||
|
loop = loop
|
||||||
|
}, Gradient);
|
||||||
|
end
|
||||||
|
---@param colors string[] | string hexadecimal color(s) to add
|
||||||
|
function SimpleGradientBuilder:add(colors)
|
||||||
|
if type(colors) == "string" then
|
||||||
|
colors = {colors}
|
||||||
|
end
|
||||||
|
for _, color in ipairs(colors) do
|
||||||
|
table.insert(self.colors, UnitRGBA.fromHexStrict(color))
|
||||||
|
end
|
||||||
|
return self;
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
UnitRGBA = UnitRGBA,
|
||||||
|
Keypoint = Keypoint,
|
||||||
|
Gradient = Gradient,
|
||||||
|
SimpleGradientBuilder = SimpleGradientBuilder,
|
||||||
|
}
|
113
scripts/libs/oklab.lua
Normal file
113
scripts/libs/oklab.lua
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
local lib = {}
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@return number
|
||||||
|
local function fromLinear(x)
|
||||||
|
if x >= 0.0031308 then
|
||||||
|
return 1.055 * math.pow(x, 1.0/2.4) - 0.055
|
||||||
|
else
|
||||||
|
return 12.92 * x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param x number
|
||||||
|
---@return number
|
||||||
|
local function toLinear(x)
|
||||||
|
if x >= 0.04045 then
|
||||||
|
return math.pow((x + 0.055)/(1 + 0.055), 2.4)
|
||||||
|
else
|
||||||
|
return x / 12.92
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from sRGB to Linear sRGB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.srgbToLinear(color)
|
||||||
|
return vec(toLinear(color.x), toLinear(color.y), toLinear(color.z))
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from Linear sRGB to sRGB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.linearToSrgb(color)
|
||||||
|
return vec(fromLinear(color.x), fromLinear(color.y), fromLinear(color.z))
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from Linear sRGB to OKLAB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.linearToOklab(color)
|
||||||
|
local l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b
|
||||||
|
local m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b
|
||||||
|
local s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b
|
||||||
|
|
||||||
|
local l_ = math.pow(l, 1/3)
|
||||||
|
local m_ = math.pow(m, 1/3)
|
||||||
|
local s_ = math.pow(s, 1/3)
|
||||||
|
|
||||||
|
return vec(
|
||||||
|
0.2104542553*l_ + 0.7936177850*m_ - 0.0040720468*s_,
|
||||||
|
1.9779984951*l_ - 2.4285922050*m_ + 0.4505937099*s_,
|
||||||
|
0.0259040371*l_ + 0.7827717662*m_ - 0.8086757660*s_
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from OKLAB to Linear sRGB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.oklabToLinear(color)
|
||||||
|
local l_ = color.x + 0.3963377774 * color.y + 0.2158037573 * color.z;
|
||||||
|
local m_ = color.x - 0.1055613458 * color.y - 0.0638541728 * color.z;
|
||||||
|
local s_ = color.x - 0.0894841775 * color.y - 1.2914855480 * color.z;
|
||||||
|
|
||||||
|
local l = l_*l_*l_;
|
||||||
|
local m = m_*m_*m_;
|
||||||
|
local s = s_*s_*s_;
|
||||||
|
|
||||||
|
return vec(
|
||||||
|
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
||||||
|
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
||||||
|
-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from LAB to LCh
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.labToLch(color)
|
||||||
|
return vec(
|
||||||
|
color.x,
|
||||||
|
math.sqrt(color.y * color.y + color.z * color.z),
|
||||||
|
math.deg(math.atan2(color.z, color.y))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from LCh to LAB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.lchToLab(color)
|
||||||
|
return vec(
|
||||||
|
color.x,
|
||||||
|
color.y * math.cos(math.rad(color.z)),
|
||||||
|
color.y * math.sin(math.rad(color.z))
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---Converts from sRGB to OKLCh
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.srgbToOklch(color)
|
||||||
|
return lib.labToLch(lib.linearToOklab(lib.srgbToLinear(color)))
|
||||||
|
end
|
||||||
|
|
||||||
|
---Converts from OKLCh to sRGB
|
||||||
|
---@param color Vector3
|
||||||
|
---@return Vector3
|
||||||
|
function lib.oklchToSrgb(color)
|
||||||
|
return lib.linearToSrgb(lib.oklabToLinear(lib.lchToLab(color)))
|
||||||
|
end
|
||||||
|
|
||||||
|
return lib
|
|
@ -1,22 +1,110 @@
|
||||||
local name = "reidlab!"
|
local g = require("scripts.libs.gradient")
|
||||||
local colors = { "#d87b5a", "#e0ab91" }
|
|
||||||
local offset = 0.05
|
|
||||||
local speed = 0.05
|
|
||||||
|
|
||||||
colors[#colors + 1] = colors[1]
|
---@class ColoredText
|
||||||
offset = offset / speed
|
---@field text string
|
||||||
|
---@field color string
|
||||||
|
|
||||||
events.TICK:register(function ()
|
---@param name string
|
||||||
local newName = "["
|
---@param gradient Gradient
|
||||||
|
---@param nameplates Nameplate | Nameplate[] where to use the animated name
|
||||||
for i = 1, #name, 1 do
|
---@param count number number of gradients to be present in the span
|
||||||
local counter = (((world.getTime() + offset * i) * speed) % (#colors - 1)) + 1
|
---@param phase_shift_rate number how fast to shift the gradient
|
||||||
local counterFloored = math.floor(counter)
|
---@returns fun():void # function to unregister
|
||||||
local color = math.lerp(vectors.hexToRGB(colors[counterFloored]), vectors.hexToRGB(colors[counterFloored + 1]), counter - counterFloored)
|
local function animate_gradient_name(name, gradient, nameplates, count, phase_shift_rate)
|
||||||
newName = newName .. '{"text":"' .. name:sub(i,i) .. '","color":"#' .. vectors.rgbToHex(color) .. '"},'
|
if type(nameplates) ~= "table" then
|
||||||
avatar:setColor(color)
|
nameplates = {nameplates}
|
||||||
end
|
end
|
||||||
|
|
||||||
newName = newName:sub(1, #newName - 1) .. "]"
|
local string_width = client.getTextWidth(name) / count;
|
||||||
nameplate.ALL:setText(newName)
|
local string_width_chars = {};
|
||||||
end)
|
for i = 1, #name do
|
||||||
|
string_width_chars[i] = client.getTextWidth(name:sub(i, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type ColoredText[]
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
local phase_shift = 0;
|
||||||
|
local function render()
|
||||||
|
if client.isPaused() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local acc = 0;
|
||||||
|
for i = 1, #name do
|
||||||
|
local offset = acc + string_width_chars[i];
|
||||||
|
local color = gradient:at((offset / string_width) + phase_shift);
|
||||||
|
acc = offset
|
||||||
|
|
||||||
|
result[i] = {
|
||||||
|
text = name:sub(i, i),
|
||||||
|
color = color:toHex(true, false)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
phase_shift = phase_shift + phase_shift_rate
|
||||||
|
|
||||||
|
local json = toJson(result);
|
||||||
|
for _, nameplate in pairs(nameplates) do
|
||||||
|
nameplate:setText(json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
events.render:register(render)
|
||||||
|
return function ()
|
||||||
|
events.render:remove(render)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param name string
|
||||||
|
---@param gradient Gradient
|
||||||
|
---@param nameplates Nameplate | Nameplate[] where to use the static name
|
||||||
|
---@param count number number of gradients to be present in the span
|
||||||
|
---@param phase_shift number shift of the gradient
|
||||||
|
local function static_gradient_name(name, gradient, nameplates, count, phase_shift)
|
||||||
|
if type(nameplates) ~= "table" then
|
||||||
|
nameplates = {nameplates}
|
||||||
|
end
|
||||||
|
|
||||||
|
local string_width = client.getTextWidth(name) / count;
|
||||||
|
local string_width_chars = {};
|
||||||
|
for i = 1, #name do
|
||||||
|
string_width_chars[i] = client.getTextWidth(name:sub(i, i))
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type ColoredText[]
|
||||||
|
local result = {}
|
||||||
|
|
||||||
|
local acc = 0;
|
||||||
|
for i = 1, #name do
|
||||||
|
local offset = acc + string_width_chars[i];
|
||||||
|
local color = gradient:at((offset / string_width) + phase_shift);
|
||||||
|
acc = offset
|
||||||
|
|
||||||
|
result[i] = {
|
||||||
|
text = name:sub(i, i),
|
||||||
|
color = color:toHex(true, false)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
local json = toJson(result);
|
||||||
|
for _, nameplate in pairs(nameplates) do
|
||||||
|
nameplate:setText(json)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local name = "reidlab!"
|
||||||
|
local gradient_count = 1
|
||||||
|
local gradient = g.SimpleGradientBuilder.new()
|
||||||
|
:add({ "#d87b5a", "#e0ab91" })
|
||||||
|
:reflect(false)
|
||||||
|
:build()
|
||||||
|
|
||||||
|
animate_gradient_name(name, gradient, {
|
||||||
|
nameplate.ENTITY,
|
||||||
|
nameplate.LIST
|
||||||
|
}, gradient_count, 0.005)
|
||||||
|
|
||||||
|
static_gradient_name(name, gradient, {
|
||||||
|
nameplate.CHAT
|
||||||
|
}, gradient_count, 0)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue