diff --git a/build.zig b/build.zig index 4a260c8..6ace546 100644 --- a/build.zig +++ b/build.zig @@ -87,6 +87,14 @@ pub fn build(b: *std.Build) void { .optimize = .ReleaseFast, }), }); + const silicon_sse_obj = b.addObject(.{ + .name = "silicon_sse", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/silicon/silicon_sse.zig"), + .target = target, + .optimize = .ReleaseFast, + }), + }); const lib = b.addLibrary(.{ .name = "weirdutils", @@ -105,6 +113,7 @@ pub fn build(b: *std.Build) void { lib.root_module.addObject(bone_sse_obj); lib.root_module.addObject(bone_sse_ref_obj); lib.root_module.addObject(math_sse_obj); + lib.root_module.addObject(silicon_sse_obj); b.installArtifact(lib); // Benchmark harness — native x86 Linux executable for profiling SSE replacements @@ -131,7 +140,19 @@ pub fn build(b: *std.Build) void { .optimize = bench_optimize, }), }); + // Link at high address so WoW PE sections (0x400000-0xD00000) can be + // mapped at their original virtual addresses for benchmarking. + bench.image_base = 0x10000000; + const bench_silicon_sse = b.addObject(.{ + .name = "bench_silicon_sse", + .root_module = b.createModule(.{ + .root_source_file = b.path("src/silicon/silicon_sse.zig"), + .target = bench_target, + .optimize = .ReleaseFast, + }), + }); bench.root_module.addObject(bench_math_sse); + bench.root_module.addObject(bench_silicon_sse); bench.root_module.linkSystemLibrary("m", .{}); const install_bench = b.addInstallArtifact(bench, .{}); const bench_step = b.step("bench", "Build math_sse benchmark harness (x86 Linux)"); diff --git a/src/bench/main.zig b/src/bench/main.zig index b174074..051d034 100644 --- a/src/bench/main.zig +++ b/src/bench/main.zig @@ -30,6 +30,26 @@ extern fn evaluatePolynomial(u32, u32, u32) f64; extern fn calculatePlaneNormal(u32, u32, u32, u32) void; extern fn transformAABox(u32, u32, u32, u32, u32) void; +// silicon_sse.zig exports +extern fn si_normalizeVec3(u32, u32) void; +extern fn si_mulMat3x4(u32, u32, u32) u32; +extern fn si_rotateMatByQuat(u32, u32) u32; +extern fn si_createRotMat3x4(u32, u32, u32, u32) u32; +extern fn si_distanceToPlane(u32, u32, u32) f64; +extern fn si_classifyPointFrustum(u32, u32, u32) u32; +extern fn si_checkBoxLineIntersect(u32, u32, u32) u32; +extern fn si_testOBBFrustum(u32, u32, u32, u32) u32; +extern fn si_testSphereFrustum(u32, u32) u32; +extern fn si_quatSlerp(u32, u32, u32, u32) u32; +extern fn si_isPointInsideBounds(u32, u32) u32; +extern fn si_calculateSinCos(u32, u32, u32) void; +extern fn si_createZRotMat3x3(u32, u32) u32; +extern fn si_transposeMat4x4(u32, u32) u32; +extern fn si_mulMat3x4InPlace(u32, u32) u32; +extern fn si_normalizeVec3InPlace(u32) void; +extern fn si_vec3Dot(u32, u32) f64; +extern fn si_translateBoundingVol(u32, u32) void; + // ========================================================================= // Infrastructure // ========================================================================= @@ -51,18 +71,41 @@ fn makeExecutable(comptime bytes: []const u8) ?[*]const u8 { return mem.ptr; } -fn mapGameConstants() bool { +/// Map WoW PE sections at their original virtual addresses. +/// .text (code) at 0x401000 + .rdata (constants) at 0x7FF000. +/// Resolves all intra-code CALL targets and float constant references. +const TEXT_START: usize = 0x401000; +const TEXT_SIZE: usize = 4186112; +const RDATA_START: usize = 0x7FF000; +const RDATA_SIZE: usize = 163840; +const wow_text_data = @embedFile("wow_text.bin"); +const wow_rdata_data = @embedFile("wow_rdata.bin"); + +var sections_mapped: bool = false; + +fn mapFixedSection(addr: usize, size: usize, data: []const u8, exec: bool) bool { + const prot: linux.PROT = if (exec) .{ .READ = true, .WRITE = true, .EXEC = true } else .{ .READ = true, .WRITE = true }; const mem = posix.mmap( - @ptrFromInt(0x007ff000), 4096, - .{ .READ = true, .WRITE = true }, + @ptrFromInt(addr), size, prot, .{ .TYPE = .PRIVATE, .ANONYMOUS = true, .FIXED = true }, -1, 0, ) catch return false; - const p: *f32 = @ptrCast(@alignCast(&mem[0x9d8])); - p.* = 1.0; + @memcpy(mem[0..data.len], data); return true; } +fn mapWowSections() bool { + if (sections_mapped) return true; + if (!mapFixedSection(TEXT_START, TEXT_SIZE, wow_text_data, true)) return false; + if (!mapFixedSection(RDATA_START, RDATA_SIZE, wow_rdata_data, false)) return false; + sections_mapped = true; + return true; +} + +fn origFn(comptime T: type, addr: usize) *const T { + return @ptrFromInt(addr); +} + inline fn rdtsc() u64 { var lo: u32 = undefined; var hi: u32 = undefined; @@ -134,8 +177,9 @@ fn tm3b() Mat3 { return .{ 0.5, -0.1, 0.3, 0.2, 1.0, -0.2, -0.1, 0.4, 0.8 }; } // ========================================================================= pub fn main() void { - if (!mapGameConstants()) { - print("WARNING: could not map game constants at 0x7ff000\n", .{}); + if (!mapWowSections()) { + print("FATAL: could not map WoW PE sections\n", .{}); + return; } print("\nmath_sse benchmark -- {d}M iterations per function\n", .{ITERS / 1_000_000}); @@ -245,7 +289,7 @@ pub fn main() void { { const va = tv3(); const vb = tv3b(); - const of: *const fn (u32, u32) callconv(cc_fc) f64 = @ptrCast(makeExecutable(&originals.dotProduct) orelse unreachable); + const of: *const fn (u32, u32) callconv(cc_fc) f64 = origFn(fn (u32, u32) callconv(cc_fc) f64, 0x602630); const ov = of(a(&va), a(&vb)); const sv = dotProduct(a(&va), a(&vb)); const ok = @abs(ov - sv) < 1e-4; @@ -389,6 +433,215 @@ pub fn main() void { report("evalPoly(inlined)", t, s, ok); } + // ===================================================================== + // Silicon SSE functions (src/silicon/silicon_sse.zig) + // ===================================================================== + print("\n{s}\n", .{"--- SILICON SSE functions ---"}); + + // si_isPointInsideBounds (1.7M/7.5s) -- fastcall(vecA_ECX, vecB_EDX) -> u32 + { + const va2 = tv3(); + const vb2 = Vec3{ 1.0, -3.0, 0.5 }; // all <= va + const of: *const fn (u32, u32) callconv(cc_fc) u32 = origFn(fn (u32, u32) callconv(cc_fc) u32, 0x699330); + const ov = of(a(&va2), a(&vb2)); + const sv = si_isPointInsideBounds(a(&va2), a(&vb2)); + const ok = ov == sv; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&va2), a(&vb2)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_isPointInsideBounds(a(&va2), a(&vb2)); } s = rdtsc() - s; + report("isPointInsideBounds", t, s, ok); + } + + // si_vec3Dot (31K/7.5s) -- fastcall(vecA_ECX, vecB_EDX) -> f64 + { + const va2 = tv3(); + const vb2 = tv3b(); + const of = origFn(fn (u32, u32) callconv(cc_fc) f64, 0x602630); + const ov = of(a(&va2), a(&vb2)); + const sv = si_vec3Dot(a(&va2), a(&vb2)); + const ok = @abs(ov - sv) < 1e-4; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&va2), a(&vb2)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_vec3Dot(a(&va2), a(&vb2)); } s = rdtsc() - s; + report("si_vec3Dot", t, s, ok); + } + + // si_normalizeVec3InPlace -- fastcall(vec3_ECX) -> void + { + var vo = tv3(); + var vs = tv3(); + const of: *const fn (u32) callconv(cc_fc) void = origFn(fn (u32) callconv(cc_fc) void, 0x6720F0); + of(a(&vo)); + si_normalizeVec3InPlace(a(&vs)); + const ok = cmpSlice(&vo, &vs); + var t = rdtsc(); for (0..ITERS) |_| { vo = tv3(); of(a(&vo)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { vs = tv3(); si_normalizeVec3InPlace(a(&vs)); } s = rdtsc() - s; + report("normalizeVec3InPlace", t, s, ok); + } + + // si_distanceToPlane (525K/7.5s) -- fastcall(point_ECX, plane_EDX, dir_stack) -> f64 + { + const pt = tv3(); + const plane = [4]f32{ 0.0, 1.0, 0.0, -5.0 }; // y=5 plane + const dir = Vec3{ 0.0, -1.0, 0.0 }; // pointing down + const of: *const fn (u32, u32, u32) callconv(cc_fc) f64 = origFn(fn (u32, u32, u32) callconv(cc_fc) f64, 0x6329E0); + const ov = of(a(&pt), a(&plane), a(&dir)); + const sv = si_distanceToPlane(a(&pt), a(&plane), a(&dir)); + const ok = @abs(ov - sv) < 1e-2; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&pt), a(&plane), a(&dir)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_distanceToPlane(a(&pt), a(&plane), a(&dir)); } s = rdtsc() - s; + report("distanceToPlane", t, s, ok); + } + + // si_checkBoxLineIntersect (2.7M/7.5s) -- fastcall(box_ECX, start_EDX, end_stack) -> u32 + { + const box = [6]f32{ -1, -1, -1, 1, 1, 1 }; // unit cube + const ls = Vec3{ -2, 0, 0 }; + const le = Vec3{ 2, 0, 0 }; // line through center + const of: *const fn (u32, u32, u32) callconv(cc_fc) u32 = origFn(fn (u32, u32, u32) callconv(cc_fc) u32, 0x6DC5A0); + const ov = of(a(&box), a(&ls), a(&le)); + const sv = si_checkBoxLineIntersect(a(&box), a(&ls), a(&le)); + const ok = ov == sv; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&box), a(&ls), a(&le)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_checkBoxLineIntersect(a(&box), a(&ls), a(&le)); } s = rdtsc() - s; + report("checkBoxLineIntersect", t, s, ok); + } + + // si_classifyPointFrustum (3.2M/7.5s) -- thiscall(planes_ECX, point_stack, mask_stack) -> u32 + { + // 6 planes forming a unit cube frustum + var planes: [24]f32 = undefined; + const normals = [6][3]f32{ .{1,0,0}, .{-1,0,0}, .{0,1,0}, .{0,-1,0}, .{0,0,1}, .{0,0,-1} }; + for (0..6) |i| { planes[i*4] = normals[i][0]; planes[i*4+1] = normals[i][1]; planes[i*4+2] = normals[i][2]; planes[i*4+3] = -5; } + const pt = Vec3{ 0, 0, 0 }; // inside + var mask_o: u32 = 0; + var mask_s: u32 = 0; + const of: *const fn (u32, u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32, u32) callconv(cc_tc) u32, 0x686C20); + _ = of(a(&planes), a(&pt), a(&mask_o)); + _ = si_classifyPointFrustum(a(&planes), a(&pt), a(&mask_s)); + const ok = mask_o == mask_s; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&planes), a(&pt), a(&mask_o)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_classifyPointFrustum(a(&planes), a(&pt), a(&mask_s)); } s = rdtsc() - s; + report("classifyPointFrustum", t, s, ok); + } + + // si_testSphereFrustum (375K/7.5s) -- thiscall(planes_ECX, sphere_stack) -> u32 + { + var planes: [24]f32 = undefined; + const normals = [6][3]f32{ .{1,0,0}, .{-1,0,0}, .{0,1,0}, .{0,-1,0}, .{0,0,1}, .{0,0,-1} }; + for (0..6) |i| { planes[i*4] = normals[i][0]; planes[i*4+1] = normals[i][1]; planes[i*4+2] = normals[i][2]; planes[i*4+3] = -5; } + const sphere = [4]f32{ 0, 0, 0, 1 }; // center origin, radius 1 + const of: *const fn (u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32) callconv(cc_tc) u32, 0x686B80); + const ov = of(a(&planes), a(&sphere)); + const sv = si_testSphereFrustum(a(&planes), a(&sphere)); + const ok = ov == sv; + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&planes), a(&sphere)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_testSphereFrustum(a(&planes), a(&sphere)); } s = rdtsc() - s; + report("testSphereFrustum", t, s, ok); + } + + // si_transposeMat4x4 -- thiscall(src_ECX, dst_stack) -> u32 + { + const src = tm4(); + var dst_o: Mat4 = undefined; + var dst_s: Mat4 = undefined; + const of: *const fn (u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32) callconv(cc_tc) u32, 0x7BCEF0); + _ = of(a(&src), a(&dst_o)); + _ = si_transposeMat4x4(a(&src), a(&dst_s)); + const ok = cmpSlice(&dst_o, &dst_s); + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&src), a(&dst_o)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_transposeMat4x4(a(&src), a(&dst_s)); } s = rdtsc() - s; + report("transposeMat4x4", t, s, ok); + } + + // si_quatSlerp -- fastcall(out_ECX, quatA_EDX, t_stack, quatB_stack) -> u32 + { + const qa = [4]f32{ 1, 0, 0, 0 }; + const qb = [4]f32{ 0.707, 0, 0.707, 0 }; + const tb: u32 = @bitCast(@as(f32, 0.5)); + var ro: [4]f32 = undefined; + var rs: [4]f32 = undefined; + const of = origFn(fn (u32, u32, u32, u32) callconv(cc_fc) u32, 0x7C0570); + _ = of(a(&ro), a(&qa), tb, a(&qb)); + _ = si_quatSlerp(a(&rs), a(&qa), tb, a(&qb)); + const ok = cmpSlice(&ro, &rs); + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&ro), a(&qa), tb, a(&qb)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_quatSlerp(a(&rs), a(&qa), tb, a(&qb)); } s = rdtsc() - s; + report("quatSlerp", t, s, ok); + } + + // si_createZRotMat3x3 -- thiscall(out_ECX, angle_stack) -> u32 + { + const ab2: u32 = @bitCast(@as(f32, 0.7854)); + var ro: Mat3 = undefined; + var rs: Mat3 = undefined; + const of: *const fn (u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32) callconv(cc_tc) u32, 0x7BE5B0); + _ = of(a(&ro), ab2); + _ = si_createZRotMat3x3(a(&rs), ab2); + const ok = cmpSlice(&ro, &rs); + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&ro), ab2); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_createZRotMat3x3(a(&rs), ab2); } s = rdtsc() - s; + report("createZRotMat3x3", t, s, ok); + } + + // si_mulMat3x4 -- fastcall(out_ECX, matA_EDX, matB_stack) -> u32 + { + const ma = [12]f32{ 1,0,0, 0,1,0, 0,0,1, 1,2,3 }; + const mb = [12]f32{ 0,1,0, -1,0,0, 0,0,1, 4,5,6 }; + var ro: [12]f32 = undefined; + var rs: [12]f32 = undefined; + const of: *const fn (u32, u32, u32) callconv(cc_fc) u32 = origFn(fn (u32, u32, u32) callconv(cc_fc) u32, 0x7BAE60); + _ = of(a(&ro), a(&ma), a(&mb)); + _ = si_mulMat3x4(a(&rs), a(&ma), a(&mb)); + const ok = cmpSlice(&ro, &rs); + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&ro), a(&ma), a(&mb)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_mulMat3x4(a(&rs), a(&ma), a(&mb)); } s = rdtsc() - s; + report("mulMat3x4", t, s, ok); + } + + // si_rotateMatByQuat -- thiscall(mat_ECX, quat_stack) -> u32 + { + const quat2 = [4]f32{ 0.0, 0.383, 0.0, 0.924 }; // ~45 deg Y + var mo = tm4(); + var ms = tm4(); + const of: *const fn (u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32) callconv(cc_tc) u32, 0x7BDDB0); + _ = of(a(&mo), a(&quat2)); + _ = si_rotateMatByQuat(a(&ms), a(&quat2)); + const ok = cmpSlice(&mo, &ms); + mo = tm4(); ms = tm4(); + var t = rdtsc(); for (0..ITERS) |_| { mo = tm4(); _ = of(a(&mo), a(&quat2)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { ms = tm4(); _ = si_rotateMatByQuat(a(&ms), a(&quat2)); } s = rdtsc() - s; + report("rotateMatByQuat", t, s, ok); + } + + // si_createRotMat3x4 -- fastcall(out_ECX, axis_EDX, angle_stack, isNorm_stack) -> u32 + { + const axis2 = Vec3{ 0, 1, 0 }; + const ab2: u32 = @bitCast(@as(f32, 0.7854)); + var ro: [12]f32 = undefined; + var rs: [12]f32 = undefined; + const of: *const fn (u32, u32, u32, u32) callconv(cc_fc) u32 = origFn(fn (u32, u32, u32, u32) callconv(cc_fc) u32, 0x7BB860); + _ = of(a(&ro), a(&axis2), ab2, 1); + _ = si_createRotMat3x4(a(&rs), a(&axis2), ab2, 1); + const ok = cmpSlice(&ro, &rs); + var t = rdtsc(); for (0..ITERS) |_| { _ = of(a(&ro), a(&axis2), ab2, 1); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { _ = si_createRotMat3x4(a(&rs), a(&axis2), ab2, 1); } s = rdtsc() - s; + report("createRotMat3x4", t, s, ok); + } + + // si_mulMat3x4InPlace -- thiscall(matA_ECX, matB_stack) -> u32 + { + const mb2 = [12]f32{ 0,1,0, -1,0,0, 0,0,1, 4,5,6 }; + const tmpl2 = [12]f32{ 1,0,0, 0,1,0, 0,0,1, 1,2,3 }; + var mo2 = tmpl2; + var ms2 = tmpl2; + const of: *const fn (u32, u32) callconv(cc_tc) u32 = origFn(fn (u32, u32) callconv(cc_tc) u32, 0x7BB420); + _ = of(a(&mo2), a(&mb2)); + _ = si_mulMat3x4InPlace(a(&ms2), a(&mb2)); + const ok = cmpSlice(&mo2, &ms2); + var t = rdtsc(); for (0..ITERS) |_| { mo2 = tmpl2; _ = of(a(&mo2), a(&mb2)); } t = rdtsc() - t; + var s = rdtsc(); for (0..ITERS) |_| { ms2 = tmpl2; _ = si_mulMat3x4InPlace(a(&ms2), a(&mb2)); } s = rdtsc() - s; + report("mulMat3x4InPlace", t, s, ok); + } + print("\n", .{}); } diff --git a/src/bench/wow_rdata.bin b/src/bench/wow_rdata.bin new file mode 100644 index 0000000..262c6ab Binary files /dev/null and b/src/bench/wow_rdata.bin differ diff --git a/src/bench/wow_text.bin b/src/bench/wow_text.bin new file mode 100644 index 0000000..be83b2c Binary files /dev/null and b/src/bench/wow_text.bin differ diff --git a/src/silicon/silicon_sse.zig b/src/silicon/silicon_sse.zig new file mode 100644 index 0000000..6d0e19f --- /dev/null +++ b/src/silicon/silicon_sse.zig @@ -0,0 +1,316 @@ +//! SSE math implementations for silicon module. +//! +//! Pure math functions extracted from silicon.zig for standalone compilation. +//! Used by both the DLL (via silicon.zig wrappers) and the bench harness. +//! All functions use C ABI (export fn) — callers handle CC translation. +//! +//! Functions that depend on game state (globals, hook trampolines, game structs) +//! remain in silicon.zig and are not benchmarkable in isolation. + +const std = @import("std"); + +// --- 0x4549C0: normalizeVec3 (137K/7.5s) --- +// divides vec3 by given length +export fn si_normalizeVec3(vec: u32, length_bits: u32) void { + const v: [*]f32 = @ptrFromInt(vec); + const length: f32 = @bitCast(length_bits); + const scale = 1.0 / length; + v[0] *= scale; + v[1] *= scale; + v[2] *= scale; +} + +// --- 0x7BAE60: mulMat3x4 --- +// out = A * B (3x4 layout: 3x3 rotation + 3 translation) +export fn si_mulMat3x4(out: u32, a_ptr: u32, b_ptr: u32) u32 { + const dst: [*]f32 = @ptrFromInt(out); + const a: [*]const f32 = @ptrFromInt(a_ptr); + const b: [*]const f32 = @ptrFromInt(b_ptr); + inline for (0..3) |row| { + inline for (0..3) |col| { + dst[row * 3 + col] = a[col] * b[row * 3] + a[col + 3] * b[row * 3 + 1] + a[col + 6] * b[row * 3 + 2]; + } + } + inline for (0..3) |col| { + dst[9 + col] = a[col] * b[9] + a[col + 3] * b[10] + a[col + 6] * b[11] + a[9 + col]; + } + return out; +} + +// --- 0x7BDDB0: rotateMatByQuat --- +// builds rotation matrix from quaternion, multiplies with existing 4x4 matrix +export fn si_rotateMatByQuat(mat: u32, quat: u32) u32 { + const m: [*]f32 = @ptrFromInt(mat); + const q: [*]const f32 = @ptrFromInt(quat); + const x = q[0]; + const y = q[1]; + const z = q[2]; + const w = q[3]; + const x2 = x + x; + const y2 = y + y; + const z2 = z + z; + const xx = x * x2; + const xy = x * y2; + const xz = x * z2; + const yy = y * y2; + const yz = y * z2; + const zz = z * z2; + const wx = w * x2; + const wy = w * y2; + const wz = w * z2; + var r: [16]f32 = undefined; + r[0] = 1.0 - (yy + zz); r[1] = xy + wz; r[2] = xz - wy; r[3] = 0; + r[4] = xy - wz; r[5] = 1.0 - (xx + zz); r[6] = yz + wx; r[7] = 0; + r[8] = xz + wy; r[9] = yz - wx; r[10] = 1.0 - (xx + yy); r[11] = 0; + r[12] = 0; r[13] = 0; r[14] = 0; r[15] = 1; + var tmp: [16]f32 = undefined; + inline for (0..4) |row| { + inline for (0..4) |col| { + tmp[row * 4 + col] = r[row * 4] * m[col] + r[row * 4 + 1] * m[4 + col] + r[row * 4 + 2] * m[8 + col] + r[row * 4 + 3] * m[12 + col]; + } + } + inline for (0..16) |i| { m[i] = tmp[i]; } + return mat; +} + +// --- 0x7BB860: createRotMat3x4 --- +// Rodrigues rotation matrix, 3x4 layout (3x3 rot + zero translation) +export fn si_createRotMat3x4(out: u32, axis_ptr: u32, angle_bits: u32, is_normalized: u32) u32 { + const m: [*]f32 = @ptrFromInt(out); + const ax: [*]const f32 = @ptrFromInt(axis_ptr); + var x = ax[0]; var y = ax[1]; var z = ax[2]; + if (is_normalized == 0) { + const len = @sqrt(x * x + y * y + z * z); + if (len > 1.0e-20) { const inv = 1.0 / len; x *= inv; y *= inv; z *= inv; } + } + const angle: f32 = @bitCast(angle_bits); + const c = @cos(angle); const s = @sin(angle); const t = 1.0 - c; + m[0] = t * x * x + c; m[1] = t * x * y + s * z; m[2] = t * x * z - s * y; + m[3] = t * x * y - s * z; m[4] = t * y * y + c; m[5] = t * y * z + s * x; + m[6] = t * x * z + s * y; m[7] = t * y * z - s * x; m[8] = t * z * z + c; + m[9] = 0; m[10] = 0; m[11] = 0; + return out; +} + +// --- 0x6329E0: distanceToPlane (525K/7.5s) --- +// (dot(point,normal)+d) / dot(direction,normal) +export fn si_distanceToPlane(point: u32, plane: u32, direction: u32) f64 { + const p: [*]const f32 = @ptrFromInt(point); + const pl: [*]const f32 = @ptrFromInt(plane); + const dir: [*]const f32 = @ptrFromInt(direction); + const dot1 = p[0] * pl[0] + p[1] * pl[1] + p[2] * pl[2] + pl[3]; + const dot2 = dir[0] * pl[0] + dir[1] * pl[1] + dir[2] * pl[2]; + if (@abs(dot2) < 1.0e-20) return 0.0; + return @floatCast(dot1 / dot2); +} + +// --- 0x686C20: classifyPointFrustum (3.2M/7.5s) --- +// tests point against 6 frustum planes, produces 6-bit bitmask +export fn si_classifyPointFrustum(planes_ptr: u32, point: u32, out_mask: u32) u32 { + const p: [*]const f32 = @ptrFromInt(point); + const mask: *u32 = @ptrFromInt(out_mask); + const planes: [*]const f32 = @ptrFromInt(planes_ptr); + const px = p[0]; const py = p[1]; const pz = p[2]; + var bits: u32 = 0; + inline for (0..6) |i| { + const pl = planes + i * 4; + const dist = px * pl[0] + py * pl[1] + pz * pl[2] + pl[3]; + if (dist < 0) bits |= (@as(u32, 1) << @intCast(i)); + } + mask.* = bits; + return planes_ptr; +} + +// --- 0x6DC5A0: checkBoxLineIntersect (2.7M/7.5s) --- +// slab-method AABB-line intersection +export fn si_checkBoxLineIntersect(box_ptr: u32, line_start: u32, line_end: u32) u32 { + const bmin: [*]const f32 = @ptrFromInt(box_ptr); + const bmax: [*]const f32 = @ptrFromInt(box_ptr + 0xC); + const start: [*]const f32 = @ptrFromInt(line_start); + const end: [*]const f32 = @ptrFromInt(line_end); + var tmin: f32 = 0.0; + var tmax: f32 = 1.0; + inline for (0..3) |i| { + const dir = end[i] - start[i]; + if (@abs(dir) < 1.0e-20) { + if (start[i] < bmin[i] or start[i] > bmax[i]) return 0; + } else { + const inv_dir = 1.0 / dir; + var t0 = (bmin[i] - start[i]) * inv_dir; + var t1 = (bmax[i] - start[i]) * inv_dir; + if (t0 > t1) { const tmp = t0; t0 = t1; t1 = tmp; } + if (t0 > tmin) tmin = t0; + if (t1 < tmax) tmax = t1; + if (tmin > tmax) return 0; + } + } + return 1; +} + +// --- 0x6869C0: testOBBFrustum --- +// tests oriented bounding box against 6 frustum planes +export fn si_testOBBFrustum(planes_ptr: u32, aabb_ptr: u32, rot_ptr: u32, trans_ptr: u32) u32 { + const planes: [*]const f32 = @ptrFromInt(planes_ptr); + const aabb: [*]const f32 = @ptrFromInt(aabb_ptr); + const rot: [*]const f32 = @ptrFromInt(rot_ptr); + const trans: [*]const f32 = @ptrFromInt(trans_ptr); + const min2 = [3]f32{ aabb[0], aabb[1], aabb[2] }; + const max2 = [3]f32{ aabb[3], aabb[4], aabb[5] }; + var corners: [8][3]f32 = undefined; + inline for (0..8) |i| { + const lx = if (i & 1 != 0) max2[0] else min2[0]; + const ly = if (i & 2 != 0) max2[1] else min2[1]; + const lz = if (i & 4 != 0) max2[2] else min2[2]; + corners[i][0] = rot[0] * lx + rot[3] * ly + rot[6] * lz + trans[0]; + corners[i][1] = rot[1] * lx + rot[4] * ly + rot[7] * lz + trans[1]; + corners[i][2] = rot[2] * lx + rot[5] * ly + rot[8] * lz + trans[2]; + } + inline for (0..6) |p| { + const pl = planes + p * 4; + var all_outside = true; + inline for (0..8) |c| { + const dist = corners[c][0] * pl[0] + corners[c][1] * pl[1] + corners[c][2] * pl[2] + pl[3]; + if (dist >= 0) all_outside = false; + } + if (all_outside) return 0; + } + return 3; +} + +// --- 0x686B80: testSphereFrustum (375K/7.5s) --- +export fn si_testSphereFrustum(planes_ptr: u32, sphere: u32) u32 { + const planes: [*]const f32 = @ptrFromInt(planes_ptr); + const s: [*]const f32 = @ptrFromInt(sphere); + const cx = s[0]; const cy = s[1]; const cz = s[2]; const r = s[3]; + inline for (0..6) |i| { + const pl = planes + i * 4; + const dist = cx * pl[0] + cy * pl[1] + cz * pl[2] + pl[3]; + if (dist < -r) return 0; + } + return 3; +} + +// --- 0x7C0570: quatSlerp --- +export fn si_quatSlerp(out: u32, a_ptr: u32, t_bits: u32, b_ptr: u32) u32 { + const dst: [*]f32 = @ptrFromInt(out); + const a: [*]const f32 = @ptrFromInt(a_ptr); + const b_raw: [*]const f32 = @ptrFromInt(b_ptr); + const t: f32 = @bitCast(t_bits); + var dot_val = a[0] * b_raw[0] + a[1] * b_raw[1] + a[2] * b_raw[2] + a[3] * b_raw[3]; + var sign: f32 = 1.0; + if (dot_val < 0) { dot_val = -dot_val; sign = -1.0; } + var s0: f32 = undefined; + var s1: f32 = undefined; + if (dot_val > 0.9995) { + s0 = 1.0 - t; + s1 = t * sign; + } else { + const theta = std.math.acos(dot_val); + const sin_theta = @sin(theta); + const inv_sin = 1.0 / sin_theta; + s0 = @sin((1.0 - t) * theta) * inv_sin; + s1 = @sin(t * theta) * inv_sin * sign; + } + dst[0] = s0 * a[0] + s1 * b_raw[0]; + dst[1] = s0 * a[1] + s1 * b_raw[1]; + dst[2] = s0 * a[2] + s1 * b_raw[2]; + dst[3] = s0 * a[3] + s1 * b_raw[3]; + return out; +} + +// --- 0x699330: isPointInsideBounds (1.7M/7.5s) --- +export fn si_isPointInsideBounds(a: u32, b: u32) u32 { + const va: [*]const f32 = @ptrFromInt(a); + const vb: [*]const f32 = @ptrFromInt(b); + if (vb[0] <= va[0] and vb[1] <= va[1] and vb[2] <= va[2]) return 1; + return 0; +} + +// --- 0x749280: calculateSinCos --- +export fn si_calculateSinCos(angle_bits: u32, out_sin: u32, out_cos: u32) void { + const angle: f32 = @bitCast(angle_bits); + const sp: *f32 = @ptrFromInt(out_sin); + const cp: *f32 = @ptrFromInt(out_cos); + sp.* = @sin(angle); + cp.* = @cos(angle); +} + +// --- 0x7BE5B0: createZRotMat3x3 --- +export fn si_createZRotMat3x3(out: u32, angle_bits: u32) u32 { + const m: [*]f32 = @ptrFromInt(out); + const angle: f32 = @bitCast(angle_bits); + const c = @cos(angle); const s = @sin(angle); + m[0] = c; m[1] = s; m[2] = 0; + m[3] = -s; m[4] = c; m[5] = 0; + m[6] = 0; m[7] = 0; m[8] = 1; + return out; +} + +// --- 0x7BCEF0: transposeMat4x4 --- +export fn si_transposeMat4x4(src: u32, dst: u32) u32 { + const s: [*]const f32 = @ptrFromInt(src); + const d: [*]f32 = @ptrFromInt(dst); + inline for (0..4) |row| { + inline for (0..4) |col| { + d[row * 4 + col] = s[col * 4 + row]; + } + } + return src; +} + +// --- 0x7BB420: mulMat3x4InPlace --- +// this = this * matB +export fn si_mulMat3x4InPlace(mat_a: u32, mat_b: u32) u32 { + const a: [*]f32 = @ptrFromInt(mat_a); + const b: [*]const f32 = @ptrFromInt(mat_b); + var tmp: [12]f32 = undefined; + inline for (0..3) |row| { + inline for (0..3) |col| { + tmp[row * 3 + col] = a[col] * b[row * 3] + a[col + 3] * b[row * 3 + 1] + a[col + 6] * b[row * 3 + 2]; + } + } + inline for (0..3) |col| { + tmp[9 + col] = a[col] * b[9] + a[col + 3] * b[10] + a[col + 6] * b[11] + a[9 + col]; + } + inline for (0..12) |i| { a[i] = tmp[i]; } + return mat_a; +} + +// --- 0x6720F0: normalizeVec3InPlace --- +export fn si_normalizeVec3InPlace(vec: u32) void { + const v: [*]f32 = @ptrFromInt(vec); + const len = @sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + if (len > 1.0e-20) { + const inv = 1.0 / len; + v[0] *= inv; + v[1] *= inv; + v[2] *= inv; + } +} + +// --- 0x602630: vec3Dot --- +export fn si_vec3Dot(a: u32, b: u32) f64 { + const va: [*]const f32 = @ptrFromInt(a); + const vb: [*]const f32 = @ptrFromInt(b); + return @floatCast(va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2]); +} + +// --- 0x686820: translateBoundingVol --- +// translates 8 corners, updates plane distances, translates min/max +export fn si_translateBoundingVol(this: u32, offset: u32) void { + const obj: [*]f32 = @ptrFromInt(this); + const off: [*]const f32 = @ptrFromInt(offset); + const dx = off[0]; const dy = off[1]; const dz = off[2]; + inline for (0..8) |i| { + const base = 24 + i * 3; + obj[base] += dx; + obj[base + 1] += dy; + obj[base + 2] += dz; + } + inline for (0..6) |i| { + const base = i * 4; + obj[base + 3] -= obj[base] * dx + obj[base + 1] * dy + obj[base + 2] * dz; + } + obj[48] += dx; obj[49] += dy; obj[50] += dz; + obj[51] += dx; obj[52] += dy; obj[53] += dz; +}