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

excessive / cpml / 6132372428

09 Sep 2023 05:51PM UTC coverage: 58.701% (+0.6%) from 58.146%
6132372428

push

github

web-flow
Merge pull request #1 from mcclure/euler-quat

quat.to_euler_angles and quat.to_euler_angles_unpack

14 of 14 new or added lines in 1 file covered. (100.0%)

4662 of 7942 relevant lines covered (58.7%)

165.78 hits per line

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

94.3
/modules/vec3.lua
1
--- A 3 component vector.
2
-- @module vec3
3

4
local modules = (...):gsub('%.[^%.]+$', '') .. "."
33✔
5
local precond = require(modules .. "_private_precond")
33✔
6
local private = require(modules .. "_private_utils")
33✔
7
local sqrt    = math.sqrt
33✔
8
local cos     = math.cos
33✔
9
local sin     = math.sin
33✔
10
local vec3    = {}
33✔
11
local vec3_mt = {}
33✔
12

13
-- Private constructor.
14
local function new(x, y, z)
15
        return setmetatable({
1,224✔
16
                x = x or 0,
612✔
17
                y = y or 0,
612✔
18
                z = z or 0
612✔
19
        }, vec3_mt)
1,224✔
20
end
21

22
-- Do the check to see if JIT is enabled. If so use the optimized FFI structs.
23
local status, ffi
24
if type(jit) == "table" and jit.status() then
33✔
25
        status, ffi = pcall(require, "ffi")
22✔
26
        if status then
22✔
27
                ffi.cdef "typedef struct { double x, y, z;} cpml_vec3;"
22✔
28
                new = ffi.typeof("cpml_vec3")
44✔
29
        end
30
end
31

32
--- Constants
33
-- @table vec3
34
-- @field unit_x X axis of rotation
35
-- @field unit_y Y axis of rotation
36
-- @field unit_z Z axis of rotation
37
-- @field zero Empty vector
38
vec3.unit_x = new(1, 0, 0)
33✔
39
vec3.unit_y = new(0, 1, 0)
33✔
40
vec3.unit_z = new(0, 0, 1)
33✔
41
vec3.zero   = new(0, 0, 0)
33✔
42

43
--- The public constructor.
44
-- @param x Can be of three types: </br>
45
-- number X component
46
-- table {x, y, z} or {x=x, y=y, z=z}
47
-- scalar To fill the vector eg. {x, x, x}
48
-- @tparam number y Y component
49
-- @tparam number z Z component
50
-- @treturn vec3 out
51
function vec3.new(x, y, z)
33✔
52
        -- number, number, number
53
        if x and y and z then
792✔
54
                precond.typeof(x, "number", "new: Wrong argument type for x")
615✔
55
                precond.typeof(y, "number", "new: Wrong argument type for y")
615✔
56
                precond.typeof(z, "number", "new: Wrong argument type for z")
615✔
57

58
                return new(x, y, z)
615✔
59

60
        -- {x, y, z} or {x=x, y=y, z=z}
61
        elseif type(x) == "table" or type(x) == "cdata" then -- table in vanilla lua, cdata in luajit
177✔
62
                local xx, yy, zz = x.x or x[1], x.y or x[2], x.z or x[3]
12✔
63
                precond.typeof(xx, "number", "new: Wrong argument type for x")
12✔
64
                precond.typeof(yy, "number", "new: Wrong argument type for y")
12✔
65
                precond.typeof(zz, "number", "new: Wrong argument type for z")
12✔
66

67
                return new(xx, yy, zz)
12✔
68

69
        -- number
70
        elseif type(x) == "number" then
165✔
71
                return new(x, x, x)
78✔
72
        else
73
                return new()
87✔
74
        end
75
end
76

77
--- Clone a vector.
78
-- @tparam vec3 a Vector to be cloned
79
-- @treturn vec3 out
80
function vec3.clone(a)
33✔
81
        return new(a.x, a.y, a.z)
216✔
82
end
83

84
--- Add two vectors.
85
-- @tparam vec3 a Left hand operand
86
-- @tparam vec3 b Right hand operand
87
-- @treturn vec3 out
88
function vec3.add(a, b)
33✔
89
        return new(
176✔
90
                a.x + b.x,
132✔
91
                a.y + b.y,
132✔
92
                a.z + b.z
132✔
93
        )
88✔
94
end
95

96
--- Subtract one vector from another.
97
-- Order: If a and b are positions, computes the direction and distance from b
98
-- to a.
99
-- @tparam vec3 a Left hand operand
100
-- @tparam vec3 b Right hand operand
101
-- @treturn vec3 out
102
function vec3.sub(a, b)
33✔
103
        return new(
188✔
104
                a.x - b.x,
141✔
105
                a.y - b.y,
141✔
106
                a.z - b.z
141✔
107
        )
94✔
108
end
109

110
--- Multiply a vector by another vector.
111
-- Component-wise multiplication not matrix multiplication.
112
-- @tparam vec3 a Left hand operand
113
-- @tparam vec3 b Right hand operand
114
-- @treturn vec3 out
115
function vec3.mul(a, b)
33✔
116
        return new(
×
117
                a.x * b.x,
×
118
                a.y * b.y,
×
119
                a.z * b.z
×
120
        )
121
end
122

123
--- Divide a vector by another.
124
-- Component-wise inv multiplication. Like a non-uniform scale().
125
-- @tparam vec3 a Left hand operand
126
-- @tparam vec3 b Right hand operand
127
-- @treturn vec3 out
128
function vec3.div(a, b)
33✔
129
        return new(
8✔
130
                a.x / b.x,
6✔
131
                a.y / b.y,
6✔
132
                a.z / b.z
6✔
133
        )
4✔
134
end
135

136
--- Scale a vector to unit length (1).
137
-- @tparam vec3 a vector to normalize
138
-- @treturn vec3 out
139
function vec3.normalize(a)
33✔
140
        if a:is_zero() then
273✔
141
                return new()
×
142
        end
143
        return a:scale(1 / a:len())
351✔
144
end
145

146
--- Scale a vector to unit length (1), and return the input length.
147
-- @tparam vec3 a vector to normalize
148
-- @treturn vec3 out
149
-- @treturn number input vector length
150
function vec3.normalize_len(a)
33✔
151
        if a:is_zero() then
7✔
152
                return new(), 0
×
153
        end
154
        local len = a:len()
5✔
155
        return a:scale(1 / len), len
7✔
156
end
157

158
--- Trim a vector to a given length
159
-- @tparam vec3 a vector to be trimmed
160
-- @tparam number len Length to trim the vector to
161
-- @treturn vec3 out
162
function vec3.trim(a, len)
33✔
163
        return a:normalize():scale(math.min(a:len(), len))
13✔
164
end
165

166
--- Get the cross product of two vectors.
167
-- Resulting direction is right-hand rule normal of plane defined by a and b.
168
-- Magnitude is the area spanned by the parallelograms that a and b span.
169
-- Order: Direction determined by right-hand rule.
170
-- @tparam vec3 a Left hand operand
171
-- @tparam vec3 b Right hand operand
172
-- @treturn vec3 out
173
function vec3.cross(a, b)
33✔
174
        return new(
104✔
175
                a.y * b.z - a.z * b.y,
78✔
176
                a.z * b.x - a.x * b.z,
78✔
177
                a.x * b.y - a.y * b.x
78✔
178
        )
52✔
179
end
180

181
--- Get the dot product of two vectors.
182
-- @tparam vec3 a Left hand operand
183
-- @tparam vec3 b Right hand operand
184
-- @treturn number dot
185
function vec3.dot(a, b)
33✔
186
        return a.x * b.x + a.y * b.y + a.z * b.z
219✔
187
end
188

189
--- Get the length of a vector.
190
-- @tparam vec3 a Vector to get the length of
191
-- @treturn number len
192
function vec3.len(a)
33✔
193
        return sqrt(a.x * a.x + a.y * a.y + a.z * a.z)
168✔
194
end
195

196
--- Get the squared length of a vector.
197
-- @tparam vec3 a Vector to get the squared length of
198
-- @treturn number len
199
function vec3.len2(a)
33✔
200
        return a.x * a.x + a.y * a.y + a.z * a.z
39✔
201
end
202

203
--- Get the distance between two vectors.
204
-- @tparam vec3 a Left hand operand
205
-- @tparam vec3 b Right hand operand
206
-- @treturn number dist
207
function vec3.dist(a, b)
33✔
208
        local dx = a.x - b.x
33✔
209
        local dy = a.y - b.y
33✔
210
        local dz = a.z - b.z
33✔
211
        return sqrt(dx * dx + dy * dy + dz * dz)
33✔
212
end
213

214
--- Get the squared distance between two vectors.
215
-- @tparam vec3 a Left hand operand
216
-- @tparam vec3 b Right hand operand
217
-- @treturn number dist
218
function vec3.dist2(a, b)
33✔
219
        local dx = a.x - b.x
3✔
220
        local dy = a.y - b.y
3✔
221
        local dz = a.z - b.z
3✔
222
        return dx * dx + dy * dy + dz * dz
3✔
223
end
224

225
--- Scale a vector by a scalar.
226
-- @tparam vec3 a Left hand operand
227
-- @tparam number b Right hand operand
228
-- @treturn vec3 out
229
function vec3.scale(a, b)
33✔
230
        return new(
340✔
231
                a.x * b,
255✔
232
                a.y * b,
255✔
233
                a.z * b
255✔
234
        )
170✔
235
end
236

237
--- Rotate vector about an axis.
238
-- @tparam vec3 a Vector to rotate
239
-- @tparam number phi Angle to rotate vector by (in radians)
240
-- @tparam vec3 axis Axis to rotate by
241
-- @treturn vec3 out
242
function vec3.rotate(a, phi, axis)
33✔
243
        if not vec3.is_vec3(axis) then
15✔
244
                return a
3✔
245
        end
246

247
        local u = axis:normalize()
10✔
248
        local c = cos(phi)
6✔
249
        local s = sin(phi)
6✔
250

251
        -- Calculate generalized rotation matrix
252
        local m1 = new((c + u.x * u.x * (1 - c)),       (u.x * u.y * (1 - c) - u.z * s), (u.x * u.z * (1 - c) + u.y * s))
6✔
253
        local m2 = new((u.y * u.x * (1 - c) + u.z * s), (c + u.y * u.y * (1 - c)),       (u.y * u.z * (1 - c) - u.x * s))
6✔
254
        local m3 = new((u.z * u.x * (1 - c) - u.y * s), (u.z * u.y * (1 - c) + u.x * s), (c + u.z * u.z * (1 - c))      )
6✔
255

256
        return new(
8✔
257
                a:dot(m1),
10✔
258
                a:dot(m2),
10✔
259
                a:dot(m3)
10✔
260
        )
4✔
261
end
262

263
--- Get the perpendicular vector of a vector.
264
-- @tparam vec3 a Vector to get perpendicular axes from
265
-- @treturn vec3 out
266
function vec3.perpendicular(a)
33✔
267
        return new(-a.y, a.x, 0)
3✔
268
end
269

270
--- Lerp between two vectors.
271
-- @tparam vec3 a Left hand operand
272
-- @tparam vec3 b Right hand operand
273
-- @tparam number s Step value
274
-- @treturn vec3 out
275
function vec3.lerp(a, b, s)
33✔
276
        return a + (b - a) * s
9✔
277
end
278

279
-- Round all components to nearest int (or other precision).
280
-- @tparam vec3 a Vector to round.
281
-- @tparam precision Digits after the decimal (round numebr if unspecified)
282
-- @treturn vec3 Rounded vector
283
function vec3.round(a, precision)
33✔
284
        return vec3.new(private.round(a.x, precision), private.round(a.y, precision), private.round(a.z, precision))
27✔
285
end
286

287
--- Unpack a vector into individual components.
288
-- @tparam vec3 a Vector to unpack
289
-- @treturn number x
290
-- @treturn number y
291
-- @treturn number z
292
function vec3.unpack(a)
33✔
293
        return a.x, a.y, a.z
9✔
294
end
295

296
--- Return the component-wise minimum of two vectors.
297
-- @tparam vec3 a Left hand operand
298
-- @tparam vec3 b Right hand operand
299
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
300
function vec3.component_min(a, b)
33✔
301
        return new(math.min(a.x, b.x), math.min(a.y, b.y), math.min(a.z, b.z))
27✔
302
end
303

304
--- Return the component-wise maximum of two vectors.
305
-- @tparam vec3 a Left hand operand
306
-- @tparam vec3 b Right hand operand
307
-- @treturn vec3 A vector where each component is the lesser value for that component between the two given vectors.
308
function vec3.component_max(a, b)
33✔
309
        return new(math.max(a.x, b.x), math.max(a.y, b.y), math.max(a.z, b.z))
27✔
310
end
311

312
-- Negate x axis only of vector.
313
-- @tparam vec3 a Vector to x-flip.
314
-- @treturn vec3 x-flipped vector
315
function vec3.flip_x(a)
33✔
316
        return vec3.new(-a.x, a.y, a.z)
3✔
317
end
318

319
-- Negate y axis only of vector.
320
-- @tparam vec3 a Vector to y-flip.
321
-- @treturn vec3 y-flipped vector
322
function vec3.flip_y(a)
33✔
323
        return vec3.new(a.x, -a.y, a.z)
3✔
324
end
325

326
-- Negate z axis only of vector.
327
-- @tparam vec3 a Vector to z-flip.
328
-- @treturn vec3 z-flipped vector
329
function vec3.flip_z(a)
33✔
330
        return vec3.new(a.x, a.y, -a.z)
3✔
331
end
332

333
function vec3.angle_to(a, b)
33✔
334
        local v = a:normalize():dot(b:normalize())
182✔
335
        return math.acos(v)
42✔
336
end
337

338
--- Return a boolean showing if a table is or is not a vec3.
339
-- @tparam vec3 a Vector to be tested
340
-- @treturn boolean is_vec3
341
function vec3.is_vec3(a)
33✔
342
        if type(a) == "cdata" then
1,229✔
343
                return ffi.istype("cpml_vec3", a)
608✔
344
        end
345

346
        return
×
347
                type(a)   == "table"  and
621✔
348
                type(a.x) == "number" and
345✔
349
                type(a.y) == "number" and
300✔
350
                type(a.z) == "number"
621✔
351
end
352

353
--- Return a boolean showing if a table is or is not a zero vec3.
354
-- @tparam vec3 a Vector to be tested
355
-- @treturn boolean is_zero
356
function vec3.is_zero(a)
33✔
357
        return a.x == 0 and a.y == 0 and a.z == 0
123✔
358
end
359

360
--- Return whether any component is NaN
361
-- @tparam vec3 a Vector to be tested
362
-- @treturn boolean if x,y, or z are nan
363
function vec3.has_nan(a)
33✔
364
        return private.is_nan(a.x) or
5✔
365
                private.is_nan(a.y) or
×
366
                private.is_nan(a.z)
3✔
367
end
368

369
--- Return a formatted string.
370
-- @tparam vec3 a Vector to be turned into a string
371
-- @treturn string formatted
372
function vec3.to_string(a)
33✔
373
        return string.format("(%+0.3f,%+0.3f,%+0.3f)", a.x, a.y, a.z)
3✔
374
end
375
--provide basic compatibility with other vector libraries
376
local vec_enum = {
33✔
377
        "x",
11✔
378
        "y",
11✔
379
        "z"
380
}
11✔
381
vec3_mt.__index    = function(vec, i)
382
        if vec3[i] then return vec3[i] end
1,844✔
383
        if type(i) == "number" and i < 4 then
8✔
384
                return vec[vec_enum[i]]
×
385
        end
386
end
387
vec3_mt.__tostring = vec3.to_string
33✔
388

389
function vec3_mt.__call(_, x, y, z)
33✔
390
        return vec3.new(x, y, z)
771✔
391
end
392

393
function vec3_mt.__unm(a)
33✔
394
        return new(-a.x, -a.y, -a.z)
3✔
395
end
396

397
function vec3_mt.__eq(a, b)
33✔
398
        if not vec3.is_vec3(a) or not vec3.is_vec3(b) then
229✔
399
                return false
×
400
        end
401
        return a.x == b.x and a.y == b.y and a.z == b.z
97✔
402
end
403

404
function vec3_mt.__add(a, b)
33✔
405
        precond.assert(vec3.is_vec3(a), "__add: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
215✔
406
        precond.assert(vec3.is_vec3(b), "__add: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
215✔
407
        return a:add(b)
215✔
408
end
409

410
function vec3_mt.__sub(a, b)
33✔
411
        precond.assert(vec3.is_vec3(a), "__sub: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
230✔
412
        precond.assert(vec3.is_vec3(b), "__sub: Wrong argument type '%s' for right hand operand. (<cpml.vec3> expected)", type(b))
230✔
413
        return a:sub(b)
230✔
414
end
415

416
function vec3_mt.__mul(a, b)
33✔
417
        precond.assert(vec3.is_vec3(a), "__mul: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
190✔
418
        precond.assert(vec3.is_vec3(b) or type(b) == "number", "__mul: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
190✔
419

420
        if vec3.is_vec3(b) then
190✔
421
                return a:mul(b)
×
422
        end
423

424
        return a:scale(b)
190✔
425
end
426

427
function vec3_mt.__div(a, b)
33✔
428
        precond.assert(vec3.is_vec3(a), "__div: Wrong argument type '%s' for left hand operand. (<cpml.vec3> expected)", type(a))
30✔
429
        precond.assert(vec3.is_vec3(b) or type(b) == "number", "__div: Wrong argument type '%s' for right hand operand. (<cpml.vec3> or <number> expected)", type(b))
30✔
430

431
        if vec3.is_vec3(b) then
30✔
432
                return a:div(b)
5✔
433
        end
434

435
        return a:scale(1 / b)
25✔
436
end
437

438
if status then
33✔
439
        xpcall(function() -- Allow this to silently fail; assume failure means someone messed with package.loaded
44✔
440
                ffi.metatype(new, vec3_mt)
22✔
441
        end, function() end)
44✔
442
end
443

444
return setmetatable({}, vec3_mt)
33✔
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