-
Notifications
You must be signed in to change notification settings - Fork 0
/
physics.lua
343 lines (293 loc) · 8.92 KB
/
physics.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
local physics = {}
local ax, bx, pad_x, sign_x, ay, by, pad_y, sign_y
local near_time_x, far_time_x, near_time_y, far_time_y, far_time
local hit_time, hit_x, hit_y, nx, ny
--- test if moving a by (vx,vy) will cause it to hit b
-- if so, return x,y (point of impact), 0 <= t <= 1 ("time" of impact), nx,ny (normal of the surface we ran into)
function physics.collision_aabb_sweep(a, b, vx, vy)
ax, bx = a.x, b.x
pad_x = b.half_w + a.half_w
sign_x = mymath.sign(vx)
ay, by = a.y, b.y
pad_y = b.half_h + a.half_h
sign_y = mymath.sign(vy)
if vx ~= 0 then
scale_x = 1 / vx
near_time_x = (bx - sign_x * (pad_x) - ax) * scale_x
far_time_x = (bx + sign_x * (pad_x) - ax) * scale_x
else
if ax > bx - pad_x and ax < bx + pad_x then
near_time_x, far_time_x = -9999, 9999
else
return
end
end
if vy ~= 0 then
scale_y = 1 / vy
near_time_y = (by - sign_y * (pad_y) - ay) * scale_y
far_time_y = (by + sign_y * (pad_y) - ay) * scale_y
else
if ay > by - pad_y and ay < by + pad_y then
near_time_y, far_time_y = -9999, 9999
else
return
end
end
if near_time_x > far_time_y or near_time_y > far_time_x then
-- missed
return
end
-- pick the times we were closest
near_time = math.max(near_time_x, near_time_y)
far_time = math.min(far_time_x, far_time_y)
if near_time > 1 or far_time < 0 then
-- didn't reach b, or already past and moving away
return
end
-- okay, we hit the aabb
hit_time = mymath.clamp(0, near_time, 1)
if near_time_x > near_time_y then
nx = -sign_x
ny = 0
else
nx = 0
ny = -sign_y
end
hit_x = a.x + hit_time * vx - 0.001 * sign_x
hit_y = a.y + hit_time * vy - 0.001 * sign_y
return hit_x, hit_y, hit_time, nx, ny
end
local rx, ry, norm
function physics.collision_aabb_sweep_slope(a, b, vx, vy, slope, slope_y_offset, bhm)
ax, bx = a.x, b.x
sign_x = mymath.sign(vx)
scale_x = 1 / vx
ay, by = a.y, b.y
sign_y = mymath.sign(vy)
scale_y = 1 / vy
if vx ~= 0 then
if sign_x == 1 then
near_time_x = (bx - ax - b.half_w * bhm.l - a.half_w) * scale_x
far_time_x = (bx - ax + b.half_w * bhm.r + a.half_w) * scale_x
else
near_time_x = (bx - ax + b.half_w * bhm.r + a.half_w) * scale_x
far_time_x = (bx - ax - b.half_w * bhm.l - a.half_w) * scale_x
end
else
if ax > bx - b.half_w * bhm.l - a.half_w and ax < bx + b.half_w * bhm.r + a.half_w then
near_time_x, far_time_x = -9999, 9999
else
return
end
end
if vy ~= 0 then
if sign_y == 1 then
near_time_y = (by - ay - b.half_h * bhm.u - a.half_h) * scale_y
far_time_y = (by - ay + b.half_h * bhm.d + a.half_h) * scale_y
else
near_time_y = (by - ay + b.half_h * bhm.d + a.half_h) * scale_y
far_time_y = (by - ay - b.half_h * bhm.u - a.half_h) * scale_y
end
else
if ay > by - b.half_h * bhm.u - a.half_h and ay < by + b.half_h * bhm.d + a.half_h then
near_time_y, far_time_y = -9999, 9999
else
return
end
end
if near_time_x > far_time_y or near_time_y > far_time_x then
-- missed the whole box
return
end
-- ugh, now deal with the slanted edge
-- coords of the relevant corner of a; currently this is always a bottom corner
rx = ax - a.half_w * mymath.sign(slope)
ry = ay + a.half_h
if vx ~= 0 then
-- find the x distance traveled; divide by vx to find near_time_q
vslope = vy / vx
-- x coord of the contact point is (1/(slope - vslope))(slope * bx - by - vslope * rx + ry)
near_time_q = ((slope * bx - by - slope_y_offset - vslope * rx + ry) / (slope - vslope) - rx) * scale_x
else
-- x is fixed: find the y distance and divide by vy to find near_time_q
-- y coord of the contact point is (slope * (rx - bx) + by)
near_time_q = (slope * (rx - bx) + by + slope_y_offset - ry) * scale_y
end
if ry > slope * (rx - bx) + by + slope_y_offset then
if near_time_q < 0 then
-- below and moving away
far_time_q = 9999
else
-- below, but moving out of the slope
far_time_q = near_time_q
near_time_q = -9999
end
else
if near_time_q < 0 then
-- above, but moving away
return
else
-- above, and moving towards
far_time_q = 9999
end
end
if near_time_q > far_time_x or near_time_q > far_time_y or near_time_x > far_time_q or near_time_y > far_time_q then
-- missed again, rip
return
end
-- pick the times we were closest
near_time = math.max(near_time_x, near_time_y, near_time_q)
far_time = math.min(far_time_x, far_time_y, far_time_q)
if near_time > 1 or far_time < 0 then
-- didn't reach b, or already past and moving away
return
end
-- okay?????
hit_time = mymath.clamp(0, near_time, 1)
if near_time_q > near_time_x and near_time_q > near_time_y then
-- normal to the slope
norm = math.sqrt(math.pow(slope, 2) + 1)
nx = slope / norm
ny = - 1 / norm
hit_x = a.x + hit_time * vx - 0.001 * sign_x
hit_y = a.y + hit_time * vy - 0.001 * sign_y
else
if near_time_x > near_time_y then
nx = -sign_x
ny = 0
else
nx = 0
ny = -sign_y
end
hit_x = a.x + hit_time * vx - 0.001 * sign_x
hit_y = a.y + hit_time * vy - 0.001 * sign_y
end
return hit_x, hit_y, hit_time, nx, ny
end
-------------
local grid_x1, grid_x2, grid_y1, grid_y2
local block_type
local box
local hit
local mx, my, mt, mnx, mny
function physics.map_collision_aabb_sweep(a, vx, vy)
grid_x1 = map.grid_at_pos(math.min(a.x - a.half_w, a.x - a.half_w + vx))
grid_x2 = map.grid_at_pos(math.max(a.x + a.half_w, a.x + a.half_w + vx))
grid_y1 = map.grid_at_pos(math.min(a.y - a.half_h, a.y - a.half_h + vy))
grid_y2 = map.grid_at_pos(math.max(a.y + a.half_h, a.y + a.half_h + vy))
mt = 1
hit = nil
for i = grid_x1, grid_x2 do
for j = grid_y1, grid_y2 do
if mainmap:grid_has_collision(i, j) then
block_type = mainmap:block_at(i, j)
box = map.bounding_box(i, j)
if block_data[block_type].slope and
not (mainmap:grid_has_collision(i, j-1) and (block_type == "slope_23_b" or block_type == "slope_-23_b"))then
hx, hy, ht, nx, ny = physics.collision_aabb_sweep_slope(
a, box, vx, vy,
block_data[block_type].slope, block_data[block_type].slope_y_offset,
block_data.get_box_half_multipliers(block_type))
else
hx, hy, ht, nx, ny = physics.collision_aabb_sweep(a, box, vx, vy)
end
if ht and ht < mt then
if (nx ~= 0 and ny ~= 0) or not mainmap:grid_blocks_dir(i + nx, j + ny, map.orth_normal_to_dir(-nx, -ny)) then
hit = {"block", i, j}
mt = ht
mx = hit_x
my = hit_y
mnx = nx
mny = ny
if nx > 0 then
mx = math.ceil(mx)
elseif nx < 0 then
mx = math.floor(mx)
end
if ny > 0 then
my = math.ceil(my)
elseif ny < 0 then
my = math.floor(my)
end
end
end
end
end
end
if not hit then
mx, my = a.x + vx, a.y + vy
mnx, mny = 0, 0
end
return hit, mx, my, mt, mnx, mny
end
local vx, vy
local ijhx, ijhy, ijht, ijnx, ijny
local rt, rvx, rvy
function physics.map_collision_test(a)
vx = mouse.x + camera.x - a.x
vy = mouse.y + camera.y - a.y
grid_x1 = map.grid_at_pos(math.min(a.x - a.half_w, a.x - a.half_w + vx))
grid_x2 = map.grid_at_pos(math.max(a.x + a.half_w, a.x + a.half_w + vx))
grid_y1 = map.grid_at_pos(math.min(a.y - a.half_h, a.y - a.half_h + vy))
grid_y2 = map.grid_at_pos(math.max(a.y + a.half_h, a.y + a.half_h + vy))
mx, my, mt, mnx, mny = a.x + vx, a.y + vy, 1, 0, 0
for i = grid_x1, grid_x2 do
for j = grid_y1, grid_y2 do
if mainmap:grid_has_collision(i, j) then
block_type = mainmap:block_at(i, j)
box = map.bounding_box(i, j)
if block_data[block_type].slope then
ijhx, ijhy, ijht, ijnx, ijny = physics.collision_aabb_sweep_slope(
a, box, vx, vy,
block_data[block_type].slope, block_data[block_type].slope_y_offset,
block_data.get_box_half_multipliers(block_type))
else
ijhx, ijhy, ijht, ijnx, ijny = physics.collision_aabb_sweep(a, box, vx, vy)
end
if ijht and ijht < mt then
mt = hit_time
mx = ijhx
my = ijhy
mnx = nx
mny = ny
if mnx > 0 then
mx = math.ceil(mx)
elseif nx < 0 then
mx = math.floor(mx)
end
if mny > 0 then
my = math.ceil(my)
elseif ny < 0 then
my = math.floor(my)
end
end
end
end
end
if mt < 1 then
rt = 1 - mt
-- px = ny
-- py = -nx
-- wx = vx * rem_time
-- wy = vy * rem_time
-- (vx rt, vy rt) dot (ny, -nx)
r = vx * rt * mny - vy * rt * mnx
rvx = r * mny
rvy = r * (-mnx)
end
-- draw debug tracer
if mt == 1 then
love.graphics.setColor(100, 200, 150)
else
love.graphics.setColor(200, 200, 50)
end
love.graphics.line(a.x - camera.x, a.y - camera.y, mx - camera.x, my - camera.y)
love.graphics.rectangle('line', mx - a.half_w - camera.x, my - a.half_h - camera.y, a.half_w * 2, a.half_h * 2)
love.graphics.setColor(color.white)
-- love.graphics.rectangle('line', a.x - camera.x, a.y - camera.y, a.w, a.h)
if mt < 1 then
love.graphics.line(mx - camera.x, my - camera.y, mx - camera.x + mnx * 8, my - camera.y + mny * 8)
love.graphics.line(mx - camera.x, my - camera.y, mx - camera.x + rvx, my - camera.y + rvy)
end
end
return physics