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}
|