@bornengine/engine 0.4.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +231 -0
- package/native/android/Cargo.lock +1848 -0
- package/native/android/Cargo.toml +24 -0
- package/native/android/src/lib.rs +702 -0
- package/native/ios/Cargo.lock +1690 -0
- package/native/ios/Cargo.toml +32 -0
- package/native/ios/src/lib.rs +1267 -0
- package/native/linux/Cargo.lock +3279 -0
- package/native/linux/Cargo.toml +29 -0
- package/native/linux/src/lib.rs +1331 -0
- package/native/macos/Cargo.lock +3310 -0
- package/native/macos/Cargo.toml +46 -0
- package/native/macos/src/lib.rs +1302 -0
- package/native/shared/Cargo.lock +1899 -0
- package/native/shared/Cargo.toml +62 -0
- package/native/shared/assets/default_font.ttf +0 -0
- package/native/shared/build.rs +270 -0
- package/native/shared/shaders/common/clouds.wgsl +122 -0
- package/native/shared/shaders/common/fog.wgsl +16 -0
- package/native/shared/shaders/common/foliage_wind.wgsl +98 -0
- package/native/shared/shaders/common/imposter.wgsl +112 -0
- package/native/shared/shaders/common/pbr.wgsl +186 -0
- package/native/shared/shaders/common/shadows.wgsl +186 -0
- package/native/shared/shaders/common/sky.wgsl +8 -0
- package/native/shared/shaders/common/tonemap.wgsl +25 -0
- package/native/shared/shaders/impulse_field.wgsl +57 -0
- package/native/shared/shaders/material_abi.wgsl +383 -0
- package/native/shared/shaders/materials/test_minimal.wgsl +42 -0
- package/native/shared/src/anim_mixer.rs +61 -0
- package/native/shared/src/attach.rs +263 -0
- package/native/shared/src/audio/decode.rs +123 -0
- package/native/shared/src/audio/mod.rs +863 -0
- package/native/shared/src/audio/render.rs +892 -0
- package/native/shared/src/audio/spsc.rs +156 -0
- package/native/shared/src/audio/stream.rs +226 -0
- package/native/shared/src/custom_shaders.rs +104 -0
- package/native/shared/src/decals.rs +245 -0
- package/native/shared/src/drs.rs +211 -0
- package/native/shared/src/engine.rs +261 -0
- package/native/shared/src/ffi.rs +116 -0
- package/native/shared/src/ffi_core/assets.rs +388 -0
- package/native/shared/src/ffi_core/audio_ffi.rs +184 -0
- package/native/shared/src/ffi_core/draw.rs +334 -0
- package/native/shared/src/ffi_core/game_loop.rs +577 -0
- package/native/shared/src/ffi_core/input.rs +234 -0
- package/native/shared/src/ffi_core/mod.rs +127 -0
- package/native/shared/src/ffi_core/models.rs +1154 -0
- package/native/shared/src/ffi_core/ragdoll_ffi.rs +261 -0
- package/native/shared/src/ffi_core/scene.rs +626 -0
- package/native/shared/src/ffi_core/vfx.rs +212 -0
- package/native/shared/src/ffi_core/visual.rs +691 -0
- package/native/shared/src/frame_callbacks.rs +122 -0
- package/native/shared/src/geometry.rs +236 -0
- package/native/shared/src/handles.rs +182 -0
- package/native/shared/src/input.rs +448 -0
- package/native/shared/src/jolt_sys.rs +822 -0
- package/native/shared/src/lib.rs +55 -0
- package/native/shared/src/models.rs +1093 -0
- package/native/shared/src/models_gltf.rs +1280 -0
- package/native/shared/src/particles.rs +391 -0
- package/native/shared/src/physics_jolt.rs +1908 -0
- package/native/shared/src/picking.rs +298 -0
- package/native/shared/src/postfx.rs +345 -0
- package/native/shared/src/profiler.rs +492 -0
- package/native/shared/src/ragdoll.rs +474 -0
- package/native/shared/src/renderer/atmosphere_lut.rs +573 -0
- package/native/shared/src/renderer/brdf_lut.rs +154 -0
- package/native/shared/src/renderer/draw2d.rs +143 -0
- package/native/shared/src/renderer/formats.rs +822 -0
- package/native/shared/src/renderer/froxel.rs +421 -0
- package/native/shared/src/renderer/gi_bake.rs +653 -0
- package/native/shared/src/renderer/graph.rs +462 -0
- package/native/shared/src/renderer/hiz.rs +269 -0
- package/native/shared/src/renderer/hot_reload.rs +390 -0
- package/native/shared/src/renderer/impulse_field.rs +456 -0
- package/native/shared/src/renderer/lighting.rs +154 -0
- package/native/shared/src/renderer/material_instancing.rs +171 -0
- package/native/shared/src/renderer/material_pipeline.rs +700 -0
- package/native/shared/src/renderer/material_system.rs +1996 -0
- package/native/shared/src/renderer/material_system_tests.rs +601 -0
- package/native/shared/src/renderer/material_system_wasm.rs +41 -0
- package/native/shared/src/renderer/mod.rs +12556 -0
- package/native/shared/src/renderer/model_draw.rs +641 -0
- package/native/shared/src/renderer/occlusion.rs +429 -0
- package/native/shared/src/renderer/planar_pass.rs +593 -0
- package/native/shared/src/renderer/planar_reflection.rs +499 -0
- package/native/shared/src/renderer/post_pass.rs +249 -0
- package/native/shared/src/renderer/postfx_chain.rs +728 -0
- package/native/shared/src/renderer/pt_pass.rs +577 -0
- package/native/shared/src/renderer/scene_pass.rs +607 -0
- package/native/shared/src/renderer/shader_include.rs +205 -0
- package/native/shared/src/renderer/shader_library.rs +135 -0
- package/native/shared/src/renderer/shaders/ao.rs +570 -0
- package/native/shared/src/renderer/shaders/core.rs +1243 -0
- package/native/shared/src/renderer/shaders/env.rs +907 -0
- package/native/shared/src/renderer/shaders/gi.rs +810 -0
- package/native/shared/src/renderer/shaders/mod.rs +19 -0
- package/native/shared/src/renderer/shaders/post.rs +1558 -0
- package/native/shared/src/renderer/shaders/pt.rs +1859 -0
- package/native/shared/src/renderer/shaders/ssgi.rs +1586 -0
- package/native/shared/src/renderer/shadow_pass.rs +731 -0
- package/native/shared/src/renderer/ssgi_pass.rs +392 -0
- package/native/shared/src/renderer/ssr_pass.rs +188 -0
- package/native/shared/src/renderer/texture_store.rs +473 -0
- package/native/shared/src/renderer/transient.rs +591 -0
- package/native/shared/src/renderer/types.rs +941 -0
- package/native/shared/src/renderer/util.rs +152 -0
- package/native/shared/src/scene.rs +1362 -0
- package/native/shared/src/sdf_cache.rs +274 -0
- package/native/shared/src/shadows.rs +1036 -0
- package/native/shared/src/staging.rs +102 -0
- package/native/shared/src/string_header.rs +266 -0
- package/native/shared/src/text_renderer.rs +502 -0
- package/native/shared/src/textures.rs +197 -0
- package/native/tvos/Cargo.lock +1693 -0
- package/native/tvos/Cargo.toml +36 -0
- package/native/tvos/metal-patched/Cargo.toml +178 -0
- package/native/tvos/metal-patched/LICENSE-APACHE +201 -0
- package/native/tvos/metal-patched/LICENSE-MIT +25 -0
- package/native/tvos/metal-patched/src/acceleration_structure.rs +667 -0
- package/native/tvos/metal-patched/src/acceleration_structure_pass.rs +108 -0
- package/native/tvos/metal-patched/src/argument.rs +366 -0
- package/native/tvos/metal-patched/src/blitpass.rs +102 -0
- package/native/tvos/metal-patched/src/buffer.rs +71 -0
- package/native/tvos/metal-patched/src/capturedescriptor.rs +76 -0
- package/native/tvos/metal-patched/src/capturemanager.rs +113 -0
- package/native/tvos/metal-patched/src/commandbuffer.rs +192 -0
- package/native/tvos/metal-patched/src/commandqueue.rs +44 -0
- package/native/tvos/metal-patched/src/computepass.rs +107 -0
- package/native/tvos/metal-patched/src/constants.rs +152 -0
- package/native/tvos/metal-patched/src/counters.rs +119 -0
- package/native/tvos/metal-patched/src/depthstencil.rs +190 -0
- package/native/tvos/metal-patched/src/device.rs +2134 -0
- package/native/tvos/metal-patched/src/drawable.rs +39 -0
- package/native/tvos/metal-patched/src/encoder.rs +2041 -0
- package/native/tvos/metal-patched/src/heap.rs +281 -0
- package/native/tvos/metal-patched/src/indirect_encoder.rs +344 -0
- package/native/tvos/metal-patched/src/lib.rs +657 -0
- package/native/tvos/metal-patched/src/library.rs +902 -0
- package/native/tvos/metal-patched/src/mps.rs +575 -0
- package/native/tvos/metal-patched/src/pipeline/compute.rs +475 -0
- package/native/tvos/metal-patched/src/pipeline/mod.rs +71 -0
- package/native/tvos/metal-patched/src/pipeline/render.rs +762 -0
- package/native/tvos/metal-patched/src/renderpass.rs +443 -0
- package/native/tvos/metal-patched/src/resource.rs +182 -0
- package/native/tvos/metal-patched/src/sampler.rs +165 -0
- package/native/tvos/metal-patched/src/sync.rs +178 -0
- package/native/tvos/metal-patched/src/texture.rs +352 -0
- package/native/tvos/metal-patched/src/types.rs +90 -0
- package/native/tvos/metal-patched/src/vertexdescriptor.rs +250 -0
- package/native/tvos/src/audio_backend.rs +197 -0
- package/native/tvos/src/lib.rs +1891 -0
- package/native/visionos/Cargo.lock +1693 -0
- package/native/visionos/Cargo.toml +40 -0
- package/native/visionos/src/audio_backend.rs +197 -0
- package/native/visionos/src/lib.rs +1887 -0
- package/native/watchos/Cargo.lock +16 -0
- package/native/watchos/Cargo.toml +19 -0
- package/native/watchos/shaders/bloom_postfx.metal +99 -0
- package/native/watchos/src/BloomWatchApp.swift +1267 -0
- package/native/watchos/src/BloomWatchAudio.swift +179 -0
- package/native/watchos/src/audio.rs +55 -0
- package/native/watchos/src/draw_list.rs +229 -0
- package/native/watchos/src/ffi_stubs.rs +915 -0
- package/native/watchos/src/ffi_stubs_manual.rs +35 -0
- package/native/watchos/src/lib.rs +1124 -0
- package/native/watchos/src/models.rs +746 -0
- package/native/watchos/src/postfx.rs +95 -0
- package/native/watchos/src/scene.rs +534 -0
- package/native/watchos/src/textures.rs +184 -0
- package/native/web/Cargo.lock +1657 -0
- package/native/web/Cargo.toml +43 -0
- package/native/web/bloom_glue.js +695 -0
- package/native/web/build.sh +131 -0
- package/native/web/index.html +35 -0
- package/native/web/jolt_bridge.js +1519 -0
- package/native/web/src/input_ffi.rs +286 -0
- package/native/web/src/lib.rs +1796 -0
- package/native/web/src/material_ffi.rs +710 -0
- package/native/web/src/parity_ffi.rs +343 -0
- package/native/web/src/physics_ffi.rs +643 -0
- package/native/web/src/ragdoll_ffi.rs +250 -0
- package/native/web/src/render_settings.rs +98 -0
- package/native/windows/Cargo.lock +1815 -0
- package/native/windows/Cargo.toml +68 -0
- package/native/windows/src/lib.rs +1486 -0
- package/package.json +4279 -0
- package/src/audio/index.ts +315 -0
- package/src/core/colors.ts +63 -0
- package/src/core/index.ts +1206 -0
- package/src/core/keys.ts +63 -0
- package/src/core/types.ts +104 -0
- package/src/index.ts +171 -0
- package/src/math/index.ts +516 -0
- package/src/mobile/index.ts +294 -0
- package/src/models/index.ts +1258 -0
- package/src/physics/index.ts +1134 -0
- package/src/scene/index.ts +698 -0
- package/src/shapes/index.ts +120 -0
- package/src/text/index.ts +48 -0
- package/src/textures/index.ts +187 -0
- package/src/vfx/index.ts +191 -0
- package/src/world/index.ts +24 -0
- package/src/world/loader.ts +423 -0
- package/src/world/prefab.ts +217 -0
- package/src/world/render.ts +172 -0
- package/src/world/saver.ts +108 -0
- package/src/world/serialize.ts +301 -0
- package/src/world/terrain.ts +355 -0
- package/src/world/types.ts +160 -0
- package/src/world/validate.ts +319 -0
- package/src/world/version.ts +114 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
//! Disk cache for per-mesh signed distance fields baked by ticket 014.
|
|
2
|
+
//!
|
|
3
|
+
//! Each mesh content-hashes (positions + indices) to a 64-bit key; the
|
|
4
|
+
//! 32³ R32Float voxel data (~128 KB) lives under the platform cache
|
|
5
|
+
//! directory. Cold launches that hit the cache skip the GPU
|
|
6
|
+
//! brute-force point-triangle bake entirely and `queue.write_texture`
|
|
7
|
+
//! the bytes directly. Misses fall through to the existing in-process
|
|
8
|
+
//! bake; the renderer reads the texture back on the same frame and
|
|
9
|
+
//! writes the cache entry so the next launch hits.
|
|
10
|
+
//!
|
|
11
|
+
//! Caching is best-effort by design — every fallible operation
|
|
12
|
+
//! (hashing aside) returns `Option`/`Result` and the renderer treats
|
|
13
|
+
//! an error or `None` as "no cache, just bake."
|
|
14
|
+
//!
|
|
15
|
+
//! Web isn't supported here. The wasm32 build needs IndexedDB plumbing
|
|
16
|
+
//! before it can store anything; until then `cache_dir()` returns
|
|
17
|
+
//! `None` on wasm and the bake falls through normally.
|
|
18
|
+
|
|
19
|
+
use std::fs;
|
|
20
|
+
use std::io::{Read, Write};
|
|
21
|
+
use std::path::PathBuf;
|
|
22
|
+
|
|
23
|
+
/// File header written before the raw R32Float voxel bytes. Fixed
|
|
24
|
+
/// 16 bytes so a future version can extend without breaking layout
|
|
25
|
+
/// readers (the `version` byte is the gate).
|
|
26
|
+
const FILE_MAGIC: [u8; 6] = *b"BLSDF\0";
|
|
27
|
+
const FILE_VERSION: u8 = 1;
|
|
28
|
+
|
|
29
|
+
/// Voxel resolution we bake at. Mirrors `renderer::formats::MESH_SDF_RES`.
|
|
30
|
+
/// Hardcoded here rather than imported to keep the cache module
|
|
31
|
+
/// dependency-free of the renderer.
|
|
32
|
+
pub const VOXEL_RES: u32 = 32;
|
|
33
|
+
|
|
34
|
+
/// Total payload size: 32³ × f32. Exposed for callers sizing a staging
|
|
35
|
+
/// buffer or a `queue.write_texture` source slice.
|
|
36
|
+
pub const VOXEL_BYTES: usize = (VOXEL_RES as usize).pow(3) * 4;
|
|
37
|
+
|
|
38
|
+
/// Content hash of a mesh's geometry. Stable across Rust versions
|
|
39
|
+
/// because the underlying mix is FNV-1a over fixed little-endian bytes;
|
|
40
|
+
/// renaming the type or adding fields is fine, just don't change the
|
|
41
|
+
/// hash math without bumping `FILE_VERSION`.
|
|
42
|
+
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
|
|
43
|
+
pub struct MeshHash(pub u64);
|
|
44
|
+
|
|
45
|
+
impl MeshHash {
|
|
46
|
+
fn to_filename(self) -> String {
|
|
47
|
+
format!("{:016x}.sdf", self.0)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// FNV-1a 64-bit. Algorithmically frozen — changes here invalidate
|
|
52
|
+
/// every existing cache entry without warning.
|
|
53
|
+
fn fnv1a(input: &[u8]) -> u64 {
|
|
54
|
+
let mut h: u64 = 0xcbf29ce484222325;
|
|
55
|
+
for &b in input {
|
|
56
|
+
h ^= b as u64;
|
|
57
|
+
h = h.wrapping_mul(0x100000001b3);
|
|
58
|
+
}
|
|
59
|
+
h
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Compute a stable 64-bit content hash for the SDF input. Only
|
|
63
|
+
/// position bits and indices feed in — meshes that share a surface
|
|
64
|
+
/// but differ in normals/uv/colour share a cache entry, which is
|
|
65
|
+
/// correct: SDF is geometry-only.
|
|
66
|
+
///
|
|
67
|
+
/// `positions` is the contiguous `[f32; 3]` slice (one entry per
|
|
68
|
+
/// vertex). Callers with an interleaved vertex stride extract just
|
|
69
|
+
/// the position component before calling.
|
|
70
|
+
pub fn compute_mesh_hash(positions: &[[f32; 3]], indices: &[u32]) -> MeshHash {
|
|
71
|
+
let mut h: u64 = 0xcbf29ce484222325;
|
|
72
|
+
let mix = |h: &mut u64, b: u8| {
|
|
73
|
+
*h ^= b as u64;
|
|
74
|
+
*h = h.wrapping_mul(0x100000001b3);
|
|
75
|
+
};
|
|
76
|
+
for p in positions {
|
|
77
|
+
for c in p {
|
|
78
|
+
for b in c.to_bits().to_le_bytes() {
|
|
79
|
+
mix(&mut h, b);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
for i in indices {
|
|
84
|
+
for b in i.to_le_bytes() {
|
|
85
|
+
mix(&mut h, b);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Fold the vertex/index counts in so two meshes that share a
|
|
89
|
+
// prefix can't accidentally collide via positions-as-suffix.
|
|
90
|
+
for b in (positions.len() as u64).to_le_bytes() {
|
|
91
|
+
mix(&mut h, b);
|
|
92
|
+
}
|
|
93
|
+
for b in (indices.len() as u64).to_le_bytes() {
|
|
94
|
+
mix(&mut h, b);
|
|
95
|
+
}
|
|
96
|
+
let _ = fnv1a; // expose the helper for any future direct use
|
|
97
|
+
MeshHash(h)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Platform cache root. Returns `None` when the host has no usable
|
|
101
|
+
/// cache directory (wasm) or when the env vars used to derive the
|
|
102
|
+
/// path aren't set.
|
|
103
|
+
///
|
|
104
|
+
/// Resolution order:
|
|
105
|
+
/// - macOS / iOS / tvOS / watchOS: `$HOME/Library/Caches/bloom/sdf`
|
|
106
|
+
/// - Linux / Android: `${XDG_CACHE_HOME:-$HOME/.cache}/bloom/sdf`
|
|
107
|
+
/// - Windows: `%LOCALAPPDATA%\bloom\cache\sdf`
|
|
108
|
+
/// - wasm32: `None`
|
|
109
|
+
pub fn cache_dir() -> Option<PathBuf> {
|
|
110
|
+
#[cfg(target_arch = "wasm32")]
|
|
111
|
+
{ return None; }
|
|
112
|
+
|
|
113
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
114
|
+
{
|
|
115
|
+
let dir = if cfg!(target_vendor = "apple") {
|
|
116
|
+
let home = std::env::var_os("HOME")?;
|
|
117
|
+
PathBuf::from(home).join("Library").join("Caches").join("bloom").join("sdf")
|
|
118
|
+
} else if cfg!(target_os = "windows") {
|
|
119
|
+
let local = std::env::var_os("LOCALAPPDATA")?;
|
|
120
|
+
PathBuf::from(local).join("bloom").join("cache").join("sdf")
|
|
121
|
+
} else {
|
|
122
|
+
// Linux + Android (XDG-style).
|
|
123
|
+
let base = std::env::var_os("XDG_CACHE_HOME")
|
|
124
|
+
.map(PathBuf::from)
|
|
125
|
+
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".cache")))?;
|
|
126
|
+
base.join("bloom").join("sdf")
|
|
127
|
+
};
|
|
128
|
+
Some(dir)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Resolve a hash to its cache file path, creating the cache root if
|
|
133
|
+
/// needed. Returns `None` if the cache dir can't be created.
|
|
134
|
+
fn cache_path(hash: MeshHash) -> Option<PathBuf> {
|
|
135
|
+
let dir = cache_dir()?;
|
|
136
|
+
if !dir.exists() {
|
|
137
|
+
fs::create_dir_all(&dir).ok()?;
|
|
138
|
+
}
|
|
139
|
+
Some(dir.join(hash.to_filename()))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Look up cached voxel bytes. Returns `Some(bytes)` only when the
|
|
143
|
+
/// file exists, parses, and matches the expected magic + version +
|
|
144
|
+
/// resolution + payload size. Any failure is silently treated as a
|
|
145
|
+
/// miss — the caller falls through to the GPU bake.
|
|
146
|
+
pub fn load(hash: MeshHash) -> Option<Vec<u8>> {
|
|
147
|
+
let path = cache_path(hash)?;
|
|
148
|
+
let mut f = fs::File::open(&path).ok()?;
|
|
149
|
+
|
|
150
|
+
let mut header = [0u8; 16];
|
|
151
|
+
f.read_exact(&mut header).ok()?;
|
|
152
|
+
if header[..6] != FILE_MAGIC { return None; }
|
|
153
|
+
if header[6] != FILE_VERSION { return None; }
|
|
154
|
+
// header[7] reserved (alignment pad / future flags).
|
|
155
|
+
let res = u32::from_le_bytes(header[8..12].try_into().ok()?);
|
|
156
|
+
if res != VOXEL_RES { return None; }
|
|
157
|
+
// header[12..16] reserved.
|
|
158
|
+
|
|
159
|
+
let mut bytes = Vec::with_capacity(VOXEL_BYTES);
|
|
160
|
+
f.read_to_end(&mut bytes).ok()?;
|
|
161
|
+
if bytes.len() != VOXEL_BYTES { return None; }
|
|
162
|
+
Some(bytes)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Write voxel bytes for a mesh hash. Best-effort; an `Err` return
|
|
166
|
+
/// means the cache wasn't updated but rendering can continue.
|
|
167
|
+
pub fn store(hash: MeshHash, voxel_bytes: &[u8]) -> std::io::Result<()> {
|
|
168
|
+
if voxel_bytes.len() != VOXEL_BYTES {
|
|
169
|
+
return Err(std::io::Error::new(
|
|
170
|
+
std::io::ErrorKind::InvalidInput,
|
|
171
|
+
"voxel payload size mismatch",
|
|
172
|
+
));
|
|
173
|
+
}
|
|
174
|
+
let path = cache_path(hash).ok_or_else(|| {
|
|
175
|
+
std::io::Error::new(std::io::ErrorKind::Other, "cache directory unavailable")
|
|
176
|
+
})?;
|
|
177
|
+
|
|
178
|
+
// Write to a temp file and rename so a crash mid-write can never
|
|
179
|
+
// leave a partial entry that survives validation.
|
|
180
|
+
let tmp = path.with_extension("sdf.tmp");
|
|
181
|
+
{
|
|
182
|
+
let mut f = fs::File::create(&tmp)?;
|
|
183
|
+
let mut header = [0u8; 16];
|
|
184
|
+
header[..6].copy_from_slice(&FILE_MAGIC);
|
|
185
|
+
header[6] = FILE_VERSION;
|
|
186
|
+
// header[7] reserved.
|
|
187
|
+
header[8..12].copy_from_slice(&VOXEL_RES.to_le_bytes());
|
|
188
|
+
// header[12..16] reserved.
|
|
189
|
+
f.write_all(&header)?;
|
|
190
|
+
f.write_all(voxel_bytes)?;
|
|
191
|
+
f.sync_data()?;
|
|
192
|
+
}
|
|
193
|
+
fs::rename(&tmp, &path)?;
|
|
194
|
+
Ok(())
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
#[cfg(test)]
|
|
198
|
+
mod tests {
|
|
199
|
+
use super::*;
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
fn hash_is_stable_for_identical_input() {
|
|
203
|
+
let pos = vec![[0.0_f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
|
|
204
|
+
let idx = vec![0_u32, 1, 2];
|
|
205
|
+
assert_eq!(compute_mesh_hash(&pos, &idx), compute_mesh_hash(&pos, &idx));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
#[test]
|
|
209
|
+
fn hash_changes_when_position_changes() {
|
|
210
|
+
let pos1 = vec![[0.0_f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
|
|
211
|
+
let pos2 = vec![[0.0_f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.001, 0.0]];
|
|
212
|
+
let idx = vec![0_u32, 1, 2];
|
|
213
|
+
assert_ne!(compute_mesh_hash(&pos1, &idx), compute_mesh_hash(&pos2, &idx));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn hash_changes_when_index_changes() {
|
|
218
|
+
let pos = vec![[0.0_f32; 3]; 3];
|
|
219
|
+
let idx1 = vec![0_u32, 1, 2];
|
|
220
|
+
let idx2 = vec![0_u32, 2, 1];
|
|
221
|
+
assert_ne!(compute_mesh_hash(&pos, &idx1), compute_mesh_hash(&pos, &idx2));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[test]
|
|
225
|
+
fn hash_distinguishes_count_from_value() {
|
|
226
|
+
// Two empty inputs must hash distinctly from a single-zero
|
|
227
|
+
// input — guards against the count-fold being load-bearing.
|
|
228
|
+
let h_empty = compute_mesh_hash(&[], &[]);
|
|
229
|
+
let h_one = compute_mesh_hash(&[[0.0_f32; 3]], &[0_u32]);
|
|
230
|
+
assert_ne!(h_empty, h_one);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
fn store_then_load_roundtrips() {
|
|
235
|
+
// Skip when the env doesn't expose a cache dir (CI sandbox can do this).
|
|
236
|
+
let Some(_) = cache_dir() else { return; };
|
|
237
|
+
// Use a hash unlikely to collide with anything else's tests.
|
|
238
|
+
let h = MeshHash(0xfeed_cafe_dead_beef);
|
|
239
|
+
let bytes: Vec<u8> = (0..VOXEL_BYTES).map(|i| (i * 7 + 13) as u8).collect();
|
|
240
|
+
store(h, &bytes).expect("store");
|
|
241
|
+
let got = load(h).expect("load hit");
|
|
242
|
+
assert_eq!(got, bytes);
|
|
243
|
+
// Cleanup so the test is repeatable.
|
|
244
|
+
if let Some(p) = cache_path(h) { let _ = fs::remove_file(p); }
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
#[test]
|
|
248
|
+
fn load_miss_returns_none() {
|
|
249
|
+
let Some(_) = cache_dir() else { return; };
|
|
250
|
+
let h = MeshHash(0x0000_0000_dead_dead);
|
|
251
|
+
if let Some(p) = cache_path(h) { let _ = fs::remove_file(p); }
|
|
252
|
+
assert!(load(h).is_none());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
fn store_rejects_wrong_size() {
|
|
257
|
+
let h = MeshHash(0);
|
|
258
|
+
assert!(store(h, &[0u8; 100]).is_err());
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
#[test]
|
|
262
|
+
fn load_rejects_wrong_magic() {
|
|
263
|
+
let Some(dir) = cache_dir() else { return; };
|
|
264
|
+
let _ = fs::create_dir_all(&dir);
|
|
265
|
+
let h = MeshHash(0xbad_0_bad_1);
|
|
266
|
+
let p = dir.join(h.to_filename());
|
|
267
|
+
// Hand-write a file with the wrong magic.
|
|
268
|
+
let mut bad = vec![0u8; 16 + VOXEL_BYTES];
|
|
269
|
+
bad[..6].copy_from_slice(b"NOTBLM");
|
|
270
|
+
std::fs::write(&p, &bad).unwrap();
|
|
271
|
+
assert!(load(h).is_none());
|
|
272
|
+
let _ = fs::remove_file(p);
|
|
273
|
+
}
|
|
274
|
+
}
|