How can I improve this Vector4 module?

I have a module that is supposed to create 4d vectors. The code works fine, but I’m wondering if I can improve performance or readability.

--!strict
local Vector4 = {}

local TYPE_STRING: string = "Vector4"

-- to make typechecker happy
local function CalculateMagnitude(self: Vector4Type): number
	return math.sqrt(self.X^2 + self.Y^2 + self.Z^2 + self.W^2)
end
local function CalculateLerp(x0: number, y0: number, x1: number, y1: number, x: number): number
	return y0 * (1 - (x - x0) / (x1 - x0)) + y1 * (1 - (x1 - x) / (x1 - x0))
end


local function MultiplyVector4(self: Vector4Type, value: Vector4Type | number): Vector4Type
	if typeof(value) == "number" then
		return Vector4.new(self.X * value, self.Y * value, self.Z * value, self.W * value)
	end
	if typeof(value) ~= "table" then
		error(string.format("Attempted to add %s and %s", TYPE_STRING, typeof(value)))
	end
	local value: Vector4Type = (value:: Vector4Type)
	return Vector4.new(value.X * self.X, value.Y * self.Y, value.Z * self.Z, value.W * self.W)
end

local function DivideVector4(self: Vector4Type, value: Vector4Type | number): Vector4Type
	if typeof(value) == "number" then
		return Vector4.new(self.X / value, self.Y / value, self.Z / value, self.W / value)
	end
	if typeof(value) ~= "table" then
		error(string.format("Attempted to add %s and %s", TYPE_STRING, typeof(value)))
	end
	local value: Vector4Type = (value:: Vector4Type)
	return Vector4.new(value.X / self.X, value.Y / self.Y, value.Z / self.Z, value.W / self.W) 
end

Vector4.__index = function(self: Vector4Type, key): any
	-- We can't precompute the Unit Vector, since the unit vector would require
	-- it's own unit vector, which would need another unit vector blah blah
	-- causing a stack overflow
	if key == "Unit" then
		
		return DivideVector4(self, CalculateMagnitude(self))
	elseif key == "Magnitude" then
		return CalculateMagnitude(self)
	else
		return Vector4[key]
	end
	
end
Vector4.__add = function(self: Vector4Type, value: Vector4Type)
	if typeof(value) ~= "table" then
		error(string.format("Attempted to add %s and %s", TYPE_STRING, typeof(value)))
	end
	return Vector4.new(value.X + self.X, value.Y + self.Y, value.Z + self.Z, value.W + self.W)
end
Vector4.__mul = MultiplyVector4

Vector4.__sub = function(self: Vector4Type, value: Vector4Type)
	if typeof(value) ~= "table" then
		error(string.format("Attempted to subtract %s and %s", TYPE_STRING, typeof(value)))
	end
	return Vector4.new(value.X - self.X, value.Y - self.Y, value.Z - self.Z, value.W - self.W)
end

Vector4.__div = DivideVector4

Vector4.__tostring = function(self)
	return string.format("{%s, %s, %s, %s}", tostring(self.X), tostring(self.Y), tostring(self.Z), tostring(self.W))
end
Vector4.__newindex = function(self, key, value)
	error(string.format("Attempted to set %s of %s to %s.", key, tostring(self), tostring(value)))
end

export type Vector4Type = {
	X: number,
	Y: number,
	Z: number,
	W: number,
	__type: string,
	-- Needed to allow error less indexing of Magnitude and Unit
	Magnitude: number?,
	Unit: Vector4Type?
}
function Vector4.new(X: number?, Y: number?, Z: number?, W: number?): Vector4Type
	local X: number = X or 0
	local Y: number = Y or 0
	local Z: number = Z or 0
	local W: number = W or 0
	local self: Vector4Type = {
		X = X,
		Y = Y,
		Z = Z,
		W = W,
		__type = TYPE_STRING
	}
	return setmetatable(self, Vector4)
end

function Vector4:Dot(OtherVector4: Vector4Type): number
	return OtherVector4.X * self.X + OtherVector4.Y * self.Y + OtherVector4.Z * self.Z + OtherVector4.W * self.W
end
function Vector4:FuzzyEq(OtherVector4: Vector4Type, Epsilon: number?): boolean
	local Epsilon: number = Epsilon or 0.001
	for key: string, value: number in pairs(OtherVector4) do
		if key == "__type" then continue end
		if math.abs(value - OtherVector4[key]) <= Epsilon then
			return true
		end
	end
	return false
end
function Vector4:GetComponents(): (number, number, number, number)
	return self.X, self.Y, self.Z, self.W
end
function Vector4:Lerp(EndVector4: Vector4Type, Alpha: number?): Vector4Type
	local Alpha: number = Alpha or 0.5
	
	local x0: number = self.X
	local x1: number = EndVector4.X
	
	local y0: number = self.Y
	local y1: number = EndVector4.Y
	
	local z0: number = self.Z
	local z1: number = EndVector4.Z
	
	local w0: number = self.W
	local w1: number = EndVector4.W
	
	local Alpha: number = x1 - x0 * Alpha
	local y = CalculateLerp(x0, y0, x1, y1, Alpha)
	local z = CalculateLerp(x0, z0, x0, z0, Alpha)
	local w = CalculateLerp(x0, w0, x1, w1, Alpha)
	return Vector4.new(Alpha, y, z, w)
end
return Vector4
5 Likes

First of all I don’t really see the point of this module but if its for practice there a lot of stuffs you should change. You should try using meta-methods instead of having functions like MultiplyVector4 DivideVector4 etc. As you can see in the roblox vector3 datatype , you could vector1/integer and vector1 * integer instead of having a function to do it,I saw you do it on two Vector4 so you can do the same on integer and vector4,this makes it more friendly to use. For the stack over-flow issue you told about Unit vector , just checking whether the new unit vector’s magnitude is1 if it is 1 do not calculate it else you should calculate it.

1 Like

It seems that you’re using math and string library a lot, such as math.sqrt, math.abs and string.format. It would definitely help if you create a variable that holds the library functions instead of indexing it over and over again. If you’re into micro optimization or want more performance tips, you could look into this.

1 Like

Is there any benefit to doing this?

Also just out of curiosity, what does “:any” do?

And why do: “self: Vector4Type”?
I’m assuming self and vector4type referes to same object?

Luau already micro optimizes the global library indexing for you so there is no need to save library functions to variables like people used to back in Lua 5.1.4 days:
image

3 Likes