summaryrefslogtreecommitdiff
path: root/common/coords.lua
blob: 420b287c81c5d3c7b9300929bbb4b1c35e4da6a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
local class = require"common.class"
local CHUNK_SIZE = require"common.constants".CHUNK_SIZE

-- Hex: q,r,s. invariant that q+r+s=0
-- add, subtract
-- constructor takes 3 positions and rounds to closest hex centre.

-- round to nearest int, rounding .5 away from 0.
local function round(x)
	if x<0 then
		return math.ceil(x-0.5)
	else
		return math.floor(x+0.5)
	end
end

local Pos, Hex, ChunkPos

local SR3 = math.sqrt(3)

Hex = class()
function Hex.new(cls)
	return setmetatable({},cls)
end
function Hex.init(self,q,r,s)
	s=s or -q-r
	assert(q+r+s==0,"hex coord doesn't meet invariant")
	self.q=q
	self.r=r
	self.s=s
	return self
end
function Hex.make(cls,...)
	return cls:new():init(...)
end
function Hex.round(self)
	-- return a new Hex rounded to integer coordinates
	local fq,fr,fs = self.q,self.r,self.s
	-- round all to nearest integer
	-- find which was changed the most, reset that one to be coherent with the other two.
	local abs = math.abs

	local rq,rr,rs = round(fq),round(fr),round(fs)
	local dq,dr,ds = abs(fq-rq), abs(fr-rr), abs(fs-rs)

	if dq>dr and dq>ds then
		rq = -rr-rs
	elseif dr>ds then
		rr = -rq-rs
	else
		rs = -rq-rr
	end

	return Hex:make(rq,rr,rs)
end

function Hex.to_pos(self,into)
	into = into or Pos:new()
	local x = self.q*SR3 + self.r*(SR3/2)
	local y = self.r*(3/2)
	return into:init(x,y)
end

function Hex.containing_chunk(self)
	local u = math.floor(self.q/CHUNK_SIZE)
	local v = math.floor(self.r/CHUNK_SIZE)
	return ChunkPos:make(u,v)
end
function Hex.chunk_and_offset(self)
	local cp = self:containing_chunk()
	local tl = cp:extents()
	return cp, (self-tl)
end
function Hex.offset_in_chunk(self)
	local cp,offs = self:chunk_and_offset()
	return offs
end
function Hex.iter_neighbours(self,radius)
	assert(radius > 0,"radius must be at least 1")
	return coroutine.wrap(function()
		for q = -radius,radius do
			for r = math.max(-radius,-q-radius), math.min(radius,-q+radius) do
				coroutine.yield(self+Hex:make(q,r))
			end
		end
	end)
end
function Hex.__add(self,other) return Hex:make(self.q+other.q, self.r+other.r, self.s+other.s) end
function Hex.__sub(self,other) return Hex:make(self.q-other.q, self.r-other.r, self.s-other.s) end
function Hex.__tostring(self) return string.format("H(%.2f,%.2f)",self.q,self.r) end
function Hex.__eq(a,b) return a.q==b.q and a.r==b.r end

Pos = class()
function Pos.new(cls)
	return setmetatable({},cls)
end
function Pos.init(self,x,y)
	self.x = x
	self.y = y
	return self
end
function Pos.make(cls,...)
	return cls:new():init(...)
end
function Pos.__add(self,other) return Pos:make(self.x+other.x,self.y+other.y) end
function Pos.__sub(self,other) return Pos:make(self.x-other.x,self.y-other.y) end
function Pos.__mul(a,b)
	if type(a) == "number" then
		return Pos:make(a*b.x,a*b.y)
	elseif type(b) == "number" then
		return Pos:make(a.x*b,a.y*b)
	else
		error("can only multiply Pos by scalar")
	end
end
function Pos.__div(a,b)
	assert(type(b) == "number","can only divide Pos by scalar, and can't divide scalar by Pos")
	return a*(1/b)
end
function Pos.__eq(a,b) return a.x==b.x and a.y==b.y end
function Pos.lensq(self) return self.x^2 + self.y^2 end
function Pos.len(self) return math.sqrt(self:lensq()) end
function Pos.norm(self) return self/self:len() end
function Pos.dot(self,other) return self.x*other.x + self.y*other.y end
function Pos.to_hex(self,into)
	into = into or Hex:new()
	local q = self.x*(SR3/3) - self.y*(1/3)
	local r = (2/3)*self.y
	return into:init(q,r,-q-r)
end
function Pos.__tostring(self) return string.format("(%.2f,%.2f)",self.x,self.y) end

-- represents coordinates of a chunk
-- ie pair of integers. the chunk at spawn is C(0,0), the one to the right of that is C(1,0), etc
ChunkPos = class()
function ChunkPos.make(cls,u,v)
	return setmetatable({u=u,v=v},cls)
end
function ChunkPos.__add(self,other) return ChunkPos:make(self.u+other.u,self.v+other.v) end
function ChunkPos.__sub(self,other) return ChunkPos:make(self.u-other.u,self.v-other.v) end
function ChunkPos.__tostring(self) return string.format("C(%d,%d)",self.u,self.v) end
function ChunkPos.__eq(a,b) return a.u==b.u and a.v==b.v end
function ChunkPos.extents(self)
	-- returns Hex of topleft and bottomright
	local tlq,tlr = self.u*CHUNK_SIZE, self.v*CHUNK_SIZE
	local brq,brr = (self.u+1)*CHUNK_SIZE -1, (self.v+1)*CHUNK_SIZE -1
	return Hex:make(tlq,tlr), Hex:make(brq,brr)
end
function ChunkPos.neighborhood(self)
	-- return all chunkposes within the 3x3 square centered on self, including self
	local out = {}
	for du=-1,1 do
		for dv = -1,1 do
			table.insert(out,ChunkPos:make(du,dv) + self)
		end
	end
	return out
end
function ChunkPos.orth_dist(self,other)
	local du = math.abs(self.u-other.u)
	local dv = math.abs(self.v-other.v)
	return math.max(du,dv)
end
function ChunkPos.filename(self)
	-- filename of chunk with that cp
	return "world/c_"..self.u.."_"..self.v..".dat"
end




return {Hex=Hex,Pos=Pos,ChunkPos=ChunkPos}