# ============================================================================ # fmath.ludic — IEEE single-precision math for the renderer. # # Ludic's own numbers are Q16.16; a renderer wants the floats the GPU eats. A # float lives here as its bit pattern in an `int`, arithmetic goes through the # f_* helpers Gl.* provides (gl.ll), and vectors / matrices are `words` buffers # of those bit patterns — which is exactly the memory layout glUniform* and # glBufferData expect, so nothing is converted at upload. # # fl(x) a fixed literal as a float fr(n, d) n/d as a float # v3_* 3-vectors at an index of a words buffer (x, y, z consecutive) # m4_* 4x4 column-major matrices in a 16-word buffer # ============================================================================ const F_ZERO: int = 0x00000000 const F_ONE: int = 0x3F800000 const F_TWO: int = 0x40000000 const F_HALF: int = 0x3F000000 const PI: float = 3.1415927 const F_PI: int = 0x40490FDB # the n 4x4 matrices laid end to end in `m`, a view of each. Views are made once, where their # buffer is: a view is a small allocation, and one per matrix per frame is one nobody frees. @alloc_ok("views over a skeleton's matrices: made once per skeleton, with it") function m4_views(m: floats, n: int) -> [][]float { let out = new [][]float var i = 0 while i < n { push(out, view(m, i * 16, 16)) i += 1 } return out } function fl(x: fixed) -> float { return float(x) } function fi(n: int) -> float { return float(n) } function fr(n: int, d: int) -> float { return float(n) / float(d) } function f_neg1() -> float { return -1.0 } function f_clamp(x: float, lo: float, hi: float) -> float { return Math.min(Math.max(x, lo), hi) } function f_lerp(a: float, b: float, t: float) -> float { return a + (b - a) * t } function f_rad(deg: float) -> float { return deg * (PI / 180.0) } function f_fx(x: float) -> fixed { return fixed(x) } # ---- vectors ---------------------------------------------------------------- @alloc_ok("a constructor: what it makes is its caller's (records made at start-up or once per figure)") function v3_new(x: float, y: float, z: float) -> floats { let v = floats(3) v[0] = x v[1] = y v[2] = z return v } function v3_set(v: floats, x: float, y: float, z: float) -> void { v[0] = x; v[1] = y; v[2] = z } function v3_copy(o: floats, a: floats) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2] } function v3_add(o: floats, a: floats, b: floats) -> void { o[0] = a[0] + b[0]; o[1] = a[1] + b[1]; o[2] = a[2] + b[2] } function v3_sub(o: floats, a: floats, b: floats) -> void { o[0] = a[0] - b[0]; o[1] = a[1] - b[1]; o[2] = a[2] - b[2] } function v3_scale(o: floats, a: floats, s: float) -> void { o[0] = a[0] * s; o[1] = a[1] * s; o[2] = a[2] * s } function v3_madd(o: floats, a: floats, b: floats, s: float) -> void { # o = a + b*s o[0] = a[0] + b[0] * s; o[1] = a[1] + b[1] * s; o[2] = a[2] + b[2] * s } function v3_dot(a: floats, b: floats) -> float { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] } function v3_cross(o: floats, a: floats, b: floats) -> void { let x = a[1] * b[2] - a[2] * b[1] let y = a[2] * b[0] - a[0] * b[2] let z = a[0] * b[1] - a[1] * b[0] o[0] = x; o[1] = y; o[2] = z } function v3_len(a: floats) -> float { return Math.sqrt(v3_dot(a, a)) } function v3_normalize(o: floats, a: floats) -> void { let l = v3_len(a) if l == 0.0 { o[0] = 0.0; o[1] = 0.0; o[2] = 0.0; return } let inv = 1.0 / l v3_scale(o, a, inv) } function v3_dist(a: floats, b: floats) -> float { let dx = a[0] - b[0] let dy = a[1] - b[1] let dz = a[2] - b[2] let d = Math.sqrt(dx * dx + dy * dy + dz * dz) return d } # ---- matrices (column-major, m[col*4 + row]) ------------------------------------ @alloc_ok("a constructor: what it makes is its caller's (records made at start-up or once per figure)") function m4_new() -> floats { let m = floats(16); m4_identity(m); return m } function m4_identity(m: floats) -> void { for i in 0 .. 16 { m[i] = 0.0 } m[0] = 1.0; m[5] = 1.0; m[10] = 1.0; m[15] = 1.0 } function m4_copy(o: floats, a: floats) -> void { for i in 0 .. 16 { o[i] = a[i] } } # o = a * b (o may not alias a or b) function m4_mul(o: floats, a: floats, b: floats) -> void { for c in 0 .. 4 { for r in 0 .. 4 { var s = 0.0 for k in 0 .. 4 { s = s + a[k * 4 + r] * b[c * 4 + k] } o[c * 4 + r] = s } } } function m4_mul_into(a: floats, b: floats) -> void { # a = a * b let t = floats(16) m4_mul(t, a, b) m4_copy(a, t) free(t) } function m4_translation(m: floats, x: float, y: float, z: float) -> void { m4_identity(m) m[12] = x; m[13] = y; m[14] = z } function m4_scaling(m: floats, x: float, y: float, z: float) -> void { m4_identity(m) m[0] = x; m[5] = y; m[10] = z } function m4_rotation_y(m: floats, angle: float) -> void { m4_identity(m) let c = Math.cos(angle); let s = Math.sin(angle) m[0] = c; m[2] = -s; m[8] = s; m[10] = c } function m4_rotation_x(m: floats, angle: float) -> void { m4_identity(m) let c = Math.cos(angle); let s = Math.sin(angle) m[5] = c; m[6] = s; m[9] = -s; m[10] = c } function m4_rotation_z(m: floats, angle: float) -> void { m4_identity(m) let c = Math.cos(angle); let s = Math.sin(angle) m[0] = c; m[1] = s; m[4] = -s; m[5] = c } # a model matrix: translate * rotate_y * uniform scale function m4_trs(m: floats, x: float, y: float, z: float, yaw: float, s: float) -> void { m4_rotation_y(m, yaw) for i in 0 .. 12 { m[i] = m[i] * s } m[12] = x; m[13] = y; m[14] = z } # OpenGL clip space (z in [-1, 1]); fovy in radians function m4_perspective(m: floats, fovy: float, aspect: float, near: float, far: float) -> void { for i in 0 .. 16 { m[i] = 0.0 } let f = 1.0 / Math.tan(fovy * 0.5) m[0] = f / aspect m[5] = f m[10] = (far + near) / (near - far) m[11] = -1.0 m[14] = 2.0 * far * near / (near - far) } function m4_ortho(m: floats, l: float, r: float, b: float, t: float, n: float, f: float) -> void { m4_identity(m) m[0] = 2.0 / (r - l) m[5] = 2.0 / (t - b) m[10] = -2.0 / (f - n) m[12] = -((r + l) / (r - l)) m[13] = -((t + b) / (t - b)) m[14] = -((f + n) / (f - n)) } function m4_look_at(m: floats, eye: floats, at: floats, up: floats) -> void { m4_look_at_xyz(m, eye[0], eye[1], eye[2], at[0], at[1], at[2], up[0], up[1], up[2]) } # the same from nine numbers, on scalars: the camera asks it every frame, and four scratch vectors # made and freed per call were churn function m4_look_at_xyz(m: floats, ex: float, ey: float, ez: float, ax: float, ay: float, az: float, ux: float, uy: float, uz: float) -> void { var fx = ax - ex var fy = ay - ey var fz = az - ez var fl = Math.sqrt(fx * fx + fy * fy + fz * fz) if fl > 0.0 { fx = fx / fl; fy = fy / fl; fz = fz / fl } var sx = fy * uz - fz * uy var sy = fz * ux - fx * uz var sz = fx * uy - fy * ux let sl = Math.sqrt(sx * sx + sy * sy + sz * sz) if sl > 0.0 { sx = sx / sl; sy = sy / sl; sz = sz / sl } let vx = sy * fz - sz * fy let vy = sz * fx - sx * fz let vz = sx * fy - sy * fx m4_identity(m) m[0] = sx; m[4] = sy; m[8] = sz m[1] = vx; m[5] = vy; m[9] = vz m[2] = -fx; m[6] = -fy; m[10] = -fz m[12] = -(sx * ex + sy * ey + sz * ez) m[13] = -(vx * ex + vy * ey + vz * ez) m[14] = fx * ex + fy * ey + fz * ez } # general 4x4 inverse (cofactor expansion); o may not alias a function m4_inverse(o: floats, a: floats) -> bool { let i0 = a[5] * (a[10] * a[15]) + -(a[5] * (a[11] * a[14])) - a[9] * (a[6] * a[15]) + (a[9] * (a[7] * a[14]) + (a[13] * (a[6] * a[11]) - a[13] * (a[7] * a[10]))) let i4 = -(a[4] * (a[10] * a[15])) + a[4] * (a[11] * a[14]) - a[8] * (a[7] * a[14]) + (a[8] * (a[6] * a[15]) + (a[12] * (a[7] * a[10]) - a[12] * (a[6] * a[11]))) let i8 = a[4] * (a[9] * a[15]) + -(a[4] * (a[11] * a[13])) - a[8] * (a[5] * a[15]) + (a[8] * (a[7] * a[13]) + (a[12] * (a[5] * a[11]) - a[12] * (a[7] * a[9]))) let i12 = -(a[4] * (a[9] * a[14])) + a[4] * (a[10] * a[13]) - a[8] * (a[6] * a[13]) + (a[8] * (a[5] * a[14]) + (a[12] * (a[6] * a[9]) - a[12] * (a[5] * a[10]))) let i1 = -(a[1] * (a[10] * a[15])) + a[1] * (a[11] * a[14]) - a[9] * (a[3] * a[14]) + (a[9] * (a[2] * a[15]) + (a[13] * (a[3] * a[10]) - a[13] * (a[2] * a[11]))) let i5 = a[0] * (a[10] * a[15]) + -(a[0] * (a[11] * a[14])) - a[8] * (a[2] * a[15]) + (a[8] * (a[3] * a[14]) + (a[12] * (a[2] * a[11]) - a[12] * (a[3] * a[10]))) let i9 = -(a[0] * (a[9] * a[15])) + a[0] * (a[11] * a[13]) - a[8] * (a[3] * a[13]) + (a[8] * (a[1] * a[15]) + (a[12] * (a[3] * a[9]) - a[12] * (a[1] * a[11]))) let i13 = a[0] * (a[9] * a[14]) + -(a[0] * (a[10] * a[13])) - a[8] * (a[1] * a[14]) + (a[8] * (a[2] * a[13]) + (a[12] * (a[1] * a[10]) - a[12] * (a[2] * a[9]))) let i2 = a[1] * (a[6] * a[15]) + -(a[1] * (a[7] * a[14])) - a[5] * (a[2] * a[15]) + (a[5] * (a[3] * a[14]) + (a[13] * (a[2] * a[7]) - a[13] * (a[3] * a[6]))) let i6 = -(a[0] * (a[6] * a[15])) + a[0] * (a[7] * a[14]) - a[4] * (a[3] * a[14]) + (a[4] * (a[2] * a[15]) + (a[12] * (a[3] * a[6]) - a[12] * (a[2] * a[7]))) let i10 = a[0] * (a[5] * a[15]) + -(a[0] * (a[7] * a[13])) - a[4] * (a[1] * a[15]) + (a[4] * (a[3] * a[13]) + (a[12] * (a[1] * a[7]) - a[12] * (a[3] * a[5]))) let i14 = -(a[0] * (a[5] * a[14])) + a[0] * (a[6] * a[13]) - a[4] * (a[2] * a[13]) + (a[4] * (a[1] * a[14]) + (a[12] * (a[2] * a[5]) - a[12] * (a[1] * a[6]))) let i3 = -(a[1] * (a[6] * a[11])) + a[1] * (a[7] * a[10]) - a[5] * (a[3] * a[10]) + (a[5] * (a[2] * a[11]) + (a[9] * (a[3] * a[6]) - a[9] * (a[2] * a[7]))) let i7 = a[0] * (a[6] * a[11]) + -(a[0] * (a[7] * a[10])) - a[4] * (a[2] * a[11]) + (a[4] * (a[3] * a[10]) + (a[8] * (a[2] * a[7]) - a[8] * (a[3] * a[6]))) let i11 = -(a[0] * (a[5] * a[11])) + a[0] * (a[7] * a[9]) - a[4] * (a[3] * a[9]) + (a[4] * (a[1] * a[11]) + (a[8] * (a[3] * a[5]) - a[8] * (a[1] * a[7]))) let i15 = a[0] * (a[5] * a[10]) + -(a[0] * (a[6] * a[9])) - a[4] * (a[1] * a[10]) + (a[4] * (a[2] * a[9]) + (a[8] * (a[1] * a[6]) - a[8] * (a[2] * a[5]))) let det = a[0] * i0 + a[1] * i4 + (a[2] * i8 + a[3] * i12) if det == 0.0 { return false } let id = 1.0 / det o[0] = i0 * id; o[1] = i1 * id; o[2] = i2 * id; o[3] = i3 * id; o[4] = i4 * id; o[5] = i5 * id; o[6] = i6 * id; o[7] = i7 * id o[8] = i8 * id; o[9] = i9 * id; o[10] = i10 * id; o[11] = i11 * id; o[12] = i12 * id; o[13] = i13 * id; o[14] = i14 * id; o[15] = i15 * id return true } # transform a point (w = 1) by m: o = m * (x, y, z, 1), returns w function m4_xform_point(o: floats, m: floats, x: float, y: float, z: float) -> float { o[0] = m[0] * x + m[4] * y + (m[8] * z + m[12]) o[1] = m[1] * x + m[5] * y + (m[9] * z + m[13]) o[2] = m[2] * x + m[6] * y + (m[10] * z + m[14]) return m[3] * x + m[7] * y + (m[11] * z + m[15]) } # ---- uniforms: see gpu.ludic (gpu_uniform and the u_* setters) ----