render3d: shadow_fit, water_reflection_pass, layer_partition_lods and the GPU cull's scratch are made with the state; v3_dist is scalar; the pushes into lists sized at start-up, the caps probe, the table growth, the loads and the constructors declared with their bounds (one statement a line); the renderer's name made once with the device; the two error messages given back; the dead lupine models removed. runtime: a component's text is held interned in its value cell (one copy per distinct text), so the getter's own text goes with its frame instead of being kept by ludic.ui's model - 80 of the 83 keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
4.7 KiB
Text
99 lines
4.7 KiB
Text
# ============================================================================
|
|
# quat.ludic — unit quaternions (x, y, z, w) as 4-word float-bit buffers, for
|
|
# skeletal poses. Same conventions as fmath.ludic: float bits in words, column-
|
|
# major matrices, o may alias its inputs unless stated.
|
|
# ============================================================================
|
|
|
|
@alloc_ok("a constructor: what it makes is its caller's (records made at start-up or once per figure)")
|
|
function q_new() -> floats { let q = floats(4); q_identity(q); return q }
|
|
function q_identity(q: floats) -> void { q[0] = 0.0; q[1] = 0.0; q[2] = 0.0; q[3] = 1.0 }
|
|
function q_set(q: words, x: int, y: int, z: int, w: int) -> void { q[0] = x; q[1] = y; q[2] = z; q[3] = w }
|
|
function q_copy(o: floats, a: floats) -> void { o[0] = a[0]; o[1] = a[1]; o[2] = a[2]; o[3] = a[3] }
|
|
# the idx-th quaternion of a packed buffer
|
|
function q_load(o: floats, src: floats, idx: int) -> void { for i in 0 .. 4 { o[i] = src[idx * 4 + i] } }
|
|
function q_store(dst: floats, idx: int, a: floats) -> void { for i in 0 .. 4 { dst[idx * 4 + i] = a[i] } }
|
|
|
|
# a rotation of `angle` radians about the unit axis (ax, ay, az)
|
|
function q_axis_angle(o: floats, ax: float, ay: float, az: float, angle: float) -> void {
|
|
let h = angle * 0.5
|
|
let s = Math.sin(h)
|
|
o[0] = ax * s; o[1] = ay * s; o[2] = az * s; o[3] = Math.cos(h)
|
|
}
|
|
# o = a * b (apply b first, then a); o may alias a or b
|
|
function q_mul(o: floats, a: floats, b: floats) -> void {
|
|
let ax = a[0]; let ay = a[1]; let az = a[2]; let aw = a[3]
|
|
let bx = b[0]; let by = b[1]; let bz = b[2]; let bw = b[3]
|
|
let x = aw * bx + ax * bw + ay * bz - az * by
|
|
let y = aw * by - ax * bz + ay * bw + az * bx
|
|
let z = aw * bz + ax * by - ay * bx + az * bw
|
|
let w = aw * bw - ax * bx - ay * by - az * bz
|
|
o[0] = x; o[1] = y; o[2] = z; o[3] = w
|
|
}
|
|
function q_conj(o: floats, a: floats) -> void { o[0] = -a[0]; o[1] = -a[1]; o[2] = -a[2]; o[3] = a[3] }
|
|
function q_normalize(q: floats) -> void {
|
|
let l = Math.sqrt(q[0] * q[0] + q[1] * q[1] + (q[2] * q[2] + q[3] * q[3]))
|
|
if l == 0.0 { q_identity(q); return }
|
|
let inv = 1.0 / l
|
|
for i in 0 .. 4 { q[i] = q[i] * inv }
|
|
}
|
|
# normalised linear blend from a to b (shortest arc), fine for the small steps a pose takes
|
|
function q_nlerp(o: floats, a: floats, b: floats, t: float) -> void {
|
|
var d = a[0] * b[0] + a[1] * b[1] + (a[2] * b[2] + a[3] * b[3])
|
|
var sg = 1.0
|
|
if d < 0.0 { sg = -1.0 }
|
|
for i in 0 .. 4 { o[i] = Math.lerp(a[i], b[i] * sg, t) }
|
|
q_normalize(o)
|
|
}
|
|
# rotate the vector v by q: o = q v q*
|
|
function q_rotate(o: floats, q: floats, v: floats) -> void {
|
|
let qx = q[0]; let qy = q[1]; let qz = q[2]; let qw = q[3]
|
|
# t = 2 * cross(q.xyz, v)
|
|
let tx = 2.0 * (qy * v[2] - qz * v[1])
|
|
let ty = 2.0 * (qz * v[0] - qx * v[2])
|
|
let tz = 2.0 * (qx * v[1] - qy * v[0])
|
|
# o = v + w t + cross(q.xyz, t)
|
|
let x = v[0] + qw * tx + (qy * tz - qz * ty)
|
|
let y = v[1] + qw * ty + (qz * tx - qx * tz)
|
|
let z = v[2] + qw * tz + (qx * ty - qy * tx)
|
|
o[0] = x; o[1] = y; o[2] = z
|
|
}
|
|
# pitch about X, yaw about Y, roll about Z, composed as yaw * pitch * roll
|
|
function q_euler(render3d_st: mut Render3dState, o: floats, pitch: float, yaw: float, roll: float) -> void {
|
|
if render3d_st.q_sy == null { # q_scratch is made with the state; its views once
|
|
render3d_st.q_sy = view(render3d_st.q_scratch, 4, 4)
|
|
render3d_st.q_sz = view(render3d_st.q_scratch, 8, 4)
|
|
render3d_st.q_st = view(render3d_st.q_scratch, 12, 4)
|
|
}
|
|
let qx = render3d_st.q_scratch; let qy = render3d_st.q_sy; let qz = render3d_st.q_sz; let t = render3d_st.q_st
|
|
q_axis_angle(qx, 1.0, 0.0, 0.0, pitch)
|
|
q_axis_angle(qy, 0.0, 1.0, 0.0, yaw)
|
|
q_axis_angle(qz, 0.0, 0.0, 1.0, roll)
|
|
q_mul(t, qy, qx)
|
|
q_mul(o, t, qz)
|
|
}
|
|
# the rotation matrix of q (column-major, translation cleared)
|
|
function q_to_m4(m: floats, q: floats) -> void {
|
|
let x = q[0]; let y = q[1]; let z = q[2]; let w = q[3]
|
|
let xx = x * x; let yy = y * y; let zz = z * z
|
|
let xy = x * y; let xz = x * z; let yz = y * z
|
|
let wx = w * x; let wy = w * y; let wz = w * z
|
|
m[0] = 1.0 - 2.0 * (yy + zz)
|
|
m[1] = 2.0 * (xy + wz)
|
|
m[2] = 2.0 * (xz - wy)
|
|
m[3] = 0.0
|
|
m[4] = 2.0 * (xy - wz)
|
|
m[5] = 1.0 - 2.0 * (xx + zz)
|
|
m[6] = 2.0 * (yz + wx)
|
|
m[7] = 0.0
|
|
m[8] = 2.0 * (xz + wy)
|
|
m[9] = 2.0 * (yz - wx)
|
|
m[10] = 1.0 - 2.0 * (xx + yy)
|
|
m[11] = 0.0
|
|
m[12] = 0.0; m[13] = 0.0; m[14] = 0.0; m[15] = 1.0
|
|
}
|
|
# m = translate(t) * rotate(q) * scale(s)
|
|
function m4_trs_q(m: floats, tx: float, ty: float, tz: float, q: floats, sx: float, sy: float, sz: float) -> void {
|
|
q_to_m4(m, q)
|
|
for r in 0 .. 3 { m[r] = m[r] * sx; m[4 + r] = m[4 + r] * sy; m[8 + r] = m[8 + r] * sz }
|
|
m[12] = tx; m[13] = ty; m[14] = tz
|
|
}
|