I have made a custom Vector3 module that uses OOP and a ton of metamethods. It’s not the best code for sure but the reason why it’s efficient is because the methods don’t get stored inside every object but instead only inside the parent table (the metatable). This saves a lot of memory and that’s the reason why people use metatables in OOP too.
local DataHolder = {}
local module = {}
-- Axis X = i, Axis Y = j, Axis Z = k
setmetatable(module, DataHolder)
function module.calculateMagnitude(Vector) : number
return math.abs(math.sqrt(Vector.X ^ 2 + Vector.Y ^ 2 + Vector.Z ^ 2))
end
function module.calculateUnitVector(Vector)
return Vector / Vector.Magnitude
end
function module.calculateVector(Vector) : nil
Vector.Magnitude = module.calculateMagnitude(Vector)
if rawget(Vector, "Unit") ~= nil then
local Estimate = module.calculateUnitVector(Vector)
if not (Estimate == Vector.Unit) then
Vector.Unit = Estimate
end
end
if rawequal(Vector, "V") ~= nil then
Vector.V = Vector3.new(Vector.X, Vector.Y, Vector.Z)
end
end
function module.new(X : number?, Y : number?, Z : number?)
local Vector = {}
X = X ~= nil and X or 0
Y = Y ~= nil and Y or 0
Z = Z ~= nil and Z or 0
setmetatable(Vector, module)
Vector.X, Vector.Y, Vector.Z = X, Y, Z
module.calculateVector(Vector)
return Vector
end
function module.xAxis(Mutliplier : number)
return module.new(1 * Mutliplier, 0, 0)
end
function module.yAxis(Mutliplier : number)
return module.new(0, 1 * Mutliplier, 0)
end
function module.zAxis(Mutliplier : number)
return module.new(0, 0, 1 * Mutliplier)
end
function module:setX(NewX : number) self.X = NewX; module.calculateVector(self) end
function module:setY(NewY : number) self.Y = NewY; module.calculateVector(self) end
function module:setZ(NewZ : number) self.Z = NewZ; module.calculateVector(self) end
function module:IncrementX(Increment : number) self.X += Increment; module.calculateVector(self) end
function module:IncrementY(Increment : number) self.Y += Increment; module.calculateVector(self) end
function module:IncrementZ(Increment : number) self.Z += Increment; module.calculateVector(self) end
function module:DecrementX(Decrement : number) self.X -= Decrement; module.calculateVector(self) end
function module:DecrementY(Decrement : number) self.Y -= Decrement; module.calculateVector(self) end
function module:DecrementZ(Decrement : number) self.Z -= Decrement; module.calculateVector(self) end
function module:Increment(IncrementVector) self.X += IncrementVector.X; self.Y += IncrementVector.Y; self.Z += IncrementVector.Z; module.calculateVector(self) end
function module:Decrement(DecrementVector) self.X -= DecrementVector.X; self.Y -= DecrementVector.Y; self.Z -= DecrementVector.Z; module.calculateVector(self) end
function module:Dot(Vector) : number
return self.X * Vector.X + self.Y * Vector.Y + self.Z * Vector.Z
end
function module:Cross(Vector)
local DeterminantI = self.Y * Vector.Z - self.Z * Vector.Y
local DeterminantJ = self.X * Vector.Z - self.Z * Vector.X
local DeterminantK = self.X * Vector.Y - self.Y * Vector.X
local X, Y, Z = DeterminantI, DeterminantJ == -0 and 0 or -DeterminantJ, DeterminantK
local CrossVector = module.new(X, Y, Z)
return CrossVector
end
function module:Lerp(GoalVector, alpha : number)
local Lerp = self
if alpha ~= 0 and alpha ~= 1 then Lerp = GoalVector * alpha end
if alpha == 1 then Lerp = GoalVector end
return Lerp
end
function module.CalculateFuzzy(a, b, Epsilon)
return a == b or math.abs(a - b) <= (math.abs(a) + 1) * Epsilon
end
function module:FuzzyEq(Vector, Epsilon)
for _, Axis in ipairs({"x", "Y", "Z"}) do
if not module.CalculateFuzzy(self[Axis], Vector[Axis], Epsilon) then
return false
end
end
return true
end
function module:Min(...)
local args = {self, ...}
local X, Y, Z = math.huge, math.huge, math.huge
for _, Vector in args do
if Vector.X < X then X = Vector.X end
if Vector.Y < Y then Y = Vector.Y end
if Vector.Z < Z then Z = Vector.Z end
end
return module.new(X, Y, Z)
end
function module:Max(...)
local args = {self, ...}
local X, Y, Z = -math.huge, -math.huge, -math.huge
for _, Vector in args do
if Vector.X > X then X = Vector.X end
if Vector.Y > Y then Y = Vector.Y end
if Vector.Z > Z then Z = Vector.Z end
end
return module.new(X, Y, Z)
end
--<< Custom >>--
function module:GetAngle(Vector)
return math.acos(math.clamp(self.Unit:Dot(Vector.Unit), -1, 1))
end
function module:AverageVector3s(...)
local Vectors = {self, ...}
local sum = module.zero
for _, Vector in pairs(Vectors) do
sum = sum + Vector
end
return sum / #Vectors
end
function module:MapRange(MinVector, MaxVector, MinRange, MaxRange)
return MinRange + (self - MinVector) * (MaxRange - MinRange) / (MaxVector - MinVector)
end
function module:Clone()
return module.new(self.X, self.Y, self.Z)
end
--<< Stuff >>--
function module.__index(t, Value)
if module[Value] == nil then
if Value == "Unit" then t.Unit = module.calculateUnitVector(t); return t.Unit end
if Value == "V" then t.V = Vector3.new(t.X, t.Y, t.Z); return t.V end
else
return module[Value]
end
end
function DataHolder.__index(t, Value)
if t == module then
if Value == "zero" then return module.new(0, 0, 0) end
if Value == "one" then return module.new(1, 1, 1) end
else
return module[Value]
end
end
--<< Operator Functions >>--
function module.__add(t, Value)
if type(Value) == "number" then
return module.new(t.X + Value, t.Y + Value, t.Z + Value)
elseif type(Value) == "table" or typeof(Value) == "Vector3" then
return module.new(t.X + Value.X, t.Y + Value.Y, t.Z + Value.Z)
end
error("Vectors can only be added by numbers or other vectors!")
end
function module.__sub(t, Value)
if type(Value) == "number" then
return module.new(t.X - Value, t.Y - Value, t.Z - Value)
elseif type(Value) == "table" or typeof(Value) == "Vector3" then
return module.new(t.X - Value.X, t.Y - Value.Y, t.Z - Value.Z)
end
error("Vectors can only be subtracted by numbers or other vectors!")
end
function module.__mul(t, Value)
if type(Value) == "number" then
return module.new(t.X * Value, t.Y * Value, t.Z * Value)
elseif type(Value) == "table" or typeof(Value) == "Vector3" then
return module.new(t.X * Value.X, t.Y * Value.Y, t.Z * Value.Z)
end
error("Vectors can only be multiplied by numbers or other vectors!")
end
function module.__div(t, Value)
if type(Value) == "number" then
return module.new(t.X / Value, t.Y / Value, t.Z / Value)
elseif type(Value) == "table" or typeof(Value) == "Vector3" then
return module.new(t.X / Value.X, t.Y / Value.Y, t.Z / Value.Z)
end
error("Vectors can only be divided by numbers or other vectors!")
end
function module.__eq(t, Value)
if type(Value) == "table" then
return (t.X == Value.X and t.Y == Value.Y and t.Z == Value.Z) and true or false
else
error(Value .. " is not the same datatype!")
end
end
--<< Printing >>--
function module.__tostring(self)
return table.concat({self.X, self.Y, self.Z}, ", ")
end
--<< Convertion >>--
function module:ToNativeVector3()
return Vector3.new(self.X, self.Y, self.Z)
end
return module
I use colons on some functions inside the script. Those just automatically set the “self” variable to the first parameter given inside the function. If we also use the colon outside of the module then it would look something like this: CustomObject:SomeFunction() in this case the first argument will be CustomObject. And inside the module script “self” will be set to that first argument.