better Vector3 coercion semantics

This commit is contained in:
Lera Elvoé 2025-02-15 13:37:39 +03:00
parent 4900c50850
commit 40c9af4803
Signed by: yagich
SSH Key Fingerprint: SHA256:6xjGb6uA7lAVcULa7byPEN//rQ0wPoG+UzYVMfZnbvc

View File

@ -27,6 +27,25 @@ local function is_weak_vector3(t)
end
end
---Coerces t to a Vector3. If t is already a Vector3, returns it without copying.
---@param t vectorlike
---@param do_err boolean?
---@param def Vector3?
---@return boolean error, Vector3 result
local function coerce(t, do_err, def)
do_err = do_err or true
if do_err and not is_weak_vector3(t) then
def = def or Vector3()
error("Vector3: can not coerce t into Vector3. Returning " .. tostring(def))
return true, def
end
if t["_CLASS_"] == "Vector3" then
return false, t
end
return false, Vector3(t)
end
---Returns a Vector3 multiplied either component-wise (if b is a weak Vector3) or multiplies each component by b if b is a number.
---@param b number|vectorlike
---@return Vector3
@ -167,12 +186,11 @@ end
---@param with vectorlike
---@return number
function Vector3:dot(with)
if not is_weak_vector3(with) then
error("Vector3: with must be a Vector3-like table. Returning 0")
return 0
local err, v2 = coerce(with)
if err then
return 0.0
end
local v2 = Vector3(with)
return self.x * v2.x + self.y * v2.y + self.z * v2.z
end
@ -180,11 +198,10 @@ end
---@param with vectorlike
---@return Vector3
function Vector3:cross(with)
if not is_weak_vector3(with) then
error("Vector3: with must be a Vector3-like table. Returning Vector3()")
return Vector3()
local err, v2 = coerce(with)
if err then
return v2
end
local v2 = Vector3(with)
return Vector3 {
self.y * v2.z - self.z * v2.y,
self.z * v2.x - self.x * v2.z,
@ -197,17 +214,22 @@ end
---@param angle number
---@return Vector3
function Vector3:rotated(axis, angle)
if not is_weak_vector3(axis) then
error("Vector3: axis must be a Vector3-like table. Returning Vector3()")
return Vector3()
-- if not is_weak_vector3(axis) then
-- error("Vector3: axis must be a Vector3-like table. Returning Vector3()")
-- return Vector3()
-- end
local err, vaxis = coerce(axis)
if err then
return vaxis
end
axis = Vector3(axis):normalized()
---@diagnostic disable-next-line: undefined-field
vaxis = vaxis:normalized()
local cosa = math.cos(angle)
local sina = math.sin(angle)
-- __mul is only defined for the left operand (table), numbers don't get metatables.
-- as such, the ordering of operations here is specific
local v = (self * cosa) + (axis * ((1 - cosa) * self:dot(axis))) + (axis:cross(self) * sina)
local v = (self * cosa) + (vaxis * ((1 - cosa) * self:dot(vaxis))) + (vaxis:cross(self) * sina)
return Vector3(v)
end
@ -222,12 +244,11 @@ end
---@param t number
---@return Vector3
function Vector3:lerp(b, t)
-- return self:copy()
if not is_weak_vector3(b) then
error("Vector3: b must be a Vector3-like table. Returning copy of self.")
return self:copy()
local err, w = coerce(b)
if err then
return w
end
local w = Vector3(b)
return Vector3{
util.lerp(self.x, w.x, t),
util.lerp(self.y, w.y, t),
@ -235,6 +256,44 @@ function Vector3:lerp(b, t)
}
end
function Vector3:direction_to(to)
local err, other = coerce(to)
if err then
return 0.0
end
return Vector3(other.x - self.x, other.y - self.y, other.z - self.z):normalized()
end
---Returns the squared distance between this vector and to.
---@param to vectorlike
---@return number
function Vector3:distance_squared_to(to)
local err, other = coerce(to)
if err then
return 0.0
end
return (other - self):length_squared()
end
---Returns the distance between this vector and to.
---@param to vectorlike
---@return number
function Vector3:distance_to(to)
local err, other = coerce(to)
if err then
return 0.0
end
return (other - self):length()
end
---Returns a new vector with the Y discarded (0).
---@return Vector3
function Vector3:horizontal()
return Vector3(self.x, 0.0, self.z)
end
---- CONSTANTS
Vector3.UP = Vector3(0, 1, 0)