Vector

alpaca

King of Ungulates
Joined
Aug 3, 2006
Messages
2,322
I needed a more powerful and general vector implementation that supports adding and such, so yeah, not much to say, really. Create a new vector with Vector.new({1,1,0}) or Vector.new(3), where the latter creates a 0 vector of length 3, or by calling Vector({1,1,0}).

Supports adding, subtracting, scalar (elementwise) multiplication and division, dot product using the concatenation .. operator, cross-product for 3d vectors using the ^ operator. Also supports equality checks and a less than v1 < v2 implementation that checks if the absolute value of v1 is smaller than that of v2 (so v1 is in a ball defined by abs(v2)).

Most functions work with scalars if you would expect them to, so *, /, +, -, <, == all support scalars in some way.

Just add the code to a lua file and into the VFS, then include it in your scripts when you need it.

Code:
--[[
	vector.lua
	
	Creator: alpaca
	Last Change: 04.02.2011
	
	Description: Defines a vector type with the following operators:
		+ adds two vectors or adds a scalar onto each element of the vector
		- the same but subtracts rhs from lhs; can be used unary in which case -v is returned
		* scalar multiplication for vector*scalar or element-wise product for vector*vector
		/ scalar division; element-wise for vectors
		.. dot (scalar) product
		^ outer product (c = a^b means c[i] = a[i+1]*b[i+2] - a[i+2]*b[i+1] where i is taken modulo #n, so a[4] == a[1]); only defined for vectors of dimension 3
		
		Supports equality and an implementation of v1 < v2 that means abs(v1) < abs(v2)
		
		Note: Civ5 lua doesn't support rawget :/
		{Supports slicing in the form ["a:b"] where a is the first included index and b the last included one (so v["1:#v"] = v, v["1:1"] = v[1]); a or b can be nil in which case 1 or #v respectively will be assumed}
]]--

Vector = {}
local meta = {}
local mt = {}
setmetatable(Vector, mt)

--[[
	Constructor. Shallow-copies the array part of a table for initialisation
	Arguments:
		t: table or num. The array part of t will be used to initialise the vector, so new({1,0,0}) will create a 3d-unit vector. If t is a number, a zero-vector of length t will be created
]]--
function Vector.new(t)
	local vector = {}
	if t then
		if type(t) == "table" then
			for k, v in ipairs(t) do
				vector[k] = v
			end
		elseif type(t) == "number" then
			for k = 1, t do
				vector[k] = 0
			end
		else
			error("Vector constructor called with illegal value: "..tostring(t))
		end
	end
	vector.type = "vector"
	vector.abs = Vector.abs
	setmetatable(vector, meta)
	return vector
end
mt.__call = function(tbl, ...) return Vector.new(...)  end

function Vector.add(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" then
		if type(rhs) == "table" and rhs.type == "vector" then
			if #lhs ~= #rhs then
				error("Can't add, vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
			end
			
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v + rhs[k]
			end
			return result
		elseif type(rhs) == "number" then
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v + rhs
			end
			return result
		end
	elseif type(rhs) == "table" and rhs.type == "vector" then
		local result = Vector.new(lhs)
		for k, v in ipairs(rhs) do
			result[k] = v + lhs
		end
		return result
	end
	error("Can't add. Either argument is neither a vector nor a number: "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.subtract(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" then
		if type(rhs) == "table" and rhs.type == "vector" then
			if #lhs ~= #rhs then
				error("Can't subtract, vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
			end
			
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v - rhs[k]
			end
			return result
		elseif type(rhs) == "number" then
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v - rhs
			end
			return result
		end
	elseif type(rhs) == "table" and rhs.type == "vector" then
		local result = Vector.new(lhs)
		for k, v in ipairs(rhs) do
			result[k] = lhs - v
		end
		return result
	end
	error("Can't subtract. Either argument is neither a vector nor a number: "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.negate(v)
	return Vector.subtract(0, v)
end

function Vector.scalarMult(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" then
		if type(rhs) == "table" and rhs.type == "vector" then
			if #lhs ~= #rhs then
				error("Scalar multiplication invalid: vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
			end
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v * rhs[k]
			end
			return result
		elseif type(rhs) == "number" then
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v * rhs
			end
			return result
		else
			error("Scalar multiplication invalid: lhs is a vector but rhs is neither vector nor scalar: "..tostring(lhs)..", "..tostring(rhs))
		end
	elseif type(rhs) == "table" and rhs.type == "vector" then
		if type(lhs) == "number" then
			local result = Vector.new(rhs)
			for k, v in ipairs(rhs) do
				result[k] = v * lhs
			end
			return result
		else
			error("Scalar multiplication invalid: rhs is a vector but lhs is no scalar"..tostring(lhs)..", "..tostring(rhs))
		end
	end
	error("Scalar multiplication invalid: neither side is a vector"..tostring(lhs)..", "..tostring(rhs))
end

function Vector.divide(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" then
		if type(rhs) == "table" and rhs.type == "vector" then
			if #lhs ~= #rhs then
				error("Scalar division invalid: vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
			end
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v / rhs[k]
			end
			return result
		elseif type(rhs) == "number" then
			local result = Vector.new(lhs)
			for k, v in ipairs(lhs) do
				result[k] = v / rhs
			end
			return result
		else
			error("Scalar division invalid: lhs is a vector but rhs is neither vector nor scalar: "..tostring(lhs)..", "..tostring(rhs))
		end
	elseif type(rhs) == "table" and rhs.type == "vector" then
		if type(lhs) == "number" then
			local result = Vector.new(rhs)
			for k, v in ipairs(rhs) do
				result[k] = lhs / v
			end
			return result
		else
			error("Scalar division invalid: rhs is a vector but lhs is no scalar"..tostring(lhs)..", "..tostring(rhs))
		end
	end
	error("Scalar division invalid: neither side is a vector"..tostring(lhs)..", "..tostring(rhs))
end

function Vector.dotProduct(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "table" and rhs.type == "vector" then
		if #lhs ~= #rhs then
			error("Dot product invalid: vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
		end
		
		local result = 0
		for k, v in ipairs(lhs) do
			result = result + v * rhs[k]
		end
		return result
	end
	error("Dot product invalid: either lhs or rhs is not a vector: "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.crossProduct(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "table" and rhs.type == "vector" then
		if #lhs ~= 3 or #rhs ~= 3 then
			error("Cross product invalid: both vectors have to be of length 3: "..tostring(lhs)..", "..tostring(rhs))
		end
		
		local result = Vector.new(3)
		result[1] = lhs[2]*rhs[3] - lhs[3]*rhs[2]
		result[2] = lhs[3]*rhs[1] - lhs[1]*rhs[3]
		result[3] = lhs[1]*rhs[2] - lhs[2]*rhs[1]
		return result
	end
	error("Cross product invalid: either lhs or rhs is not a vector: "..tostring(lhs)..", "..tostring(rhs))
end


function Vector.eq(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "table" and rhs.type == "vector" then
		if #lhs ~= #rhs then
			error("Equality check invalid: vectors don't have the same length: "..tostring(lhs)..", "..tostring(rhs))
		end
		for k, v in ipairs(lhs) do
			if v ~= rhs[k] then
				return false
			end
		end
		return true
	elseif type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "number" then
		return Vector.abs(lhs) == rhs
	elseif type(rhs) == "table" and rhs.type == "vector" and type(lhs) == "number" then
		return lhs == Vector.abs(rhs)
	end
	error("Equality check invalid: not both sides are vectors "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.lt(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "table" and rhs.type == "vector" then
		return Vector.abs(lhs) < Vector.abs(rhs)
	elseif type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "number" then
		return Vector.abs(lhs) < rhs
	elseif type(rhs) == "table" and rhs.type == "vector" and type(lhs) == "number" then
		return lhs < Vector.abs(rhs)
	end
	error("Less than check invalid: not both sides are vectors "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.le(lhs, rhs)
	if type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "table" and rhs.type == "vector" then
		return Vector.abs(lhs) <= Vector.abs(rhs)
	elseif type(lhs) == "table" and lhs.type == "vector" and type(rhs) == "number" then
		return Vector.abs(lhs) <= rhs
	elseif type(rhs) == "table" and rhs.type == "vector" and type(lhs) == "number" then
		return lhs <= Vector.abs(rhs)
	end
	error("Leq check invalid: not both sides are vectors "..tostring(lhs)..", "..tostring(rhs))
end

function Vector.tostring(v)
	local rs = "("
	for k, n in ipairs(v) do
		rs = rs .. tostring(n) .. (k < #v and ", " or "")
	end
	return rs .. ")"
end



meta.__add = Vector.add
meta.__sub = Vector.subtract
meta.__unm = Vector.negate
meta.__mul = Vector.scalarMult
meta.__div = Vector.divide
meta.__concat = Vector.dotProduct
meta.__pow = Vector.crossProduct
meta.__eq = Vector.eq
meta.__lt = Vector.lt
meta.__le = Vector.le
meta.__tostring = Vector.tostring


function Vector.abs(v)
	local result = 0
	for _, v in ipairs(v) do
		result = result + v^2
	end
	return math.sqrt(result)
end
 
Interesting... what is it used for?
 
You mean what I use it for? Well, I'm in the process of writing replacement functions for the vanilla build yield functions (which are bugged beyond repair) and I use vectors to store yields. Then I can just go through all improvements and add the vectors instead of having to manually loop over all yield types every time. It's slightly less efficient but a lot easier to code.

Apart from that, I'll probably end up using it who-knows-where. A lot of things can be thought of as vector space, actually. For example say you want to treat strength and ranged strength equally and you want to compare two units, then you can put both in a vector and calculate the difference. Or you want to check how many units of a certain characteristic you have, which you can for example get by using a vector that stores all unit numbers and another vector that has a 1 wherever your characteristic is true, then just do the dot product between those vectors and you get the number of units. You can store the second vector so you can use it every turn for example.

Ultimately, I guess it just bugged me that vanilla's vector implementation is fixed-length and is not even an actual vector because it defines no addition :lol:
 
Ultimately, I guess it just bugged me that vanilla's vector implementation is fixed-length and is not even an actual vector because it defines no addition :lol:
I rather suspect they meant vector in the computer data structures sense (meaning an ordered collection, basically), rather than in the mathematical sense. In which case, the vanilla vector is, indeed a vector. For another example, see the Vector class in the Java class library.
 
So it's adding, subtracting, etc, individual pairs as you trace down two equal lists? I looked at Wiki's definition... :crazyeye: mathththth.... Perhaps a {CENSOR: "developmentally delayed"}* icon would be more appropriate here, but enough believe that isn't appropriate anywhere, thus is missing from my list of choices.

So per your example, {1,0,1} + {1,1,0} = {2,1,1}?




*Crap! They even got the post submit censor! :p
 
I rather suspect they meant vector in the computer data structures sense (meaning an ordered collection, basically), rather than in the mathematical sense. In which case, the vanilla vector is, indeed a vector. For another example, see the Vector class in the Java class library.

Then why is it used for coordinates in the gane world and has x,y and z entries?

So it's adding, subtracting, etc, individual pairs as you trace down two equal lists? I looked at Wiki's definition... :crazyeye: mathththth.... Perhaps a {CENSOR: "developmentally delayed"}* icon would be more appropriate here, but enough believe that isn't appropriate anywhere, thus is missing from my list of choices.

So per your example, {1,0,1} + {1,1,0} = {2,1,1}?


*Crap! They even got the post submit censor! :p

Yes. Some more high-level languages such as Mathematica or Matlab allow you to do such operations with vectors and tensors very easily, and it's quite comfortable to write. Anyhow, adding vectors is a useful thing for coordinates and such.
 
Then why is it used for coordinates in the gane world and has x,y and z entries?
Okay, I guess they confused the two concepts, giving a least-useful-of-each interpretation...

Actually, the existence of a z coordinate for a spatial vector on a 2-D world is pretty weird, as well...
 
Okay, I guess they confused the two concepts, giving a least-useful-of-each interpretation...

Actually, the existence of a z coordinate for a spatial vector on a 2-D world is pretty weird, as well...

The engine is 3D, actually, it's just movement that is restricted to two dimensions. Units and stuff have height and planes fly above the ground
 
The engine is 3D, actually, it's just movement that is restricted to two dimensions. Units and stuff have height and planes fly above the ground
The graphics are 3D... the 'game' itself appears, as in previous games, to be 2D. Even SMAC was 2D, just with each square having a 'height' - positions etc were all 2D. Units have a height graphically, planes fly above the ground graphically, but as far as I can tell, in game engine (not graphics engine) terms, there are no heights. If you wish to state where anything is, it takes two variables - the position space is (roughly, given the hexy oddness) Z^2.
 
The graphics are 3D... the 'game' itself appears, as in previous games, to be 2D. Even SMAC was 2D, just with each square having a 'height' - positions etc were all 2D. Units have a height graphically, planes fly above the ground graphically, but as far as I can tell, in game engine (not graphics engine) terms, there are no heights. If you wish to state where anything is, it takes two variables - the position space is (roughly, given the hexy oddness) Z^2.

In my opinion, the game (what you call graphics engine) is three-dimensional. However, when you move units, they only move in discrete steps in two directions on a certain plane in 3d space. All 2d graphics like the hex grid are actually overlays on the 3d world and are, in fact, defined in three dimensions (hex overlays follow trees for example).

Look at it like this: You say, when you play Chess, the world is 2d. I say, when you play Chess, the world is 3d but my pieces move on a board in only two dimensions in discrete steps. I'm pretty sure the world wasn't 2d the last time I played chess, I would have noticed not being able to turn around :lol:

Sure, the game rules don't care about the 3rd dimension but that doesn't mean I can always get away with not representing it. Hence, there are 3d vectors
 
But the Lua code doesn't allow you to touch the 3D graphics; it only cares about the rules-world, which is 2D. However, this is a highly abstract theoretical argument with very little application to reality, as we appear to agree in terms of actual application (or nearly).

Oh yeah, evidence that the abstract 'game' itself is only 2D... the Strategic View represents everything important to the abstract 'game', it's just a different visual reification to that of the normal view.
 
I suppose if a modder wanted to implement a 3rd dimension, they could implement their own lua code that utilizes the 3d data. Perhaps as part of their own combat calculation system? {shrug} Just guessing.
 
Back
Top Bottom