-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbullider.lua
158 lines (137 loc) · 4.67 KB
/
bullider.lua
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
local bullider = {}
bullider.midphase = true
bullider.continuous = true
local colliders = {}
local maxColliders = 0
local maxInUseIndex = 0
function bullider.init(maxColliders_)
maxColliders = maxColliders_
for i = 1, maxColliders do
colliders[i] = {
index = i,
inUse = false,
x = 0, y = 0,
lastX = 0, lastY = 0,
radius = 0,
group = nil,
sweptBoundsX = 0,
sweptBoundsY = 0,
sweptBoundsRadius = 0,
}
end
end
function bullider.spawn(x, y, radius, group) --> table (collider)
if maxInUseIndex == maxColliders then
error("Too many colliders", 2)
end
local index = maxInUseIndex + 1
maxInUseIndex = index
-- We need to make sure to overwrite every field
local collider = colliders[index]
collider.index = index
collider.inUse = true
collider.x = assert(x)
collider.y = assert(y)
collider.lastX = collider.x
collider.lastY = collider.y
collider.radius = assert(radius)
collider.group = assert(group)
collider.sweptBoundsX = collider.x
collider.sweptBoundsY = collider.y
collider.sweptBoundsRadius = collider.radius
return collider
end
function bullider.despawn(collider)
assert(collider and collider.inUse)
collider.inUse = false
-- swap collider to end and pop
colliders[collider.index], colliders[maxInUseIndex] =
colliders[maxInUseIndex], colliders[collider.index]
maxInUseIndex = maxInUseIndex - 1
-- fix the swapped in collider
colliders[collider.index].index = collider.index
end
function bullider.update(collider, x, y)
assert(collider and collider.inUse)
collider.lastX = collider.x
collider.lastY = collider.y
collider.x = x
collider.y = y
if bullider.continuous and bullider.midphase then
collider.sweptBoundsX = (collider.lastX + collider.x) / 2
collider.sweptBoundsY = (collider.lastY + collider.y) / 2
local relX = collider.x - collider.lastX
local relY = collider.y - collider.lastY
local relLen = math.sqrt(relX*relX + relY*relY)
collider.sweptBoundsRadius = relLen / 2.0 + collider.radius
end
end
local function circleCircle(x1, y1, r1, x2, y2, r2)
local relx, rely = x1 - x2, y1 - y2
local rsum = r1 + r2
return relx*relx + rely*rely <= rsum*rsum
end
local function clamp(x, lo, hi)
return math.max(lo, math.min(hi, x))
end
local function checkSweptCollision(collider, other) --> boolean
-- We move to the reference frame of `collider` and then determine the
-- collision between a static circle at 0, 0 (`collider`)
-- and a swept circle (`other`) from
local sX = other.lastX - collider.lastX -- sweep start
local sY = other.lastY - collider.lastY
-- to
local eX = other.x - collider.x -- sweep end
local eY = other.y - collider.y
local rX = eX - sX -- relative sweep vector
local rY = eY - sY
-- project collider position (0, 0) onto relative sweep vector:
-- dot(0 - s, r) / dot(r, r) = -dot(s, r) / dot(r, r)
local t = clamp(-(sX*rX + sY*rY) / (rX*rX + rY*rY), 0, 1)
local cX = sX + t * rX -- closest point on line segment of sweep
local cY = sY + t * rY
local rsum = collider.radius + other.radius
return cX*cX + cY*cY <= rsum*rsum
end
local function checkCollision(collider, other) --> boolean
if bullider.continuous then
if bullider.midphase then
if not circleCircle(collider.x, collider.y, collider.radius,
other.sweptBoundsX, other.sweptBoundsY,
other.sweptBoundsRadius) then
return false
end
end
return checkSweptCollision(collider, other)
else
return circleCircle(collider.x, collider.y, collider.radius,
other.x, other.y, other.radius)
end
end
local collisions = {}
local groupMatch = {}
function bullider.getCollisions(collider, ...) --> list { collider1, collider2, ... }
assert(collider and collider.inUse)
for k, _ in pairs(groupMatch) do
groupMatch[k] = nil
end
for i = 1, select("#", ...) do
local group = select(i, ...)
groupMatch[group] = true
end
local numCollisions = 0
for i = 1, maxInUseIndex do
local other = colliders[i]
if groupMatch[other.group] then
if checkCollision(collider, other) then
collisions[numCollisions + 1] = other
numCollisions = numCollisions + 1
end
end
end
for i = numCollisions + 1, #collisions do
collisions[i] = nil
end
return collisions
end
return bullider