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
|
local Pos = require"common.coords".Pos
local function sign(x)
if x == 0 then return 0
elseif x < 0 then return -1
elseif x > 0 then return 1
end
end
local function clamp(x,minv,maxv)
return math.min(math.max(x,minv),maxv)
end
-- https://iquilezles.org/articles/distgradfunctions2d/
local function iqz_hex_sdgf(px,py, r)
local kx,ky,kz = -0.866025404,0.5,0.577350269
local sx,sy = sign(px),sign(py)
px = math.abs(px)
py = math.abs(py)
local w = kx*px+ky*py
local _1 = 2*math.min(w,0)
px = px - _1*kx
py = py - _1*ky
px = px - clamp(px,-kz*r,kz*r)
py = py - r
d = math.sqrt(px*px+py*py)*sign(py)
local gx,gy
if w<0 then
gx = -ky*px - kx*py
gy = -kx*px + ky*py
else
gx,gy = px,py
end
-- dist, gradx, grady
return d, sx*gx/d, sy*gy/d
end
-- rotate by 30 degrees
local c30,s30 = math.cos(math.rad(30)), math.sin(math.rad(30))
local function t_in(x,y) return c30*x-s30*y, s30*x+c30*y end
local function t_out(x,y) return c30*x+s30*y, -s30*x+c30*y end
-- above sdf has flat top not pointy-top hexagons
-- so need to rotate
-- also, 'r' parameter in the above seems to be distance to side
-- but in our unit system, 1 unit is distance to vertex
-- conversion factor between these is also cos(30deg)
-- this is distance to hex at 0,0
local function hex_sdgf_00(pos)
local tpx,tpy = t_in(pos.x,pos.y)
local d,gx,gy = iqz_hex_sdgf(tpx,tpy,c30)
return d, t_out(gx,gy)
end
local function hex_sdgf(pos, hex)
return hex_sdgf_00(pos-hex:to_pos())
end
local PLAYER_SIZE = 0.35
local function collide_with_terrain(old_pos, try_pos, map, tries_remaining)
tries_remaining = tries_remaining or 3
if tries_remaining <= 0 then return old_pos end
local try_h = try_pos:to_hex():round()
for near_h in try_h:iter_neighbours(1) do
local tile = map:at(near_h)
if tile ~= 0 then
local d,gx,gy = hex_sdgf(try_pos, near_h)
if d < PLAYER_SIZE then
local push_dist = PLAYER_SIZE - d
local push_dir = Pos:make(gx,gy)
local new_try_pos = try_pos + (push_dist*push_dir)
return collide_with_terrain(old_pos,new_try_pos,map,tries_remaining-1)
end
end
end
return try_pos
end
return {
collide_with_terrain = collide_with_terrain,
hex_sdgf=hex_sdgf
}
|