From 8e35729201e3e595fb53e938891d884236edcffe Mon Sep 17 00:00:00 2001 From: Strand8319 Date: Fri, 27 Mar 2026 22:40:35 +0500 Subject: [PATCH 1/2] =?UTF-8?q?=E2=9C=A8=20new=20quaternion=20gates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lua/autorun/wire_load.lua | 4 +- .../gmod_wire_expression2/core/quaternion.lua | 432 ++------- lua/entities/gmod_wire_fpga/shared.lua | 5 +- lua/entities/gmod_wire_value.lua | 11 +- lua/wire/client/node_editor/nodeeditor.lua | 46 +- lua/wire/fpga_gates/constants.lua | 10 + lua/wire/fpga_gates/io.lua | 20 + lua/wire/gates/quaternion.lua | 836 ++++++++++++++++++ lua/wire/server/wirelib.lua | 488 ++++++++++ lua/wire/stools/value.lua | 14 +- 10 files changed, 1493 insertions(+), 373 deletions(-) create mode 100644 lua/wire/gates/quaternion.lua diff --git a/lua/autorun/wire_load.lua b/lua/autorun/wire_load.lua index 404705a750..42b023701e 100644 --- a/lua/autorun/wire_load.lua +++ b/lua/autorun/wire_load.lua @@ -97,7 +97,6 @@ include("wire/sh_modelplug.lua") include("wire/wireshared.lua") include("wire/wirenet.lua") include("wire/wire_paths.lua") -include("wire/wiregates.lua") include("wire/fpgagates.lua") include("wire/cpugates.lua") include("wire/wiremonitors.lua") @@ -125,6 +124,9 @@ if SERVER then end end +-- shared include wiregates (moved it so gates will be able to use WireLib) +include("wire/wiregates.lua") + -- client includes if CLIENT then include("wire/client/cl_wirelib.lua") diff --git a/lua/entities/gmod_wire_expression2/core/quaternion.lua b/lua/entities/gmod_wire_expression2/core/quaternion.lua index 66646ee118..03765b3f9b 100644 --- a/lua/entities/gmod_wire_expression2/core/quaternion.lua +++ b/lua/entities/gmod_wire_expression2/core/quaternion.lua @@ -1,24 +1,9 @@ +local Q = WireLib.Quaternion + /******************************************************************************\ Quaternion support \******************************************************************************/ -// TODO: implement more! - --- faster access to some math library functions -local abs = math.abs -local Round = math.Round -local sqrt = math.sqrt -local exp = math.exp -local log = math.log -local sin = math.sin -local cos = math.cos -local sinh = math.sinh -local cosh = math.cosh -local acos = math.acos - -local deg2rad = math.pi/180 -local rad2deg = 180/math.pi - registerType("quaternion", "q", { 0, 0, 0, 0 }, nil, nil, @@ -27,234 +12,97 @@ registerType("quaternion", "q", { 0, 0, 0, 0 }, return !istable(v) or #v ~= 4 end ) - -local function format(value) - local r,i,j,k,dbginfo - - r = "" - i = "" - j = "" - k = "" - - if abs(value[1]) > 0.0005 then - r = Round(value[1]*1000)/1000 - end - dbginfo = r - if abs(value[2]) > 0.0005 then - i = tostring(Round(value[2]*1000)/1000) - if string.sub(i,1,1)~="-" and dbginfo ~= "" then i = "+"..i end - i = i .. "i" - end - dbginfo = dbginfo .. i - if abs(value[3]) > 0.0005 then - j = tostring(Round(value[3]*1000)/1000) - if string.sub(j,1,1)~="-" and dbginfo ~= "" then j = "+"..j end - j = j .. "j" - end - dbginfo = dbginfo .. j - if abs(value[4]) > 0.0005 then - k = tostring(Round(value[4]*1000)/1000) - if string.sub(k,1,1)~="-" and dbginfo ~= "" then k = "+"..k end - k = k .. "k" - end - dbginfo = dbginfo .. k - if dbginfo == "" then dbginfo = "0" end - return dbginfo -end - -WireLib.registerDebuggerFormat("QUATERNION", format) - -/****************************** Helper functions ******************************/ - -local function qmul(lhs, rhs) - local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] - local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] - return { - lhs1 * rhs1 - lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, - lhs1 * rhs2 + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, - lhs1 * rhs3 + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, - lhs1 * rhs4 + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 - } -end - -local function qexp(q) - local m = sqrt(q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) - local u - if m ~= 0 then - u = { q[2]*sin(m)/m, q[3]*sin(m)/m, q[4]*sin(m)/m } - else - u = { 0, 0, 0 } - end - local r = exp(q[1]) - return { r*cos(m), r*u[1], r*u[2], r*u[3] } -end - -local function qlog(q) - local l = sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) - if l == 0 then return { -1e+100, 0, 0, 0 } end - local u = { q[1]/l, q[2]/l, q[3]/l, q[4]/l } - local a = acos(u[1]) - local m = sqrt(u[2]*u[2] + u[3]*u[3] + u[4]*u[4]) - if abs(m) > 0 then - return { log(l), a*u[2]/m, a*u[3]/m, a*u[4]/m } - else - return { log(l), 0, 0, 0 } --when m is 0, u[2], u[3] and u[4] are 0 too - end -end - -local function qDot(q1, q2) - return q1[1]*q2[1] + q1[2]*q2[2] + q1[3]*q2[3] + q1[4]*q2[4] -end - -local function qGetNormalized(q) - local len = sqrt(q[1]^2 + q[2]^2 + q[3]^2 + q[4]^2) - return {q[1]/len, q[2]/len, q[3]/len, q[4]/len} -end - -local function qNormalize(q) - local len = sqrt(q[1]^2 + q[2]^2 + q[3]^2 + q[4]^2) - q[1] = q[1]/len - q[2] = q[2]/len - q[3] = q[3]/len - q[4] = q[4]/len -end - +WireLib.registerDebuggerFormat("QUATERNION", Q.ToString) /******************************************************************************/ __e2setcost(1) --- Creates a zero quaternion e2function quaternion quat() - return { 0, 0, 0, 0 } + return Q.Zero() end --- Creates a quaternion with real part equal to e2function quaternion quat(real) - return { real, 0, 0, 0 } + return Q.New(real, 0, 0, 0) end --- Creates a quaternion with real and "i" parts equal to e2function quaternion quat(complex c) - return { c[1], c[2], 0, 0 } + return Q.FromComplex(c) end --- Converts a vector to a quaternion (returns .x*i + .y*j + .z*k) e2function quaternion quat(vector imag) - return { 0, imag[1], imag[2], imag[3] } + return Q.FromVector(imag) end --- Returns +i+j+k e2function quaternion quat(real, i, j, k) - return { real, i, j, k } + return Q.New(real, i, j, k) end __e2setcost(6) --- Converts to a quaternion e2function quaternion quat(angle ang) - local p, y, r = ang[1], ang[2], ang[3] - p = p*deg2rad*0.5 - y = y*deg2rad*0.5 - r = r*deg2rad*0.5 - local qr = {cos(r), sin(r), 0, 0} - local qp = {cos(p), 0, sin(p), 0} - local qy = {cos(y), 0, 0, sin(y)} - return qmul(qy,qmul(qp,qr)) + return Q.Quat(ang) end __e2setcost(15) --- Creates a quaternion given forward () and up () vectors e2function quaternion quat(vector forward, vector up) - local x = Vector(forward[1], forward[2], forward[3]) - local z = Vector(up[1], up[2], up[3]) - local y = z:Cross(x):GetNormalized() --up x forward = left - - local ang = x:Angle() - if ang.p > 180 then ang.p = ang.p - 360 end - if ang.y > 180 then ang.y = ang.y - 360 end - - local yyaw = Vector(0,1,0) - yyaw:Rotate(Angle(0,ang.y,0)) - - local roll = acos(math.Clamp(y:Dot(yyaw), -1, 1))*rad2deg - - local dot = y.z - if dot < 0 then roll = -roll end - - local p, y, r = ang.p, ang.y, roll - p = p*deg2rad*0.5 - y = y*deg2rad*0.5 - r = r*deg2rad*0.5 - local qr = {cos(r), sin(r), 0, 0} - local qp = {cos(p), 0, sin(p), 0} - local qy = {cos(y), 0, 0, sin(y)} - return qmul(qy,qmul(qp,qr)) + return Q.QuatFromVectors(forward, up) end --- Converts angle of to a quaternion e2function quaternion quat(entity ent) - if(!IsValid(ent)) then - return { 0, 0, 0, 0 } - end - local ang = ent:GetAngles() - local p, y, r = ang.p, ang.y, ang.r - p = p*deg2rad*0.5 - y = y*deg2rad*0.5 - r = r*deg2rad*0.5 - local qr = {cos(r), sin(r), 0, 0} - local qp = {cos(p), 0, sin(p), 0} - local qy = {cos(y), 0, 0, sin(y)} - return qmul(qy,qmul(qp,qr)) + return Q.QuatFromEntity(ent) end __e2setcost(1) --- Returns quaternion i e2function quaternion qi() - return {0, 1, 0, 0} + return Q.New(0, 1, 0, 0) end --- Returns quaternion *i e2function quaternion qi(n) - return {0, n, 0, 0} + return Q.New(0, n, 0, 0) end --- Returns j e2function quaternion qj() - return {0, 0, 1, 0} + return Q.New(0, 0, 1, 0) end --- Returns *j e2function quaternion qj(n) - return {0, 0, n, 0} + return Q.New(0, 0, n, 0) end --- Returns k e2function quaternion qk() - return {0, 0, 0, 1} + return Q.New(0, 0, 0, 1) end --- Returns *k e2function quaternion qk(n) - return {0, 0, 0, n} + return Q.New(0, 0, 0, n) end /******************************************************************************/ -__e2setcost(2) - -/******************************************************************************/ -// TODO: define division as multiplication with (1/x), or is it not useful? - __e2setcost(4) e2function quaternion operator_neg(quaternion q) - return { -q[1], -q[2], -q[3], -q[4] } + return Q.Neg(q) end e2function quaternion operator+(quaternion lhs, quaternion rhs) - return { lhs[1] + rhs[1], lhs[2] + rhs[2], lhs[3] + rhs[3], lhs[4] + rhs[4] } + return Q.Add(lhs, rhs) end e2function quaternion operator+(number lhs, quaternion rhs) @@ -274,7 +122,7 @@ e2function quaternion operator+(quaternion lhs, complex rhs) end e2function quaternion operator-(quaternion lhs, quaternion rhs) - return { lhs[1] - rhs[1], lhs[2] - rhs[2], lhs[3] - rhs[3], lhs[4] - rhs[4] } + return Q.Sub(lhs, rhs) end e2function quaternion operator-(number lhs, quaternion rhs) @@ -294,11 +142,11 @@ e2function quaternion operator-(quaternion lhs, complex rhs) end e2function quaternion operator*(lhs, quaternion rhs) - return { lhs * rhs[1], lhs * rhs[2], lhs * rhs[3], lhs * rhs[4] } + return Q.Scale(rhs, lhs) end e2function quaternion operator*(quaternion lhs, rhs) - return { lhs[1] * rhs, lhs[2] * rhs, lhs[3] * rhs, lhs[4] * rhs } + return Q.Scale(lhs, rhs) end __e2setcost(6) @@ -328,14 +176,7 @@ end __e2setcost(9) e2function quaternion operator*(quaternion lhs, quaternion rhs) - local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] - local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] - return { - lhs1 * rhs1 - lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, - lhs1 * rhs2 + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, - lhs1 * rhs3 + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, - lhs1 * rhs4 + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 - } + return Q.Mul(lhs, rhs) end e2function quaternion operator*(quaternion lhs, vector rhs) @@ -361,74 +202,51 @@ e2function quaternion operator*(vector lhs, quaternion rhs) end e2function quaternion operator/(quaternion lhs, number rhs) - local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] - return { - lhs1/rhs, - lhs2/rhs, - lhs3/rhs, - lhs4/rhs - } + return Q.DivideByNumber(lhs, rhs) end e2function quaternion operator/(number lhs, quaternion rhs) - local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] - local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 - return { - ( lhs * rhs1)/l, - (-lhs * rhs2)/l, - (-lhs * rhs3)/l, - (-lhs * rhs4)/l - } + return Q.NumberDivide(lhs, rhs) end e2function quaternion operator/(quaternion lhs, complex rhs) local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] local rhs1, rhs2 = rhs[1], rhs[2] - local l = rhs1*rhs1 + rhs2*rhs2 + local l = rhs1 * rhs1 + rhs2 * rhs2 return { - ( lhs1 * rhs1 + lhs2 * rhs2)/l, - (-lhs1 * rhs2 + lhs2 * rhs1)/l, - ( lhs3 * rhs1 - lhs4 * rhs2)/l, - ( lhs4 * rhs1 + lhs3 * rhs2)/l + ( lhs1 * rhs1 + lhs2 * rhs2) / l, + (-lhs1 * rhs2 + lhs2 * rhs1) / l, + ( lhs3 * rhs1 - lhs4 * rhs2) / l, + ( lhs4 * rhs1 + lhs3 * rhs2) / l } end e2function quaternion operator/(complex lhs, quaternion rhs) local lhs1, lhs2 = lhs[1], lhs[2] local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] - local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 + local l = rhs1 * rhs1 + rhs2 * rhs2 + rhs3 * rhs3 + rhs4 * rhs4 return { - ( lhs1 * rhs1 + lhs2 * rhs2)/l, - (-lhs1 * rhs2 + lhs2 * rhs1)/l, - (-lhs1 * rhs3 + lhs2 * rhs4)/l, - (-lhs1 * rhs4 - lhs2 * rhs3)/l + ( lhs1 * rhs1 + lhs2 * rhs2) / l, + (-lhs1 * rhs2 + lhs2 * rhs1) / l, + (-lhs1 * rhs3 + lhs2 * rhs4) / l, + (-lhs1 * rhs4 - lhs2 * rhs3) / l } end __e2setcost(10) + e2function quaternion operator/(quaternion lhs, quaternion rhs) - local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] - local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] - local l = rhs1*rhs1 + rhs2*rhs2 + rhs3*rhs3 + rhs4*rhs4 - return { - ( lhs1 * rhs1 + lhs2 * rhs2 + lhs3 * rhs3 + lhs4 * rhs4)/l, - (-lhs1 * rhs2 + lhs2 * rhs1 - lhs3 * rhs4 + lhs4 * rhs3)/l, - (-lhs1 * rhs3 + lhs3 * rhs1 - lhs4 * rhs2 + lhs2 * rhs4)/l, - (-lhs1 * rhs4 + lhs4 * rhs1 - lhs2 * rhs3 + lhs3 * rhs2)/l - } + return Q.Divide(lhs, rhs) end __e2setcost(4) e2function quaternion operator^(number lhs, quaternion rhs) - if lhs == 0 then return { 0, 0, 0, 0 } end - local l = log(lhs) - return qexp({ l*rhs[1], l*rhs[2], l*rhs[3], l*rhs[4] }) + return Q.NumberPower(lhs, rhs) end e2function quaternion operator^(quaternion lhs, number rhs) - local l = qlog(lhs) - return qexp({ l[1]*rhs, l[2]*rhs, l[3]*rhs, l[4]*rhs }) + return Q.Power(lhs, rhs) end registerOperator("indexget", "qn", "n", function(state, this, index) @@ -456,19 +274,17 @@ __e2setcost(4) --- Returns absolute value of e2function number abs(quaternion q) - return sqrt(q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4]) + return Q.Abs(q) end --- Returns the conjugate of e2function quaternion conj(quaternion q) - return {q[1], -q[2], -q[3], -q[4]} + return Q.Conj(q) end --- Returns the inverse of e2function quaternion inv(quaternion q) - local l = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] - if l == 0 then return {0,0,0,0} end - return { q[1]/l, -q[2]/l, -q[3]/l, -q[4]/l } + return Q.Inv(q) end __e2setcost(1) @@ -499,75 +315,31 @@ __e2setcost(7) --- Raises Euler's constant e to the power e2function quaternion exp(quaternion q) - return qexp(q) + return Q.Exp(q) end --- Calculates natural logarithm of e2function quaternion log(quaternion q) - return qlog(q) + return Q.Log(q) end __e2setcost(2) ---- Changes quaternion so that the represented rotation is by an angle between 0 and 180 degrees (by coder0xff) +--- Changes quaternion so that the represented rotation is by an angle between 0 and 180 degrees e2function quaternion qMod(quaternion q) - if q[1]<0 then return {-q[1], -q[2], -q[3], -q[4]} else return {q[1], q[2], q[3], q[4]} end + return Q.Mod(q) end __e2setcost(13) --- Performs spherical linear interpolation between and . Returns for =0, for =1 ---- Derived from c++ source on https://en.wikipedia.org/wiki/Slerp e2function quaternion slerp(quaternion q0, quaternion q1, number t) - local dot = qDot(q0, q1) - - if dot < 0 then - q1 = {-q1[1], -q1[2], -q1[3], -q1[4]} - dot = -dot - end - - -- Really small theta, transcendental functions approximate to linear - if dot > 0.9995 then - local lerped = { - q0[1] + t*(q1[1] - q0[1]), - q0[2] + t*(q1[2] - q0[2]), - q0[3] + t*(q1[3] - q0[3]), - q0[4] + t*(q1[4] - q0[4]), - } - qNormalize(lerped) - return lerped - end - - local theta_0 = acos(dot) - local theta = theta_0*t - local sin_theta = sin(theta) - local sin_theta_0 = sin(theta_0) - - local s0 = cos(theta) - dot * sin_theta / sin_theta_0 - local s1 = sin_theta / sin_theta_0 - - local slerped = { - q0[1]*s0 + q1[1]*s1, - q0[2]*s0 + q1[2]*s1, - q0[3]*s0 + q1[3]*s1, - q0[4]*s0 + q1[4]*s1, - } - qNormalize(slerped) - return slerped + return Q.Slerp(q0, q1, t) end --- Performs normalized linear interpolation between and . Returns normalized for =0, normalized for =1 e2function quaternion nlerp(quaternion q0, quaternion q1, number t) - local t1 = 1 - t - local q2 - if qDot(q0, q1) < 0 then - q2 = { q0[1] * t1 - q1[1] * t, q0[2] * t1 - q1[2] * t, q0[3] * t1 - q1[3] * t, q0[4] * t1 - q1[4] * t } - else - q2 = { q0[1] * t1 + q1[1] * t, q0[2] * t1 + q1[2] * t, q0[3] * t1 + q1[3] * t, q0[4] * t1 + q1[4] * t } - end - - qNormalize(q2) - return q2 + return Q.Nlerp(q0, q1, t) end /******************************************************************************/ @@ -575,35 +347,17 @@ __e2setcost(7) --- Returns vector pointing forward for e2function vector quaternion:forward() - local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] - local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 - return Vector( - this1 * this1 + this2 * this2 - this3 * this3 - this4 * this4, - t3 * this2 + t4 * this1, - t4 * this2 - t3 * this1 - ) + return Q.Forward(this) end --- Returns vector pointing right for e2function vector quaternion:right() - local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] - local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 - return Vector( - t4 * this1 - t2 * this3, - this2 * this2 - this1 * this1 + this4 * this4 - this3 * this3, - - t2 * this1 - t3 * this4 - ) + return Q.Right(this) end --- Returns vector pointing up for e2function vector quaternion:up() - local this1, this2, this3, this4 = this[1], this[2], this[3], this[4] - local t2, t3, t4 = this2 * 2, this3 * 2, this4 * 2 - return Vector( - t3 * this1 + t2 * this4, - t3 * this4 - t2 * this1, - this1 * this1 - this2 * this2 - this3 * this3 + this4 * this4 - ) + return Q.Up(this) end /******************************************************************************/ @@ -611,50 +365,27 @@ __e2setcost(9) --- Returns quaternion for rotation about axis by angle e2function quaternion qRotation(vector axis, ang) - local ax = Vector(axis[1], axis[2], axis[3]) - ax:Normalize() - local ang2 = ang*deg2rad*0.5 - return { cos(ang2), ax.x*sin(ang2), ax.y*sin(ang2), ax.z*sin(ang2) } + return Q.Rotation(axis, ang) end ---- Construct a quaternion from the rotation vector . Vector direction is axis of rotation, magnitude is angle in degress (by coder0xff) +--- Construct a quaternion from the rotation vector . Vector direction is axis of rotation, magnitude is angle in degress e2function quaternion qRotation(vector rv1) - local angSquared = rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] - if angSquared == 0 then return { 1, 0, 0, 0 } end - local len = sqrt(angSquared) - local ang = (len + 180) % 360 - 180 - local ang2 = ang*deg2rad*0.5 - local sang2len = sin(ang2) / len - return { cos(ang2), rv1[1] * sang2len , rv1[2] * sang2len, rv1[3] * sang2len } + return Q.RotationFromVector(rv1) end ---- Returns the angle of rotation in degrees (by coder0xff) +--- Returns the angle of rotation in degrees e2function number rotationAngle(quaternion q) - local l2 = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] - if l2 == 0 then return 0 end - local l = sqrt(l2) - local ang = 2*acos(math.Clamp(q[1]/l, -1, 1))*rad2deg //this returns angle from 0 to 360 - if ang > 180 then ang = ang - 360 end //make it -180 - 180 - return ang + return Q.RotationAngle(q) end ---- Returns the axis of rotation (by coder0xff) +--- Returns the axis of rotation e2function vector rotationAxis(quaternion q) - local m2 = q[2] * q[2] + q[3] * q[3] + q[4] * q[4] - if m2 == 0 then return Vector(0, 0, 1) end - local m = sqrt(m2) - return Vector(q[2] / m, q[3] / m, q[4] / m) + return Q.RotationAxis(q) end ---- Returns the rotation vector - rotation axis where magnitude is the angle of rotation in degress (by coder0xff) +--- Returns the rotation vector - rotation axis where magnitude is the angle of rotation in degress e2function vector rotationVector(quaternion q) - local l2 = q[1]*q[1] + q[2]*q[2] + q[3]*q[3] + q[4]*q[4] - local m2 = math.max( q[2]*q[2] + q[3]*q[3] + q[4]*q[4], 0 ) - if l2 == 0 or m2 == 0 then return Vector(0, 0, 0) end - local s = 2 * acos( math.Clamp( q[1] / sqrt(l2), -1, 1 ) ) * rad2deg - if s > 180 then s = s - 360 end - s = s / sqrt(m2) - return Vector( q[2] * s, q[3] * s, q[4] * s ) + return Q.RotationVector(q) end /******************************************************************************/ @@ -662,66 +393,37 @@ __e2setcost(3) --- Converts to a vector by dropping the real component e2function vector vec(quaternion q) - return Vector(q[2], q[3], q[4]) + return Q.ToVector(q) end __e2setcost(15) --- Converts to a transformation matrix e2function matrix matrix(quaternion q) - local w,x,y,z = q[1],q[2],q[3],q[4] - return { - 1 - 2*y*y - 2*z*z , 2*x*y - 2*z*w , 2*x*z + 2*y*w, - 2*x*y + 2*z*w , 1 - 2*x*x - 2*z*z , 2*y*z - 2*x*w, - 2*x*z - 2*y*w , 2*y*z + 2*x*w , 1 - 2*x*x - 2*y*y - } + return Q.ToMatrix(q) end --- Returns angle represented by e2function angle quaternion:toAngle() - local l = sqrt(this[1]*this[1]+this[2]*this[2]+this[3]*this[3]+this[4]*this[4]) - if l == 0 then return Angle(0, 0, 0) end - local q1, q2, q3, q4 = this[1]/l, this[2]/l, this[3]/l, this[4]/l - - local x = Vector(q1*q1 + q2*q2 - q3*q3 - q4*q4, - 2*q3*q2 + 2*q4*q1, - 2*q4*q2 - 2*q3*q1) - - local y = Vector(2*q2*q3 - 2*q4*q1, - q1*q1 - q2*q2 + q3*q3 - q4*q4, - 2*q2*q1 + 2*q3*q4) - - local ang = x:Angle() - if ang.p > 180 then ang.p = ang.p - 360 end - if ang.y > 180 then ang.y = ang.y - 360 end - - local yyaw = Vector(0,1,0) - yyaw:Rotate(Angle(0,ang.y,0)) - - local roll = acos(math.Clamp(y:Dot(yyaw), -1, 1))*rad2deg - - local dot = q2*q1 + q3*q4 - if dot < 0 then roll = -roll end - - return Angle(ang.p, ang.y, roll) + return Q.ToAngle(this) end --- Returns new normalized quaternion e2function quaternion quaternion:normalized() - return qGetNormalized(this) + return Q.Normalized(this) end --- Returns dot product of two quaternion e2function number quaternion:dot(quaternion q1) - return qDot(this, q1) + return Q.Dot(this, q1) end /******************************************************************************/ --- Formats as a string. e2function string toString(quaternion q) - return format(q) + return Q.ToString(q) end e2function string quaternion:toString() - return format(this) + return Q.ToString(this) end diff --git a/lua/entities/gmod_wire_fpga/shared.lua b/lua/entities/gmod_wire_fpga/shared.lua index e382b58f4a..efa7a15d8e 100644 --- a/lua/entities/gmod_wire_fpga/shared.lua +++ b/lua/entities/gmod_wire_fpga/shared.lua @@ -26,7 +26,8 @@ FPGADefaultValueForType = { ARRAY = {}, ENTITY = NULL, RANGER = nil, - WIRELINK = nil + WIRELINK = nil, + QUATERNION = {1, 0, 0, 0}, } FPGATypeEnum = { @@ -40,6 +41,7 @@ FPGATypeEnum = { ENTITY = 8, RANGER = 9, WIRELINK = 10, + QUATERNION = 11, } FPGATypeEnumLookup = { @@ -53,6 +55,7 @@ FPGATypeEnumLookup = { "ENTITY", "RANGER", "WIRELINK", + "QUATERNION", } FPGANodeSize = 5 diff --git a/lua/entities/gmod_wire_value.lua b/lua/entities/gmod_wire_value.lua index e28e703ecc..77c448ce2d 100644 --- a/lua/entities/gmod_wire_value.lua +++ b/lua/entities/gmod_wire_value.lua @@ -19,6 +19,7 @@ local types_lookup = { VECTOR = Vector(0,0,0), VECTOR2 = {0,0}, VECTOR4 = {0,0,0,0}, + QUATERNION = {1,0,0,0}, STRING = "", } @@ -45,7 +46,7 @@ function parsers.NORMAL( val ) end function parsers.VECTOR ( val ) local x,y,z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) - if tonumber(x) and tonumber(y) and tonumber(y) then + if tonumber(x) and tonumber(y) and tonumber(z) then return Vector(tonumber(x),tonumber(y),tonumber(z)) end end @@ -55,7 +56,7 @@ function parsers.VECTOR2( val ) end function parsers.VECTOR4( val ) local x, y, z, w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) - if tonumber(x) and tonumber(y) and tonumber(y) and tonumber(w) then + if tonumber(x) and tonumber(y) and tonumber(z) and tonumber(w) then return {tonumber(x),tonumber(y),tonumber(z),tonumber(w)} end end @@ -65,6 +66,12 @@ function parsers.ANGLE( val ) return Angle(tonumber(p),tonumber(y),tonumber(r)) end end +function parsers.QUATERNION( val ) + local w, x, y, z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) + if tonumber(w) and tonumber(x) and tonumber(y) and tonumber(z) then + return { tonumber(w), tonumber(x), tonumber(y), tonumber(z) } + end +end parsers.STRING = WireLib.ParseEscapes function ENT:ParseValue( value, tp ) diff --git a/lua/wire/client/node_editor/nodeeditor.lua b/lua/wire/client/node_editor/nodeeditor.lua index 8b12f70c06..dde7fbc225 100644 --- a/lua/wire/client/node_editor/nodeeditor.lua +++ b/lua/wire/client/node_editor/nodeeditor.lua @@ -8,9 +8,10 @@ FPGATypeColor = { ANGLE = Color(100, 200, 100, 255), --Light green STRING = Color(250, 160, 90, 255), --Orange ARRAY = Color(20, 110, 20, 255), --Dark green - ENTITY = Color(255, 100, 100, 255), --Dark red + ENTITY = Color(255, 100, 100, 255), --Red RANGER = Color(130, 100, 60, 255), --Brown WIRELINK = Color(200, 80, 200, 255), --Deep purple + QUATERNION = Color(161, 35, 18), --Tomato red } --GATE HELPERS @@ -1319,7 +1320,25 @@ function Editor:PaintGate(nodeId, node, gate) surface.DrawText(node.ioName) -- Constant elseif node.value then - local s = util.TypeToString(node.value) + local s + + if getOutputType(gate, 1) == "QUATERNION" then + if node.valueAsString and node.valueAsString ~= "" then + s = node.valueAsString + elseif istable(node.value) then + s = string.format("%g, %g, %g, %g", + tonumber(node.value[1]) or 1, + tonumber(node.value[2]) or 0, + tonumber(node.value[3]) or 0, + tonumber(node.value[4]) or 0 + ) + else + s = "1, 0, 0, 0" + end + else + s = util.TypeToString(node.value) + end + local tx, ty = surface.GetTextSize(s) surface.SetTextPos(x - tx / 2, y - ty / 2 + size / 1.2) surface.DrawText(s) @@ -2661,6 +2680,11 @@ local function validateVector(string) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil, x, y, z end +local function validateQuaternion(string) + local w, x, y, z = string.match(string, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$") + return tonumber(w) ~= nil and tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil, w, x, y, z +end + function Editor:OpenConstantSetWindow(node, x, y, type) if not self.ConstantSetWindow then self:CreateConstantSetWindow() end self.ConstantSetNormal:SetVisible(false) @@ -2733,6 +2757,24 @@ function Editor:OpenConstantSetWindow(node, x, y, type) if valid then pnl:SetTextColor(color_black) else pnl:SetTextColor(invalidColor) end end + elseif type == "QUATERNION" then + self.ConstantSetString:SetVisible(true) + self.ConstantSetString:SetText(node.valueAsString or (node.value[1] .. ", " .. node.value[2] .. ", " .. node.value[3] .. ", " .. node.value[4])) + self.ConstantSetString:RequestFocus() + self.ConstantSetString.OnEnter = function(pnl) + local valid, w, x, y, z = validateQuaternion(pnl:GetValue()) + if valid then + node.value = { tonumber(w), tonumber(x), tonumber(y), tonumber(z) } + node.valueAsString = pnl:GetValue() + pnl:SetVisible(false) + pnl:GetParent():Close() + end + end + self.ConstantSetString.OnChange = function(pnl) + local valid = validateQuaternion(pnl:GetValue()) + if valid then pnl:SetTextColor(color_black) + else pnl:SetTextColor(invalidColor) end + end end local inputField = self.ConstantSetString diff --git a/lua/wire/fpga_gates/constants.lua b/lua/wire/fpga_gates/constants.lua index 10688c4a9d..b88d74f4f0 100644 --- a/lua/wire/fpga_gates/constants.lua +++ b/lua/wire/fpga_gates/constants.lua @@ -20,6 +20,16 @@ FPGAGateActions["vector-constant"] = { isConstant = true } +i = i + 1 +FPGAGateActions["quaternion-constant"] = { + order = i, + name = "Constant Quaternion", + inputs = {}, + outputs = {"Out"}, + outputtypes = {"QUATERNION"}, + isConstant = true +} + i = i + 1 FPGAGateActions["angle-constant"] = { order = i, diff --git a/lua/wire/fpga_gates/io.lua b/lua/wire/fpga_gates/io.lua index fd9c17f22f..3feb6230a6 100644 --- a/lua/wire/fpga_gates/io.lua +++ b/lua/wire/fpga_gates/io.lua @@ -56,6 +56,26 @@ FPGAGateActions["vector-output"] = { isOutput = true } +i = i + 1 +FPGAGateActions["quaternion-input"] = { + order = i, + name = "Quaternion Input", + inputs = {}, + outputs = {"Out"}, + outputtypes = {"QUATERNION"}, + isInput = true +} + +i = i + 1 +FPGAGateActions["quaternion-output"] = { + order = i, + name = "Quaternion Output", + inputs = {"A"}, + inputtypes = {"QUATERNION"}, + outputs = {}, + isOutput = true +} + -- FPGAGateActions["vector4-input"] = { -- name = "4D Vector Input", -- inputs = {}, diff --git a/lua/wire/gates/quaternion.lua b/lua/wire/gates/quaternion.lua new file mode 100644 index 0000000000..00b095a2c5 --- /dev/null +++ b/lua/wire/gates/quaternion.lua @@ -0,0 +1,836 @@ +--[[ + Quaternion gates +]] + +GateActions("Quaternion") + +local Round = math.Round +local unpack = unpack or table.unpack + +local QZERO = { 0, 0, 0, 0 } +local QIDENT = { 1, 0, 0, 0 } + +local function getQ() + return WireLib and WireLib.Quaternion +end + +local function isquat(q) + local validator = WireLib and WireLib.DT and WireLib.DT.QUATERNION and WireLib.DT.QUATERNION.Validator + if validator then return validator(q) end + + return istable(q) + and isnumber(q[1]) + and isnumber(q[2]) + and isnumber(q[3]) + and isnumber(q[4]) +end + +local function qget(q, default) + if isquat(q) then return q end + return default or QZERO +end + +local function qfmt(q) + q = qget(q, QZERO) + + local Q = getQ() + if Q and Q.ToString then + return Q.ToString(q) + end + + return string.format("(%s, %s, %s, %s)", tostring(q[1]), tostring(q[2]), tostring(q[3]), tostring(q[4])) +end + +local function nfmt(n) + if not isnumber(n) then return "0" end + return tostring(Round(n * 1000) / 1000) +end + +local function qnormalize_safe(q, fallback) + q = qget(q, fallback or QIDENT) + + local Q = getQ() + if not (Q and Q.Normalized) then + return q + end + + local normalized = Q.Normalized(q) + if isquat(normalized) and (normalized[1] ~= 0 or normalized[2] ~= 0 or normalized[3] ~= 0 or normalized[4] ~= 0) then + return normalized + end + + return fallback or QIDENT +end + +GateActions["quaternion_ident"] = { + name = "Identity", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + return qget(A, QIDENT) + end, + label = function(Out, A) + return qfmt(qget(A, QIDENT)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_add"] = { + name = "Addition", + inputs = { "A", "B", "C", "D", "E", "F", "G", "H" }, + inputtypes = { "QUATERNION", "QUATERNION", "QUATERNION", "QUATERNION", "QUATERNION", "QUATERNION", "QUATERNION", "QUATERNION" }, + compact_inputs = 2, + outputtypes = { "QUATERNION" }, + output = function(gate, ...) + local Q = getQ() + if not Q then return QZERO end + + local sum = Q.Zero() + for _, q in ipairs({ ... }) do + if isquat(q) then + sum = Q.Add(sum, q) + end + end + return sum + end, + label = function(Out, ...) + local tip = "" + for _, q in ipairs({ ... }) do + if isquat(q) then + tip = tip .. " + " .. qfmt(q) + end + end + if tip == "" then tip = "0" else tip = string.sub(tip, 4) end + return tip .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_sub"] = { + name = "Subtraction", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + if not Q then return QZERO end + return Q.Sub(qget(A), qget(B)) + end, + label = function(Out, A, B) + return qfmt(qget(A)) .. " - " .. qfmt(qget(B)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_neg"] = { + name = "Negate", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then + A = qget(A) + return { -(A[1] or 0), -(A[2] or 0), -(A[3] or 0), -(A[4] or 0) } + end + return Q.Neg(qget(A)) + end, + label = function(Out, A) + return "-" .. qfmt(qget(A)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_mul"] = { + name = "Multiplication", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + if not Q then return QIDENT end + return Q.Mul(qget(A, QIDENT), qget(B, QIDENT)) + end, + label = function(Out, A, B) + return qfmt(qget(A, QIDENT)) .. " * " .. qfmt(qget(B, QIDENT)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_scale"] = { + name = "Scalar Multiplication", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + if not Q then return QZERO end + return Q.Scale(qget(A), B or 0) + end, + label = function(Out, A, B) + return qfmt(qget(A)) .. " * " .. nfmt(B or 0) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_divide"] = { + name = "Division", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + if not Q then return QZERO end + return Q.Divide(qget(A), qget(B)) + end, + label = function(Out, A, B) + return qfmt(qget(A)) .. " / " .. qfmt(qget(B)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_dividenum"] = { + name = "Scalar Division", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + if not (Q and B and B ~= 0) then return QZERO end + return Q.DivideByNumber(qget(A), B) + end, + label = function(Out, A, B) + return qfmt(qget(A)) .. " / " .. nfmt(B or 0) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_power"] = { + name = "Power", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + A = qget(A) + B = B or 0 + + if not (Q and isnumber(B)) then return QZERO end + if Q.Abs(A) == 0 then return QZERO end + + return Q.Power(A, B) + end, + label = function(Out, A, B) + return qfmt(qget(A)) .. " ^ " .. nfmt(B or 0) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_scalarpower"] = { + name = "Scalar Power", + inputs = { "A", "B" }, + inputtypes = { "NORMAL", "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B) + local Q = getQ() + A = A or 0 + B = qget(B) + + if not (Q and A ~= 0) then return QZERO end + return Q.NumberPower(A, B) + end, + label = function(Out, A, B) + return nfmt(A or 0) .. " ^ " .. qfmt(qget(B)) .. " = " .. qfmt(Out) + end +} + +GateActions["quaternion_abs"] = { + name = "Magnitude", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + local Q = getQ() + if not Q then return 0 end + return Q.Abs(qget(A)) + end, + label = function(Out, A) + return "|" .. qfmt(qget(A)) .. "| = " .. nfmt(Out) + end +} + +GateActions["quaternion_conj"] = { + name = "Conjugate", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then + A = qget(A) + return { A[1] or 0, -(A[2] or 0), -(A[3] or 0), -(A[4] or 0) } + end + return Q.Conj(qget(A)) + end, + label = function(Out, A) + return "conj(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_inv"] = { + name = "Inverse", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then return QZERO end + return Q.Inv(qget(A)) + end, + label = function(Out, A) + return "inv(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_dot"] = { + name = "Dot Product", + inputs = { "A", "B" }, + inputtypes = { "QUATERNION", "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A, B) + local Q = getQ() + if not Q then return 0 end + return Q.Dot(qget(A), qget(B)) + end, + label = function(Out, A, B) + return "dot(" .. qfmt(qget(A)) .. ", " .. qfmt(qget(B)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_exp"] = { + name = "Exp", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then return QZERO end + return Q.Exp(qget(A)) + end, + label = function(Out, A) + return "exp(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_log"] = { + name = "Log", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then return QZERO end + return Q.Log(qget(A)) + end, + label = function(Out, A) + return "log(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_qmod"] = { + name = "qMod", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + local Q = getQ() + if not Q then return QZERO end + return Q.Mod(qget(A)) + end, + label = function(Out, A) + return "qMod(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_normalized"] = { + name = "Normalized", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A) + return qnormalize_safe(qget(A), QIDENT) + end, + label = function(Out, A) + return "normalized(" .. qfmt(qget(A)) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_slerp"] = { + name = "Slerp", + inputs = { "A", "B", "T" }, + inputtypes = { "QUATERNION", "QUATERNION", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B, T) + local Q = getQ() + if not Q then return QIDENT end + A = qnormalize_safe(qget(A, QIDENT), QIDENT) + B = qnormalize_safe(qget(B, QIDENT), QIDENT) + return Q.Slerp(A, B, T or 0) + end, + label = function(Out, A, B, T) + return "slerp(" .. qfmt(qget(A, QIDENT)) .. ", " .. qfmt(qget(B, QIDENT)) .. ", " .. nfmt(T or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_nlerp"] = { + name = "Nlerp", + inputs = { "A", "B", "T" }, + inputtypes = { "QUATERNION", "QUATERNION", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, A, B, T) + local Q = getQ() + if not Q then return QIDENT end + A = qnormalize_safe(qget(A, QIDENT), QIDENT) + B = qnormalize_safe(qget(B, QIDENT), QIDENT) + return Q.Nlerp(A, B, T or 0) + end, + label = function(Out, A, B, T) + return "nlerp(" .. qfmt(qget(A, QIDENT)) .. ", " .. qfmt(qget(B, QIDENT)) .. ", " .. nfmt(T or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_forward"] = { + name = "Forward", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Vector(1, 0, 0) end + return Q.Forward(qnormalize_safe(qget(A, QIDENT), QIDENT)) + end, + label = function(Out, A) + return "forward(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_right"] = { + name = "Right", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Vector(0, -1, 0) end + return Q.Right(qnormalize_safe(qget(A, QIDENT), QIDENT)) + end, + label = function(Out, A) + return "right(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_up"] = { + name = "Up", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Vector(0, 0, 1) end + return Q.Up(qnormalize_safe(qget(A, QIDENT), QIDENT)) + end, + label = function(Out, A) + return "up(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_qrotation"] = { + name = "qRotation (Axis + Angle)", + inputs = { "Axis", "Angle" }, + inputtypes = { "VECTOR", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Axis, Angle) + local Q = getQ() + if not Q then return QIDENT end + if not isvector(Axis) then Axis = Vector(0, 0, 1) end + return Q.Rotation(Axis, Angle or 0) + end, + label = function(Out, Axis, Angle) + return "qRotation(" .. tostring(Axis) .. ", " .. nfmt(Angle or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_qrotationvec"] = { + name = "qRotation (Rotation Vector)", + inputs = { "RV" }, + inputtypes = { "VECTOR" }, + outputtypes = { "QUATERNION" }, + output = function(gate, RV) + local Q = getQ() + if not Q then return QIDENT end + if not isvector(RV) then RV = Vector(0, 0, 0) end + return Q.RotationFromVector(RV) + end, + label = function(Out, RV) + return "qRotation(" .. tostring(RV) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_rotationangle"] = { + name = "Rotation Angle", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + local Q = getQ() + if not Q then return 0 end + return Q.RotationAngle(qget(A, QIDENT)) + end, + label = function(Out, A) + return "rotationAngle(" .. qfmt(qget(A, QIDENT)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_rotationaxis"] = { + name = "Rotation Axis", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Vector(0, 0, 0) end + return Q.RotationAxis(qget(A, QIDENT)) + end, + label = function(Out, A) + return "rotationAxis(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_rotationvector"] = { + name = "Rotation Vector", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Vector(0, 0, 0) end + return Q.RotationVector(qget(A, QIDENT)) + end, + label = function(Out, A) + return "rotationVector(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_vec"] = { + name = "Vec", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "VECTOR" }, + output = function(gate, A) + local Q = getQ() + A = qget(A) + if not Q then return Vector(A[2], A[3], A[4]) end + return Q.ToVector(A) + end, + label = function(Out, A) + return "vec(" .. qfmt(qget(A)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_toangle"] = { + name = "To Angle", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "ANGLE" }, + output = function(gate, A) + local Q = getQ() + if not Q then return Angle(0, 0, 0) end + return Q.ToAngle(qget(A, QIDENT)) + end, + label = function(Out, A) + return "toAngle(" .. qfmt(qget(A, QIDENT)) .. ") = " .. tostring(Out) + end +} + +GateActions["quaternion_tostring"] = { + name = "To String", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "STRING" }, + output = function(gate, A) + return qfmt(qget(A)) + end, + label = function(Out, A) + return "toString(" .. qfmt(qget(A)) .. ") = " .. Out + end +} + +GateActions["quaternion_matrix"] = { + name = "Matrix", + description = "Outputs the 3x3 rotation matrix as nine NORMAL outputs.", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputs = { "M11", "M12", "M13", "M21", "M22", "M23", "M31", "M32", "M33" }, + outputtypes = { "NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL", "NORMAL" }, + output = function(gate, A) + local Q = getQ() + if not Q then return 1,0,0,0,1,0,0,0,1 end + return unpack(Q.ToMatrix(qget(A, QIDENT))) + end, + label = function(Out, A) + return "matrix(" .. qfmt(qget(A, QIDENT)) .. ")" + end +} + +GateActions["quaternion_quat0"] = { + name = "quat()", + inputs = {}, + inputtypes = {}, + outputtypes = { "QUATERNION" }, + output = function(gate) + local Q = getQ() + return Q and Q.Zero() or QZERO + end, + label = function(Out) + return "quat() = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_real"] = { + name = "quat(real)", + inputs = { "Real" }, + inputtypes = { "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Real) + local Q = getQ() + return Q and Q.New(Real or 0, 0, 0, 0) or { Real or 0, 0, 0, 0 } + end, + label = function(Out, Real) + return "quat(" .. nfmt(Real or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_vec"] = { + name = "quat(vector)", + inputs = { "Imag" }, + inputtypes = { "VECTOR" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Imag) + local Q = getQ() + if not isvector(Imag) then Imag = Vector(0, 0, 0) end + if not Q then return { 0, Imag.x, Imag.y, Imag.z } end + return Q.FromVector(Imag) + end, + label = function(Out, Imag) + return "quat(" .. tostring(Imag) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_compose"] = { + name = "quat(real, i, j, k)", + inputs = { "Real", "I", "J", "K" }, + inputtypes = { "NORMAL", "NORMAL", "NORMAL", "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Real, I, J, K) + local Q = getQ() + if not Q then return { Real or 0, I or 0, J or 0, K or 0 } end + return Q.New(Real or 0, I or 0, J or 0, K or 0) + end, + label = function(Out, Real, I, J, K) + return "quat(" .. nfmt(Real or 0) .. ", " .. nfmt(I or 0) .. ", " .. nfmt(J or 0) .. ", " .. nfmt(K or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_angle"] = { + name = "quat(angle)", + inputs = { "Angle" }, + inputtypes = { "ANGLE" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Ang) + local Q = getQ() + if not Q then return QIDENT end + if not isangle(Ang) then Ang = Angle(0, 0, 0) end + return Q.Quat(Ang) + end, + label = function(Out, Ang) + return "quat(" .. tostring(Ang) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_vectors"] = { + name = "quat(forward, up)", + inputs = { "Forward", "Up" }, + inputtypes = { "VECTOR", "VECTOR" }, + outputtypes = { "QUATERNION" }, + output = function(gate, Forward, Up) + local Q = getQ() + if not Q then return QIDENT end + if not isvector(Forward) then Forward = Vector(1, 0, 0) end + if not isvector(Up) then Up = Vector(0, 0, 1) end + return Q.QuatFromVectors(Forward, Up) + end, + label = function(Out, Forward, Up) + return "quat(" .. tostring(Forward) .. ", " .. tostring(Up) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_quat_entity"] = { + name = "quat(entity)", + inputs = { "Entity" }, + inputtypes = { "ENTITY" }, + outputtypes = { "QUATERNION" }, + timed = true, + output = function(gate, Ent) + local Q = getQ() + return Q and Q.QuatFromEntity(Ent) or QIDENT + end, + label = function(Out, Ent) + return "quat(" .. tostring(Ent) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_qi"] = { + name = "qi()", + inputs = {}, + inputtypes = {}, + outputtypes = { "QUATERNION" }, + output = function(gate) + local Q = getQ() + return Q and Q.New(0, 1, 0, 0) or { 0, 1, 0, 0 } + end, + label = function(Out) + return "qi() = " .. qfmt(Out) + end +} + +GateActions["quaternion_qi_num"] = { + name = "qi(n)", + inputs = { "N" }, + inputtypes = { "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, N) + local Q = getQ() + return Q and Q.New(0, N or 0, 0, 0) or { 0, N or 0, 0, 0 } + end, + label = function(Out, N) + return "qi(" .. nfmt(N or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_qj"] = { + name = "qj()", + inputs = {}, + inputtypes = {}, + outputtypes = { "QUATERNION" }, + output = function(gate) + local Q = getQ() + return Q and Q.New(0, 0, 1, 0) or { 0, 0, 1, 0 } + end, + label = function(Out) + return "qj() = " .. qfmt(Out) + end +} + +GateActions["quaternion_qj_num"] = { + name = "qj(n)", + inputs = { "N" }, + inputtypes = { "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, N) + local Q = getQ() + return Q and Q.New(0, 0, N or 0, 0) or { 0, 0, N or 0, 0 } + end, + label = function(Out, N) + return "qj(" .. nfmt(N or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_qk"] = { + name = "qk()", + inputs = {}, + inputtypes = {}, + outputtypes = { "QUATERNION" }, + output = function(gate) + local Q = getQ() + return Q and Q.New(0, 0, 0, 1) or { 0, 0, 0, 1 } + end, + label = function(Out) + return "qk() = " .. qfmt(Out) + end +} + +GateActions["quaternion_qk_num"] = { + name = "qk(n)", + inputs = { "N" }, + inputtypes = { "NORMAL" }, + outputtypes = { "QUATERNION" }, + output = function(gate, N) + local Q = getQ() + return Q and Q.New(0, 0, 0, N or 0) or { 0, 0, 0, N or 0 } + end, + label = function(Out, N) + return "qk(" .. nfmt(N or 0) .. ") = " .. qfmt(Out) + end +} + +GateActions["quaternion_real"] = { + name = "Real", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + return qget(A)[1] + end, + label = function(Out, A) + return "real(" .. qfmt(qget(A)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_i"] = { + name = "I", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + return qget(A)[2] + end, + label = function(Out, A) + return "i(" .. qfmt(qget(A)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_j"] = { + name = "J", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + return qget(A)[3] + end, + label = function(Out, A) + return "j(" .. qfmt(qget(A)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_k"] = { + name = "K", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputtypes = { "NORMAL" }, + output = function(gate, A) + return qget(A)[4] + end, + label = function(Out, A) + return "k(" .. qfmt(qget(A)) .. ") = " .. nfmt(Out) + end +} + +GateActions["quaternion_convfrom"] = { + name = "Decompose", + description = "Splits a quaternion into four numbers.", + inputs = { "A" }, + inputtypes = { "QUATERNION" }, + outputs = { "Real", "I", "J", "K" }, + outputtypes = { "NORMAL", "NORMAL", "NORMAL", "NORMAL" }, + output = function(gate, A) + A = qget(A) + return A[1], A[2], A[3], A[4] + end, + label = function(Out, A) + return qfmt(qget(A)) .. " -> R:" .. nfmt(Out.Real) .. " I:" .. nfmt(Out.I) .. " J:" .. nfmt(Out.J) .. " K:" .. nfmt(Out.K) + end +} + +GateActions() diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index 0e7575fd90..49c47505e7 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -202,8 +202,496 @@ WireLib.DT = { Validator = istable, BiDir = true }, + QUATERNION = { + Zero = function() + return {0, 0, 0, 0} -- identity quaternion + end, + Validator = function(q) + return istable(q) + and isnumber(q[1]) + and isnumber(q[2]) + and isnumber(q[3]) + and isnumber(q[4]) + end + }, } +WireLib.Quaternion = {} + +do + local Q = WireLib.Quaternion + + local abs = math.abs + local Round = math.Round + local sqrt = math.sqrt + local exp = math.exp + local log = math.log + local sin = math.sin + local cos = math.cos + local acos = math.acos + + local deg2rad = math.pi / 180 + local rad2deg = 180 / math.pi + + local function qnew(r, i, j, k) + return { r or 0, i or 0, j or 0, k or 0 } + end + + local function format(value) + local r = "" + local i = "" + local j = "" + local k = "" + local dbginfo + + if abs(value[1]) > 0.0005 then + r = Round(value[1] * 1000) / 1000 + end + dbginfo = r + + if abs(value[2]) > 0.0005 then + i = tostring(Round(value[2] * 1000) / 1000) + if string.sub(i, 1, 1) ~= "-" and dbginfo ~= "" then i = "+" .. i end + i = i .. "i" + end + dbginfo = dbginfo .. i + + if abs(value[3]) > 0.0005 then + j = tostring(Round(value[3] * 1000) / 1000) + if string.sub(j, 1, 1) ~= "-" and dbginfo ~= "" then j = "+" .. j end + j = j .. "j" + end + dbginfo = dbginfo .. j + + if abs(value[4]) > 0.0005 then + k = tostring(Round(value[4] * 1000) / 1000) + if string.sub(k, 1, 1) ~= "-" and dbginfo ~= "" then k = "+" .. k end + k = k .. "k" + end + dbginfo = dbginfo .. k + + if dbginfo == "" then dbginfo = "0" end + return dbginfo + end + + local function qmul(lhs, rhs) + local lhs1, lhs2, lhs3, lhs4 = lhs[1], lhs[2], lhs[3], lhs[4] + local rhs1, rhs2, rhs3, rhs4 = rhs[1], rhs[2], rhs[3], rhs[4] + return { + lhs1 * rhs1 - lhs2 * rhs2 - lhs3 * rhs3 - lhs4 * rhs4, + lhs1 * rhs2 + lhs2 * rhs1 + lhs3 * rhs4 - lhs4 * rhs3, + lhs1 * rhs3 + lhs3 * rhs1 + lhs4 * rhs2 - lhs2 * rhs4, + lhs1 * rhs4 + lhs4 * rhs1 + lhs2 * rhs3 - lhs3 * rhs2 + } + end + + local function qexp(q) + local m = sqrt(q[2] * q[2] + q[3] * q[3] + q[4] * q[4]) + local u + if m ~= 0 then + u = { q[2] * sin(m) / m, q[3] * sin(m) / m, q[4] * sin(m) / m } + else + u = { 0, 0, 0 } + end + local r = exp(q[1]) + return { r * cos(m), r * u[1], r * u[2], r * u[3] } + end + + local function qlog(q) + local l = sqrt(q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4]) + if l == 0 then return { -1e+100, 0, 0, 0 } end + local u = { q[1] / l, q[2] / l, q[3] / l, q[4] / l } + local a = acos(u[1]) + local m = sqrt(u[2] * u[2] + u[3] * u[3] + u[4] * u[4]) + if abs(m) > 0 then + return { log(l), a * u[2] / m, a * u[3] / m, a * u[4] / m } + else + return { log(l), 0, 0, 0 } + end + end + + local function qdot(q1, q2) + return q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3] + q1[4] * q2[4] + end + + local function qgetnormalized(q) + local len = sqrt(q[1] ^ 2 + q[2] ^ 2 + q[3] ^ 2 + q[4] ^ 2) + if len == 0 then return qnew(0, 0, 0, 0) end + return { q[1] / len, q[2] / len, q[3] / len, q[4] / len } + end + + local function qnormalize(q) + local len = sqrt(q[1] ^ 2 + q[2] ^ 2 + q[3] ^ 2 + q[4] ^ 2) + if len == 0 then + q[1], q[2], q[3], q[4] = 0, 0, 0, 0 + return q + end + q[1] = q[1] / len + q[2] = q[2] / len + q[3] = q[3] / len + q[4] = q[4] / len + return q + end + + function Q.New(r, i, j, k) + return qnew(r, i, j, k) + end + + function Q.Zero() + return qnew(0, 0, 0, 0) + end + + function Q.Identity() + return qnew(1, 0, 0, 0) + end + + function Q.FromComplex(c) + return { c[1], c[2], 0, 0 } + end + + function Q.FromVector(v) + return { 0, v[1], v[2], v[3] } + end + + function Q.ToVector(q) + return Vector(q[2], q[3], q[4]) + end + + function Q.Add(a, b) + return { a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4] } + end + + function Q.Sub(a, b) + return { a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4] } + end + + function Q.Neg(q) + return { -q[1], -q[2], -q[3], -q[4] } + end + + function Q.Scale(q, n) + return { q[1] * n, q[2] * n, q[3] * n, q[4] * n } + end + + function Q.Mul(a, b) + return qmul(a, b) + end + + function Q.DivideByNumber(q, n) + return { q[1] / n, q[2] / n, q[3] / n, q[4] / n } + end + + function Q.NumberDivide(n, q) + local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] + local l = q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4 + return { + (n * q1) / l, + (-n * q2) / l, + (-n * q3) / l, + (-n * q4) / l + } + end + + function Q.Divide(a, b) + local a1, a2, a3, a4 = a[1], a[2], a[3], a[4] + local b1, b2, b3, b4 = b[1], b[2], b[3], b[4] + local l = b1 * b1 + b2 * b2 + b3 * b3 + b4 * b4 + return { + (a1 * b1 + a2 * b2 + a3 * b3 + a4 * b4) / l, + (-a1 * b2 + a2 * b1 - a3 * b4 + a4 * b3) / l, + (-a1 * b3 + a3 * b1 - a4 * b2 + a2 * b4) / l, + (-a1 * b4 + a4 * b1 - a2 * b3 + a3 * b2) / l + } + end + + function Q.NumberPower(base, exponent) + if base == 0 then return qnew(0, 0, 0, 0) end + local l = log(base) + return qexp({ l * exponent[1], l * exponent[2], l * exponent[3], l * exponent[4] }) + end + + function Q.Power(base, exponent) + local l = qlog(base) + return qexp({ l[1] * exponent, l[2] * exponent, l[3] * exponent, l[4] * exponent }) + end + + function Q.Dot(q1, q2) + return qdot(q1, q2) + end + + function Q.GetNormalized(q) + return qgetnormalized(q) + end + + function Q.Normalize(q) + return qnormalize(q) + end + + --- Converts angle table {p, y, r} to a quaternion + function Q.Quat(ang) + local p, y, r = ang[1], ang[2], ang[3] + p = p * deg2rad * 0.5 + y = y * deg2rad * 0.5 + r = r * deg2rad * 0.5 + local qr = { cos(r), sin(r), 0, 0 } + local qp = { cos(p), 0, sin(p), 0 } + local qy = { cos(y), 0, 0, sin(y) } + return qmul(qy, qmul(qp, qr)) + end + + function Q.QuatFromVectors(forward, up) + local x = Vector(forward[1], forward[2], forward[3]) + local z = Vector(up[1], up[2], up[3]) + local y = z:Cross(x):GetNormalized() + + local ang = x:Angle() + if ang.p > 180 then ang.p = ang.p - 360 end + if ang.y > 180 then ang.y = ang.y - 360 end + + local yyaw = Vector(0, 1, 0) + yyaw:Rotate(Angle(0, ang.y, 0)) + + local roll = acos(math_clamp(y:Dot(yyaw), -1, 1)) * rad2deg + + local dot = y.z + if dot < 0 then roll = -roll end + + local p, yy, r = ang.p, ang.y, roll + p = p * deg2rad * 0.5 + yy = yy * deg2rad * 0.5 + r = r * deg2rad * 0.5 + local qr = { cos(r), sin(r), 0, 0 } + local qp = { cos(p), 0, sin(p), 0 } + local qy = { cos(yy), 0, 0, sin(yy) } + return qmul(qy, qmul(qp, qr)) + end + + --- Converts angle of an entity to a quaternion + function Q.QuatFromEntity(ent) + if not IsValid(ent) then + return qnew(0, 0, 0, 0) + end + local ang = ent:GetAngles() + local p, y, r = ang.p, ang.y, ang.r + p = p * deg2rad * 0.5 + y = y * deg2rad * 0.5 + r = r * deg2rad * 0.5 + local qr = { cos(r), sin(r), 0, 0 } + local qp = { cos(p), 0, sin(p), 0 } + local qy = { cos(y), 0, 0, sin(y) } + return qmul(qy, qmul(qp, qr)) + end + + --- Returns absolute value (magnitude) of quaternion + function Q.Abs(q) + return sqrt(q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4]) + end + + --- Returns the conjugate of a quaternion + function Q.Conj(q) + return { q[1], -q[2], -q[3], -q[4] } + end + + --- Returns the inverse of a quaternion + function Q.Inv(q) + local l = q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4] + if l == 0 then return qnew(0, 0, 0, 0) end + return { q[1] / l, -q[2] / l, -q[3] / l, -q[4] / l } + end + + --- Raises Euler's constant e to the power of quaternion q + function Q.Exp(q) + return qexp(q) + end + + --- Calculates natural logarithm of quaternion q + function Q.Log(q) + return qlog(q) + end + + --- Changes quaternion so that the represented rotation is by an angle between 0 and 180 degrees + function Q.Mod(q) + if q[1] < 0 then + return { -q[1], -q[2], -q[3], -q[4] } + end + return { q[1], q[2], q[3], q[4] } + end + + --- Performs spherical linear interpolation between q0 and q1 + function Q.Slerp(q0, q1, t) + local dot = qdot(q0, q1) + + if dot < 0 then + q1 = { -q1[1], -q1[2], -q1[3], -q1[4] } + dot = -dot + end + + if dot > 0.9995 then + local lerped = { + q0[1] + t * (q1[1] - q0[1]), + q0[2] + t * (q1[2] - q0[2]), + q0[3] + t * (q1[3] - q0[3]), + q0[4] + t * (q1[4] - q0[4]), + } + qnormalize(lerped) + return lerped + end + + local theta_0 = acos(dot) + local theta = theta_0 * t + local sin_theta = sin(theta) + local sin_theta_0 = sin(theta_0) + + local s0 = cos(theta) - dot * sin_theta / sin_theta_0 + local s1 = sin_theta / sin_theta_0 + + local slerped = { + q0[1] * s0 + q1[1] * s1, + q0[2] * s0 + q1[2] * s1, + q0[3] * s0 + q1[3] * s1, + q0[4] * s0 + q1[4] * s1, + } + qnormalize(slerped) + return slerped + end + + --- Performs normalized linear interpolation between q0 and q1 + function Q.Nlerp(q0, q1, t) + local t1 = 1 - t + local q2 + if qdot(q0, q1) < 0 then + q2 = { q0[1] * t1 - q1[1] * t, q0[2] * t1 - q1[2] * t, q0[3] * t1 - q1[3] * t, q0[4] * t1 - q1[4] * t } + else + q2 = { q0[1] * t1 + q1[1] * t, q0[2] * t1 + q1[2] * t, q0[3] * t1 + q1[3] * t, q0[4] * t1 + q1[4] * t } + end + qnormalize(q2) + return q2 + end + + function Q.Forward(q) + local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] + local t2, t3, t4 = q2 * 2, q3 * 2, q4 * 2 + return Vector( + q1 * q1 + q2 * q2 - q3 * q3 - q4 * q4, + t3 * q2 + t4 * q1, + t4 * q2 - t3 * q1 + ) + end + + function Q.Right(q) + local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] + local t2, t3, t4 = q2 * 2, q3 * 2, q4 * 2 + return Vector( + t4 * q1 - t2 * q3, + q2 * q2 - q1 * q1 + q4 * q4 - q3 * q3, + - t2 * q1 - t3 * q4 + ) + end + + function Q.Up(q) + local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] + local t2, t3, t4 = q2 * 2, q3 * 2, q4 * 2 + return Vector( + t3 * q1 + t2 * q4, + t3 * q4 - t2 * q1, + q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4 + ) + end + + --- Returns quaternion for rotation about axis by angle (in degrees) + function Q.Rotation(axis, ang) + local ax = Vector(axis[1], axis[2], axis[3]) + ax:Normalize() + local ang2 = ang * deg2rad * 0.5 + return { cos(ang2), ax.x * sin(ang2), ax.y * sin(ang2), ax.z * sin(ang2) } + end + + function Q.RotationFromVector(rv1) + local angSquared = rv1[1] * rv1[1] + rv1[2] * rv1[2] + rv1[3] * rv1[3] + if angSquared == 0 then return qnew(1, 0, 0, 0) end + local len = sqrt(angSquared) + local ang = (len + 180) % 360 - 180 + local ang2 = ang * deg2rad * 0.5 + local sang2len = sin(ang2) / len + return { cos(ang2), rv1[1] * sang2len, rv1[2] * sang2len, rv1[3] * sang2len } + end + + function Q.RotationAngle(q) + local l2 = q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4] + if l2 == 0 then return 0 end + local l = sqrt(l2) + local ang = 2 * acos(math_clamp(q[1] / l, -1, 1)) * rad2deg + if ang > 180 then ang = ang - 360 end + return ang + end + + function Q.RotationAxis(q) + local m2 = q[2] * q[2] + q[3] * q[3] + q[4] * q[4] + if m2 == 0 then return Vector(0, 0, 1) end + local m = sqrt(m2) + return Vector(q[2] / m, q[3] / m, q[4] / m) + end + + function Q.RotationVector(q) + local l2 = q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4] + local m2 = math.max(q[2] * q[2] + q[3] * q[3] + q[4] * q[4], 0) + if l2 == 0 or m2 == 0 then return Vector(0, 0, 0) end + local s = 2 * acos(math_clamp(q[1] / sqrt(l2), -1, 1)) * rad2deg + if s > 180 then s = s - 360 end + s = s / sqrt(m2) + return Vector(q[2] * s, q[3] * s, q[4] * s) + end + + function Q.ToMatrix(q) + local w, x, y, z = q[1], q[2], q[3], q[4] + return { + 1 - 2 * y * y - 2 * z * z, 2 * x * y - 2 * z * w, 2 * x * z + 2 * y * w, + 2 * x * y + 2 * z * w, 1 - 2 * x * x - 2 * z * z, 2 * y * z - 2 * x * w, + 2 * x * z - 2 * y * w, 2 * y * z + 2 * x * w, 1 - 2 * x * x - 2 * y * y + } + end + + function Q.ToAngle(q) + local l = sqrt(q[1] * q[1] + q[2] * q[2] + q[3] * q[3] + q[4] * q[4]) + if l == 0 then return Angle(0, 0, 0) end + local q1, q2, q3, q4 = q[1] / l, q[2] / l, q[3] / l, q[4] / l + + local x = Vector( + q1 * q1 + q2 * q2 - q3 * q3 - q4 * q4, + 2 * q3 * q2 + 2 * q4 * q1, + 2 * q4 * q2 - 2 * q3 * q1 + ) + + local y = Vector( + 2 * q2 * q3 - 2 * q4 * q1, + q1 * q1 - q2 * q2 + q3 * q3 - q4 * q4, + 2 * q2 * q1 + 2 * q3 * q4 + ) + + local ang = x:Angle() + if ang.p > 180 then ang.p = ang.p - 360 end + if ang.y > 180 then ang.y = ang.y - 360 end + + local yyaw = Vector(0, 1, 0) + yyaw:Rotate(Angle(0, ang.y, 0)) + + local roll = acos(math_clamp(y:Dot(yyaw), -1, 1)) * rad2deg + + local dot = q2 * q1 + q3 * q4 + if dot < 0 then roll = -roll end + + return Angle(ang.p, ang.y, roll) + end + + function Q.Normalized(q) + return qgetnormalized(q) + end + + function Q.ToString(q) + return format(q) + end +end + --- Conversion factors for unit conversion gates and E2 functions. --- Each value represents the factor to convert from natural units to this unit. --- Natural units: inches for length/speed, kilograms for weight. diff --git a/lua/wire/stools/value.lua b/lua/wire/stools/value.lua index 9e47517121..626e31b28e 100644 --- a/lua/wire/stools/value.lua +++ b/lua/wire/stools/value.lua @@ -76,6 +76,7 @@ if CLIENT then Vector = "VECTOR", ["2D Vector"] = "VECTOR2", ["4D Vector"] = "VECTOR4", + Quaternion = "QUATERNION", } local types_lookup2 = { NORMAL = "Number", @@ -84,9 +85,10 @@ if CLIENT then VECTOR = "Vector", VECTOR2 = "2D Vector", VECTOR4 = "4D Vector", + QUATERNION = "Quaternion", } - local types_ordered = { "Number", "String", "Angle", "Vector", "2D Vector", "4D Vector" } + local types_ordered = { "Number", "String", "Angle", "Vector", "2D Vector", "4D Vector", "Quaternion" } local ValuePanels = {} local selectedValues = {} @@ -156,11 +158,17 @@ if CLIENT then saveValues() end + local function validateVec4( val ) + local x,y,z,w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) + return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil and tonumber(w) ~= nil + end + local validityChecks = { Number = function( val ) return tonumber(val) ~= nil end, ["2D Vector"] = function( val ) local x,y = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil end, Vector = function( val ) local x,y,z = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil end, - ["4D Vector"] = function( val ) local x,y,z,w = string.match( val, "^ *([^%s,]+) *, *([^%s,]+) *, *([^%s,]+) *, *([%d.]+) *$" ) return tonumber(x) ~= nil and tonumber(y) ~= nil and tonumber(z) ~= nil and tonumber(w) ~= nil end, + ["4D Vector"] = validateVec4, + Quaternion = validateVec4, String = function( val ) return true end, } validityChecks.Angle = validityChecks.Vector -- it's the same as vectors @@ -170,6 +178,7 @@ if CLIENT then ["2D Vector"] = "12.34, 12.34", Vector = "12.34, 12.34, 12.34", ["4D Vector"] = "12.34, 12.34, 12.34, 12.34", + Quaternion = "1, 0, 0, 0", String = "Hello World", Angle = "90, 180, 360", } @@ -189,6 +198,7 @@ if CLIENT then {5, validityChecks["2D Vector"]}, {4, validityChecks.Vector}, {6, validityChecks["4D Vector"]}, + {7, validityChecks.Quaternion}, {2, validityChecks.String}, } From e71f36d0e436d7f4a25b95183977ccce1b8f8ad5 Mon Sep 17 00:00:00 2001 From: Strand8319 Date: Fri, 27 Mar 2026 23:15:07 +0500 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=A7=20fix=20lint=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lua/wire/server/wirelib.lua | 4 ++-- lua/wire/stools/value.lua | 18 +++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lua/wire/server/wirelib.lua b/lua/wire/server/wirelib.lua index 49c47505e7..9b5e26e43f 100644 --- a/lua/wire/server/wirelib.lua +++ b/lua/wire/server/wirelib.lua @@ -570,7 +570,7 @@ do function Q.Forward(q) local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] - local t2, t3, t4 = q2 * 2, q3 * 2, q4 * 2 + local t3, t4 = q3 * 2, q4 * 2 return Vector( q1 * q1 + q2 * q2 - q3 * q3 - q4 * q4, t3 * q2 + t4 * q1, @@ -590,7 +590,7 @@ do function Q.Up(q) local q1, q2, q3, q4 = q[1], q[2], q[3], q[4] - local t2, t3, t4 = q2 * 2, q3 * 2, q4 * 2 + local t2, t3 = q2 * 2, q3 * 2 return Vector( t3 * q1 + t2 * q4, t3 * q4 - t2 * q1, diff --git a/lua/wire/stools/value.lua b/lua/wire/stools/value.lua index 626e31b28e..0c4ffd7597 100644 --- a/lua/wire/stools/value.lua +++ b/lua/wire/stools/value.lua @@ -20,8 +20,6 @@ local function netWriteValues( selectedValues ) local amount = math.Clamp(#selectedValues,0,20) net.WriteUInt(amount,5) for i=1,amount do - local DataType, Value = selectedValues[i].DataType, selectedValues[i].Value - net.WriteString( selectedValues[i].DataType ) net.WriteString( string.sub(selectedValues[i].Value,1,3000) ) end @@ -90,7 +88,6 @@ if CLIENT then local types_ordered = { "Number", "String", "Angle", "Vector", "2D Vector", "4D Vector", "Quaternion" } - local ValuePanels = {} local selectedValues = {} local panels = {} local slider @@ -242,7 +239,7 @@ if CLIENT then rem:SetImage( "icon16/delete.png" ) rem:SizeToContents() rem:SetPos( 0, 4 ) - rem:SetToolTip( "Remove this value" ) + rem:SetTooltip( "Remove this value" ) rem.DoClick = function() if #selectedValues == 1 then -- can't remove the last value @@ -274,10 +271,10 @@ if CLIENT then tp = types_lookup2[tp] or "Number" if validateValue( val, tp ) then - pnl.valueEntry:SetToolTip() + pnl.valueEntry:SetTooltip() pnl.parseIcon:SetImage( "icon16/accept.png" ) else - pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) + pnl.valueEntry:SetTooltip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) pnl.parseIcon:SetImage( "icon16/cancel.png" ) end end @@ -301,10 +298,10 @@ if CLIENT then guessType( val, typeSelection ) else if validateValue( val, tp ) then - pnl.valueEntry:SetToolTip() + pnl.valueEntry:SetTooltip() pnl.parseIcon:SetImage( "icon16/accept.png" ) else - pnl.valueEntry:SetToolTip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) + pnl.valueEntry:SetTooltip( "This is not a valid " .. string.lower( tp ) .. ".\nExample: '" .. (examples[tp] or "No example available for this type") .. "'." ) pnl.parseIcon:SetImage( "icon16/cancel.png" ) end end @@ -383,7 +380,7 @@ if CLIENT then resetButton = reset typeGuessCheckbox = panel:CheckBox( "Automatically guess types", "wire_value_guesstype" ) - typeGuessCheckbox:SetToolTip( + typeGuessCheckbox:SetTooltip( [[When enabled, the type dropdown will automatically be updated as you type with guessed types. It's unable to guess angles because they look the same as vectors. @@ -396,7 +393,6 @@ There will never be an error if auto type guessing is enabled (unless you manual set the type), because it will automatically set the type to a string when all other types fail.]] ) - local w,_ = panel:GetSize() local valueSlider = vgui.Create( "DNumSlider" ) slider = valueSlider panel:AddItem( valueSlider ) @@ -462,7 +458,7 @@ types fail.]] ) local add = vgui.Create( "DImageButton", pnl ) add:SetImage( "icon16/add.png" ) add:SizeToContents() - add:SetToolTip( "Add a new value" ) + add:SetTooltip( "Add a new value" ) function pnl.PerformLayout() add:Center()