• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

excessive / cpml / 6132588926

09 Sep 2023 06:38PM UTC coverage: 14.013% (-44.7%) from 58.701%
6132588926

push

github

FatalError42O
fixed Busted support (hopefully)

975 of 6958 relevant lines covered (14.01%)

10.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.27
/modules/intersect.lua
1
--- Various geometric intersections
2
-- @module intersect
3

4
local constants   = require(modules .. "constants")
3✔
5
local mat4        = require(modules .. "mat4")
×
6
local vec3        = require(modules .. "vec3")
×
7
local utils       = require(modules .. "utils")
×
8
local DBL_EPSILON = constants.DBL_EPSILON
×
9
local sqrt        = math.sqrt
×
10
local abs         = math.abs
×
11
local min         = math.min
×
12
local max         = math.max
×
13
local intersect   = {}
×
14

15
-- https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/
16
-- point       is a vec3
17
-- triangle[1] is a vec3
18
-- triangle[2] is a vec3
19
-- triangle[3] is a vec3
20
function intersect.point_triangle(point, triangle)
×
21
        local u = triangle[2] - triangle[1]
×
22
        local v = triangle[3] - triangle[1]
×
23
        local w = point       - triangle[1]
×
24

25
        local vw = v:cross(w)
×
26
        local vu = v:cross(u)
×
27

28
        if vw:dot(vu) < 0 then
×
29
                return false
×
30
        end
31

32
        local uw = u:cross(w)
×
33
        local uv = u:cross(v)
×
34

35
        if uw:dot(uv) < 0 then
×
36
                return false
×
37
        end
38

39
        local d = uv:len()
×
40
        local r = vw:len() / d
×
41
        local t = uw:len() / d
×
42

43
        return r + t <= 1
×
44
end
45

46
-- point    is a vec3
47
-- aabb.min is a vec3
48
-- aabb.max is a vec3
49
function intersect.point_aabb(point, aabb)
×
50
        return
×
51
                aabb.min.x <= point.x and
×
52
                aabb.max.x >= point.x and
×
53
                aabb.min.y <= point.y and
×
54
                aabb.max.y >= point.y and
×
55
                aabb.min.z <= point.z and
×
56
                aabb.max.z >= point.z
×
57
end
58

59
-- point          is a vec3
60
-- frustum.left   is a plane { a, b, c, d }
61
-- frustum.right  is a plane { a, b, c, d }
62
-- frustum.bottom is a plane { a, b, c, d }
63
-- frustum.top    is a plane { a, b, c, d }
64
-- frustum.near   is a plane { a, b, c, d }
65
-- frustum.far    is a plane { a, b, c, d }
66
function intersect.point_frustum(point, frustum)
×
67
        local x, y, z = point:unpack()
×
68
        local planes  = {
×
69
                frustum.left,
×
70
                frustum.right,
×
71
                frustum.bottom,
×
72
                frustum.top,
×
73
                frustum.near,
×
74
                frustum.far or false
×
75
        }
76

77
        -- Skip the last test for infinite projections, it'll never fail.
78
        if not planes[6] then
×
79
                table.remove(planes)
×
80
        end
81

82
        local dot
83
        for i = 1, #planes do
×
84
                dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
×
85
                if dot <= 0 then
×
86
                        return false
×
87
                end
88
        end
89

90
        return true
×
91
end
92

93
-- http://www.lighthouse3d.com/tutorials/maths/ray-triangle-intersection/
94
-- ray.position  is a vec3
95
-- ray.direction is a vec3
96
-- triangle[1]   is a vec3
97
-- triangle[2]   is a vec3
98
-- triangle[3]   is a vec3
99
-- backface_cull is a boolean (optional)
100
function intersect.ray_triangle(ray, triangle, backface_cull)
×
101
        local e1 = triangle[2] - triangle[1]
×
102
        local e2 = triangle[3] - triangle[1]
×
103
        local h  = ray.direction:cross(e2)
×
104
        local a  = h:dot(e1)
×
105

106
        -- if a is negative, ray hits the backface
107
        if backface_cull and a < 0 then
×
108
                return false
×
109
        end
110

111
        -- if a is too close to 0, ray does not intersect triangle
112
        if abs(a) <= DBL_EPSILON then
×
113
                return false
×
114
        end
115

116
        local f = 1 / a
×
117
        local s = ray.position - triangle[1]
×
118
        local u = s:dot(h) * f
×
119

120
        -- ray does not intersect triangle
121
        if u < 0 or u > 1 then
×
122
                return false
×
123
        end
124

125
        local q = s:cross(e1)
×
126
        local v = ray.direction:dot(q) * f
×
127

128
        -- ray does not intersect triangle
129
        if v < 0 or u + v > 1 then
×
130
                return false
×
131
        end
132

133
        -- at this stage we can compute t to find out where
134
        -- the intersection point is on the line
135
        local t = q:dot(e2) * f
×
136

137
        -- return position of intersection and distance from ray origin
138
        if t >= DBL_EPSILON then
×
139
                return ray.position + ray.direction * t, t
×
140
        end
141

142
        -- ray does not intersect triangle
143
        return false
×
144
end
145

146
-- https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code
147
-- ray.position    is a vec3
148
-- ray.direction   is a vec3
149
-- sphere.position is a vec3
150
-- sphere.radius   is a number
151
function intersect.ray_sphere(ray, sphere)
×
152
        local offset = ray.position - sphere.position
×
153
        local b = offset:dot(ray.direction)
×
154
        local c = offset:dot(offset) - sphere.radius * sphere.radius
×
155

156
        -- ray's position outside sphere (c > 0)
157
        -- ray's direction pointing away from sphere (b > 0)
158
        if c > 0 and b > 0 then
×
159
                return false
×
160
        end
161

162
        local discr = b * b - c
×
163

164
        -- negative discriminant
165
        if discr < 0 then
×
166
                return false
×
167
        end
168

169
        -- Clamp t to 0
170
        local t = -b - sqrt(discr)
×
171
        t = t < 0 and 0 or t
×
172

173
        -- Return collision point and distance from ray origin
174
        return ray.position + ray.direction * t, t
×
175
end
176

177
-- http://gamedev.stackexchange.com/a/18459
178
-- ray.position  is a vec3
179
-- ray.direction is a vec3
180
-- aabb.min      is a vec3
181
-- aabb.max      is a vec3
182
function intersect.ray_aabb(ray, aabb)
×
183
        local dir     = ray.direction:normalize()
×
184
        local dirfrac = vec3(
×
185
                1 / dir.x,
×
186
                1 / dir.y,
×
187
                1 / dir.z
×
188
        )
189

190
        local t1 = (aabb.min.x - ray.position.x) * dirfrac.x
×
191
        local t2 = (aabb.max.x - ray.position.x) * dirfrac.x
×
192
        local t3 = (aabb.min.y - ray.position.y) * dirfrac.y
×
193
        local t4 = (aabb.max.y - ray.position.y) * dirfrac.y
×
194
        local t5 = (aabb.min.z - ray.position.z) * dirfrac.z
×
195
        local t6 = (aabb.max.z - ray.position.z) * dirfrac.z
×
196

197
        local tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
×
198
        local tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))
×
199

200
        -- ray is intersecting AABB, but whole AABB is behind us
201
        if tmax < 0 then
×
202
                return false
×
203
        end
204

205
        -- ray does not intersect AABB
206
        if tmin > tmax then
×
207
                return false
×
208
        end
209

210
        -- Return collision point and distance from ray origin
211
        return ray.position + ray.direction * tmin, tmin
×
212
end
213

214
-- http://stackoverflow.com/a/23976134/1190664
215
-- ray.position   is a vec3
216
-- ray.direction  is a vec3
217
-- plane.position is a vec3
218
-- plane.normal   is a vec3
219
function intersect.ray_plane(ray, plane)
×
220
        local denom = plane.normal:dot(ray.direction)
×
221

222
        -- ray does not intersect plane
223
        if abs(denom) < DBL_EPSILON then
×
224
                return false
×
225
        end
226

227
        -- distance of direction
228
        local d = plane.position - ray.position
×
229
        local t = d:dot(plane.normal) / denom
×
230

231
        if t < DBL_EPSILON then
×
232
                return false
×
233
        end
234

235
        -- Return collision point and distance from ray origin
236
        return ray.position + ray.direction * t, t
×
237
end
238

239
function intersect.ray_capsule(ray, capsule)
×
240
        local dist2, p1, p2 = intersect.closest_point_segment_segment(
×
241
                ray.position,
×
242
                ray.position + ray.direction * 1e10,
×
243
                capsule.a,
×
244
                capsule.b
245
        )
246
        if dist2 <= capsule.radius^2 then
×
247
                return p1
×
248
        end
249

250
        return false
×
251
end
252

253
-- https://web.archive.org/web/20120414063459/http://local.wasp.uwa.edu.au/~pbourke//geometry/lineline3d/
254
-- a[1] is a vec3
255
-- a[2] is a vec3
256
-- b[1] is a vec3
257
-- b[2] is a vec3
258
-- e    is a number
259
function intersect.line_line(a, b, e)
×
260
        -- new points
261
        local p13 = a[1] - b[1]
×
262
        local p43 = b[2] - b[1]
×
263
        local p21 = a[2] - a[1]
×
264

265
        -- if lengths are negative or too close to 0, lines do not intersect
266
        if p43:len2() < DBL_EPSILON or p21:len2() < DBL_EPSILON then
×
267
                return false
×
268
        end
269

270
        -- dot products
271
        local d1343 = p13:dot(p43)
×
272
        local d4321 = p43:dot(p21)
×
273
        local d1321 = p13:dot(p21)
×
274
        local d4343 = p43:dot(p43)
×
275
        local d2121 = p21:dot(p21)
×
276
        local denom = d2121 * d4343 - d4321 * d4321
×
277

278
        -- if denom is too close to 0, lines do not intersect
279
        if abs(denom) < DBL_EPSILON then
×
280
                return false
×
281
        end
282

283
        local numer = d1343 * d4321 - d1321 * d4343
×
284
        local mua   = numer / denom
×
285
        local mub   = (d1343 + d4321 * mua) / d4343
×
286

287
        -- return positions of intersection on each line
288
        local out1 = a[1] + p21 * mua
×
289
        local out2 = b[1] + p43 * mub
×
290
        local dist = out1:dist(out2)
×
291

292
        -- if distance of the shortest segment between lines is less than threshold
293
        if e and dist > e then
×
294
                return false
×
295
        end
296

297
        return { out1, out2 }, dist
×
298
end
299

300
-- a[1] is a vec3
301
-- a[2] is a vec3
302
-- b[1] is a vec3
303
-- b[2] is a vec3
304
-- e    is a number
305
function intersect.segment_segment(a, b, e)
×
306
        local c, d = intersect.line_line(a, b, e)
×
307

308
        if c and ((
×
309
                a[1].x <= c[1].x and
×
310
                a[1].y <= c[1].y and
×
311
                a[1].z <= c[1].z and
×
312
                c[1].x <= a[2].x and
×
313
                c[1].y <= a[2].y and
×
314
                c[1].z <= a[2].z
×
315
        ) or (
×
316
                a[1].x >= c[1].x and
×
317
                a[1].y >= c[1].y and
×
318
                a[1].z >= c[1].z and
×
319
                c[1].x >= a[2].x and
×
320
                c[1].y >= a[2].y and
×
321
                c[1].z >= a[2].z
×
322
        )) and ((
×
323
                b[1].x <= c[2].x and
×
324
                b[1].y <= c[2].y and
×
325
                b[1].z <= c[2].z and
×
326
                c[2].x <= b[2].x and
×
327
                c[2].y <= b[2].y and
×
328
                c[2].z <= b[2].z
×
329
        ) or (
×
330
                b[1].x >= c[2].x and
×
331
                b[1].y >= c[2].y and
×
332
                b[1].z >= c[2].z and
×
333
                c[2].x >= b[2].x and
×
334
                c[2].y >= b[2].y and
×
335
                c[2].z >= b[2].z
×
336
        )) then
×
337
                return c, d
×
338
        end
339

340
        -- segments do not intersect
341
        return false
×
342
end
343

344
-- a.min is a vec3
345
-- a.max is a vec3
346
-- b.min is a vec3
347
-- b.max is a vec3
348
function intersect.aabb_aabb(a, b)
×
349
        return
×
350
                a.min.x <= b.max.x and
×
351
                a.max.x >= b.min.x and
×
352
                a.min.y <= b.max.y and
×
353
                a.max.y >= b.min.y and
×
354
                a.min.z <= b.max.z and
×
355
                a.max.z >= b.min.z
×
356
end
357

358
-- aabb.position is a vec3
359
-- aabb.extent   is a vec3 (half-size)
360
-- obb.position  is a vec3
361
-- obb.extent    is a vec3 (half-size)
362
-- obb.rotation  is a mat4
363
function intersect.aabb_obb(aabb, obb)
×
364
        local a   = aabb.extent
×
365
        local b   = obb.extent
×
366
        local T   = obb.position - aabb.position
×
367
        local rot = mat4():transpose(obb.rotation)
×
368
        local B   = {}
×
369
        local t
370

371
        for i = 1, 3 do
×
372
                B[i] = {}
×
373
                for j = 1, 3 do
×
374
                        assert((i - 1) * 4 + j < 16 and (i - 1) * 4 + j > 0)
×
375
                        B[i][j] = abs(rot[(i - 1) * 4 + j]) + 1e-6
×
376
                end
377
        end
378

379
        t = abs(T.x)
×
380
        if not (t <= (b.x + a.x * B[1][1] + b.y * B[1][2] + b.z * B[1][3])) then return false end
×
381
        t = abs(T.x * B[1][1] + T.y * B[2][1] + T.z * B[3][1])
×
382
        if not (t <= (b.x + a.x * B[1][1] + a.y * B[2][1] + a.z * B[3][1])) then return false end
×
383
        t = abs(T.y)
×
384
        if not (t <= (a.y + b.x * B[2][1] + b.y * B[2][2] + b.z * B[2][3])) then return false end
×
385
        t = abs(T.z)
×
386
        if not (t <= (a.z + b.x * B[3][1] + b.y * B[3][2] + b.z * B[3][3])) then return false end
×
387
        t = abs(T.x * B[1][2] + T.y * B[2][2] + T.z * B[3][2])
×
388
        if not (t <= (b.y + a.x * B[1][2] + a.y * B[2][2] + a.z * B[3][2])) then return false end
×
389
        t = abs(T.x * B[1][3] + T.y * B[2][3] + T.z * B[3][3])
×
390
        if not (t <= (b.z + a.x * B[1][3] + a.y * B[2][3] + a.z * B[3][3])) then return false end
×
391
        t = abs(T.z * B[2][1] - T.y * B[3][1])
×
392
        if not (t <= (a.y * B[3][1] + a.z * B[2][1] + b.y * B[1][3] + b.z * B[1][2])) then return false end
×
393
        t = abs(T.z * B[2][2] - T.y * B[3][2])
×
394
        if not (t <= (a.y * B[3][2] + a.z * B[2][2] + b.x * B[1][3] + b.z * B[1][1])) then return false end
×
395
        t = abs(T.z * B[2][3] - T.y * B[3][3])
×
396
        if not (t <= (a.y * B[3][3] + a.z * B[2][3] + b.x * B[1][2] + b.y * B[1][1])) then return false end
×
397
        t = abs(T.x * B[3][1] - T.z * B[1][1])
×
398
        if not (t <= (a.x * B[3][1] + a.z * B[1][1] + b.y * B[2][3] + b.z * B[2][2])) then return false end
×
399
        t = abs(T.x * B[3][2] - T.z * B[1][2])
×
400
        if not (t <= (a.x * B[3][2] + a.z * B[1][2] + b.x * B[2][3] + b.z * B[2][1])) then return false end
×
401
        t = abs(T.x * B[3][3] - T.z * B[1][3])
×
402
        if not (t <= (a.x * B[3][3] + a.z * B[1][3] + b.x * B[2][2] + b.y * B[2][1])) then return false end
×
403
        t = abs(T.y * B[1][1] - T.x * B[2][1])
×
404
        if not (t <= (a.x * B[2][1] + a.y * B[1][1] + b.y * B[3][3] + b.z * B[3][2])) then return false end
×
405
        t = abs(T.y * B[1][2] - T.x * B[2][2])
×
406
        if not (t <= (a.x * B[2][2] + a.y * B[1][2] + b.x * B[3][3] + b.z * B[3][1])) then return false end
×
407
        t = abs(T.y * B[1][3] - T.x * B[2][3])
×
408
        if not (t <= (a.x * B[2][3] + a.y * B[1][3] + b.x * B[3][2] + b.y * B[3][1])) then return false end
×
409

410
        -- https://gamedev.stackexchange.com/questions/24078/which-side-was-hit
411
        -- Minkowski Sum
412
        local wy = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.y - obb.position.y)
×
413
        local hx = (aabb.extent * 2 + obb.extent * 2) * (aabb.position.x - obb.position.x)
×
414

415
        if wy.x > hx.x and wy.y > hx.y and wy.z > hx.z then
×
416
                if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
×
417
                        return vec3(obb.rotation * {  0, -1, 0, 1 })
×
418
                else
419
                        return vec3(obb.rotation * { -1,  0, 0, 1 })
×
420
                end
421
        else
422
                if wy.x > -hx.x and wy.y > -hx.y and wy.z > -hx.z then
×
423
                        return vec3(obb.rotation * { 1, 0, 0, 1 })
×
424
                else
425
                        return vec3(obb.rotation * { 0, 1, 0, 1 })
×
426
                end
427
        end
428
end
429

430
-- http://stackoverflow.com/a/4579069/1190664
431
-- aabb.min        is a vec3
432
-- aabb.max        is a vec3
433
-- sphere.position is a vec3
434
-- sphere.radius   is a number
435
local axes = { "x", "y", "z" }
×
436
function intersect.aabb_sphere(aabb, sphere)
×
437
        local dist2 = sphere.radius ^ 2
×
438

439
        for _, axis in ipairs(axes) do
×
440
                local pos  = sphere.position[axis]
×
441
                local amin = aabb.min[axis]
×
442
                local amax = aabb.max[axis]
×
443

444
                if pos < amin then
×
445
                        dist2 = dist2 - (pos - amin) ^ 2
×
446
                elseif pos > amax then
×
447
                        dist2 = dist2 - (pos - amax) ^ 2
×
448
                end
449
        end
450

451
        return dist2 > 0
×
452
end
453

454
-- aabb.min       is a vec3
455
-- aabb.max       is a vec3
456
-- frustum.left   is a plane { a, b, c, d }
457
-- frustum.right  is a plane { a, b, c, d }
458
-- frustum.bottom is a plane { a, b, c, d }
459
-- frustum.top    is a plane { a, b, c, d }
460
-- frustum.near   is a plane { a, b, c, d }
461
-- frustum.far    is a plane { a, b, c, d }
462
function intersect.aabb_frustum(aabb, frustum)
×
463
        -- Indexed for the 'index trick' later
464
        local box = {
×
465
                aabb.min,
×
466
                aabb.max
467
        }
468

469
        -- We have 6 planes defining the frustum, 5 if infinite.
470
        local planes = {
×
471
                frustum.left,
×
472
                frustum.right,
×
473
                frustum.bottom,
×
474
                frustum.top,
×
475
                frustum.near,
×
476
                frustum.far or false
×
477
        }
478

479
        -- Skip the last test for infinite projections, it'll never fail.
480
        if not planes[6] then
×
481
                table.remove(planes)
×
482
        end
483

484
        for i = 1, #planes do
×
485
                -- This is the current plane
486
                local p = planes[i]
×
487

488
                -- p-vertex selection (with the index trick)
489
                -- According to the plane normal we can know the
490
                -- indices of the positive vertex
491
                local px = p.a > 0.0 and 2 or 1
×
492
                local py = p.b > 0.0 and 2 or 1
×
493
                local pz = p.c > 0.0 and 2 or 1
×
494

495
                -- project p-vertex on plane normal
496
                -- (How far is p-vertex from the origin)
497
                local dot = (p.a * box[px].x) + (p.b * box[py].y) + (p.c * box[pz].z)
×
498

499
                -- Doesn't intersect if it is behind the plane
500
                if dot < -p.d then
×
501
                        return false
×
502
                end
503
        end
504

505
        return true
×
506
end
507

508
-- outer.min is a vec3
509
-- outer.max is a vec3
510
-- inner.min is a vec3
511
-- inner.max is a vec3
512
function intersect.encapsulate_aabb(outer, inner)
×
513
        return
×
514
                outer.min.x <= inner.min.x and
×
515
                outer.max.x >= inner.max.x and
×
516
                outer.min.y <= inner.min.y and
×
517
                outer.max.y >= inner.max.y and
×
518
                outer.min.z <= inner.min.z and
×
519
                outer.max.z >= inner.max.z
×
520
end
521

522
-- a.position is a vec3
523
-- a.radius   is a number
524
-- b.position is a vec3
525
-- b.radius   is a number
526
function intersect.circle_circle(a, b)
×
527
        return a.position:dist(b.position) <= a.radius + b.radius
×
528
end
529

530
-- a.position is a vec3
531
-- a.radius   is a number
532
-- b.position is a vec3
533
-- b.radius   is a number
534
function intersect.sphere_sphere(a, b)
×
535
        return intersect.circle_circle(a, b)
×
536
end
537

538
-- http://realtimecollisiondetection.net/blog/?p=103
539
-- sphere.position is a vec3
540
-- sphere.radius   is a number
541
-- triangle[1]     is a vec3
542
-- triangle[2]     is a vec3
543
-- triangle[3]     is a vec3
544
function intersect.sphere_triangle(sphere, triangle)
×
545
        -- Sphere is centered at origin
546
        local A  = triangle[1] - sphere.position
×
547
        local B  = triangle[2] - sphere.position
×
548
        local C  = triangle[3] - sphere.position
×
549

550
        -- Compute normal of triangle plane
551
        local V  = (B - A):cross(C - A)
×
552

553
        -- Test if sphere lies outside triangle plane
554
        local rr = sphere.radius * sphere.radius
×
555
        local d  = A:dot(V)
×
556
        local e  = V:dot(V)
×
557
        local s1 = d * d > rr * e
×
558

559
        -- Test if sphere lies outside triangle vertices
560
        local aa = A:dot(A)
×
561
        local ab = A:dot(B)
×
562
        local ac = A:dot(C)
×
563
        local bb = B:dot(B)
×
564
        local bc = B:dot(C)
×
565
        local cc = C:dot(C)
×
566

567
        local s2 = (aa > rr) and (ab > aa) and (ac > aa)
×
568
        local s3 = (bb > rr) and (ab > bb) and (bc > bb)
×
569
        local s4 = (cc > rr) and (ac > cc) and (bc > cc)
×
570

571
        -- Test is sphere lies outside triangle edges
572
        local AB = B - A
×
573
        local BC = C - B
×
574
        local CA = A - C
×
575

576
        local d1 = ab - aa
×
577
        local d2 = bc - bb
×
578
        local d3 = ac - cc
×
579

580
        local e1 = AB:dot(AB)
×
581
        local e2 = BC:dot(BC)
×
582
        local e3 = CA:dot(CA)
×
583

584
        local Q1 = A * e1 - AB * d1
×
585
        local Q2 = B * e2 - BC * d2
×
586
        local Q3 = C * e3 - CA * d3
×
587

588
        local QC = C * e1 - Q1
×
589
        local QA = A * e2 - Q2
×
590
        local QB = B * e3 - Q3
×
591

592
        local s5 = (Q1:dot(Q1) > rr * e1 * e1) and (Q1:dot(QC) > 0)
×
593
        local s6 = (Q2:dot(Q2) > rr * e2 * e2) and (Q2:dot(QA) > 0)
×
594
        local s7 = (Q3:dot(Q3) > rr * e3 * e3) and (Q3:dot(QB) > 0)
×
595

596
        -- Return whether or not any of the tests passed
597
        return s1 or s2 or s3 or s4 or s5 or s6 or s7
×
598
end
599

600
-- sphere.position is a vec3
601
-- sphere.radius   is a number
602
-- frustum.left    is a plane { a, b, c, d }
603
-- frustum.right   is a plane { a, b, c, d }
604
-- frustum.bottom  is a plane { a, b, c, d }
605
-- frustum.top     is a plane { a, b, c, d }
606
-- frustum.near    is a plane { a, b, c, d }
607
-- frustum.far     is a plane { a, b, c, d }
608
function intersect.sphere_frustum(sphere, frustum)
×
609
        local x, y, z = sphere.position:unpack()
×
610
        local planes  = {
×
611
                frustum.left,
×
612
                frustum.right,
×
613
                frustum.bottom,
×
614
                frustum.top,
×
615
                frustum.near
616
        }
617

618
        if frustum.far then
×
619
                table.insert(planes, frustum.far, 5)
×
620
        end
621

622
        local dot
623
        for i = 1, #planes do
×
624
                dot = planes[i].a * x + planes[i].b * y + planes[i].c * z + planes[i].d
×
625

626
                if dot <= -sphere.radius then
×
627
                        return false
×
628
                end
629
        end
630

631
        -- dot + radius is the distance of the object from the near plane.
632
        -- make sure that the near plane is the last test!
633
        return dot + sphere.radius
×
634
end
635

636
function intersect.capsule_capsule(c1, c2)
×
637
        local dist2, p1, p2 = intersect.closest_point_segment_segment(c1.a, c1.b, c2.a, c2.b)
×
638
        local radius = c1.radius + c2.radius
×
639

640
        if dist2 <= radius * radius then
×
641
                return p1, p2
×
642
        end
643

644
        return false
×
645
end
646

647
function intersect.closest_point_segment_segment(p1, p2, p3, p4)
×
648
        local s  -- Distance of intersection along segment 1
649
        local t  -- Distance of intersection along segment 2
650
        local c1 -- Collision point on segment 1
651
        local c2 -- Collision point on segment 2
652

653
        local d1 = p2 - p1 -- Direction of segment 1
×
654
        local d2 = p4 - p3 -- Direction of segment 2
×
655
        local r  = p1 - p3
×
656
        local a  = d1:dot(d1)
×
657
        local e  = d2:dot(d2)
×
658
        local f  = d2:dot(r)
×
659

660
        -- Check if both segments degenerate into points
661
        if a <= DBL_EPSILON and e <= DBL_EPSILON then
×
662
                s  = 0
×
663
                t  = 0
×
664
                c1 = p1
×
665
                c2 = p3
×
666
                return (c1 - c2):dot(c1 - c2), s, t, c1, c2
×
667
        end
668

669
        -- Check if segment 1 degenerates into a point
670
        if a <= DBL_EPSILON then
×
671
                s = 0
×
672
                t = utils.clamp(f / e, 0, 1)
×
673
        else
674
                local c = d1:dot(r)
×
675

676
                -- Check is segment 2 degenerates into a point
677
                if e <= DBL_EPSILON then
×
678
                        t = 0
×
679
                        s = utils.clamp(-c / a, 0, 1)
×
680
                else
681
                        local b     = d1:dot(d2)
×
682
                        local denom = a * e - b * b
×
683

684
                        if abs(denom) > 0 then
×
685
                                s = utils.clamp((b * f - c * e) / denom, 0, 1)
×
686
                        else
687
                                s = 0
×
688
                        end
689

690
                        t = (b * s + f) / e
×
691

692
                        if t < 0 then
×
693
                                t = 0
×
694
                                s = utils.clamp(-c / a, 0, 1)
×
695
                        elseif t > 1 then
×
696
                                t = 1
×
697
                                s = utils.clamp((b - c) / a, 0, 1)
×
698
                        end
699
                end
700
        end
701

702
        c1 = p1 + d1 * s
×
703
        c2 = p3 + d2 * t
×
704

705
        return (c1 - c2):dot(c1 - c2), c1, c2, s, t
×
706
end
707

708
return intersect
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc