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

excessive / cpml / 13079282556

07 May 2022 10:53PM UTC coverage: 57.717% (+43.7%) from 14.013%
13079282556

push

github

shakesoda
fix typo in mat4.mul

0 of 1 new or added line in 1 file covered. (0.0%)

527 existing lines in 19 files now uncovered.

4581 of 7937 relevant lines covered (57.72%)

52.86 hits per line

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

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

4
local modules     = (...):gsub('%.[^%.]+$', '') .. "."
2✔
5
local constants   = require(modules .. "constants")
2✔
6
local mat4        = require(modules .. "mat4")
2✔
7
local vec3        = require(modules .. "vec3")
2✔
8
local utils       = require(modules .. "utils")
2✔
9
local DBL_EPSILON = constants.DBL_EPSILON
2✔
10
local sqrt        = math.sqrt
2✔
11
local abs         = math.abs
2✔
12
local min         = math.min
2✔
13
local max         = math.max
2✔
14
local intersect   = {}
2✔
15

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

26
        local vw = v:cross(w)
2✔
27
        local vu = v:cross(u)
2✔
28

29
        if vw:dot(vu) < 0 then
2✔
UNCOV
30
                return false
×
31
        end
32

33
        local uw = u:cross(w)
2✔
34
        local uv = u:cross(v)
2✔
35

36
        if uw:dot(uv) < 0 then
2✔
UNCOV
37
                return false
×
38
        end
39

40
        local d = uv:len()
2✔
41
        local r = vw:len() / d
2✔
42
        local t = uw:len() / d
2✔
43

44
        return r + t <= 1
2✔
45
end
46

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

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

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

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

UNCOV
91
        return true
×
92
end
93

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

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

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

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

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

126
        local q = s:cross(e1)
2✔
127
        local v = ray.direction:dot(q) * f
2✔
128

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

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

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

143
        -- ray does not intersect triangle
144
        return false
1✔
145
end
146

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

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

163
        local discr = b * b - c
1✔
164

165
        -- negative discriminant
166
        if discr < 0 then
1✔
UNCOV
167
                return false
×
168
        end
169

170
        -- Clamp t to 0
171
        local t = -b - sqrt(discr)
1✔
172
        t = t < 0 and 0 or t
1✔
173

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

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

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

198
        local tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6))
2✔
199
        local tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6))
2✔
200

201
        -- ray is intersecting AABB, but whole AABB is behind us
202
        if tmax < 0 then
2✔
203
                return false
1✔
204
        end
205

206
        -- ray does not intersect AABB
207
        if tmin > tmax then
1✔
UNCOV
208
                return false
×
209
        end
210

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

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

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

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

232
        if t < DBL_EPSILON then
2✔
233
                return false
1✔
234
        end
235

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

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

UNCOV
251
        return false
×
252
end
253

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

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

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

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

284
        local numer = d1343 * d4321 - d1321 * d4343
6✔
285
        local mua   = numer / denom
6✔
286
        local mub   = (d1343 + d4321 * mua) / d4343
6✔
287

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

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

298
        return { out1, out2 }, dist
4✔
299
end
300

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

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

341
        -- segments do not intersect
342
        return false
1✔
343
end
344

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

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

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

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

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

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

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

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

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

452
        return dist2 > 0
2✔
453
end
454

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

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

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

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

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

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

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

UNCOV
506
        return true
×
507
end
508

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

UNCOV
645
        return false
×
646
end
647

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

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

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

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

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

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

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

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

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

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

709
return intersect
2✔
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