@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,122 @@
|
|
|
1
|
+
//! Frame callback system for Bloom Engine.
|
|
2
|
+
//!
|
|
3
|
+
//! Allows Perry-compiled TypeScript to register callbacks that run every frame,
|
|
4
|
+
//! ordered by priority. This replaces React Three Fiber's useFrame(cb, priority).
|
|
5
|
+
//!
|
|
6
|
+
//! Callbacks are stored as function pointers (Perry compiles TS functions to native
|
|
7
|
+
//! function pointers). Lower priority numbers run first.
|
|
8
|
+
|
|
9
|
+
/// A registered frame callback.
|
|
10
|
+
struct FrameCallback {
|
|
11
|
+
priority: i32,
|
|
12
|
+
callback: extern "C" fn(f64), // receives delta_time as argument
|
|
13
|
+
active: bool,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Manages frame callbacks with priority ordering.
|
|
17
|
+
pub struct FrameCallbackSystem {
|
|
18
|
+
callbacks: Vec<FrameCallback>,
|
|
19
|
+
sorted: bool,
|
|
20
|
+
next_id: u64,
|
|
21
|
+
ids: Vec<u64>, // parallel to callbacks, for removal
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
impl FrameCallbackSystem {
|
|
25
|
+
pub fn new() -> Self {
|
|
26
|
+
Self {
|
|
27
|
+
callbacks: Vec::new(),
|
|
28
|
+
sorted: true,
|
|
29
|
+
next_id: 1,
|
|
30
|
+
ids: Vec::new(),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// Register a callback with a priority. Returns an ID for removal.
|
|
35
|
+
/// Lower priority numbers run first (matching R3F convention).
|
|
36
|
+
pub fn register(&mut self, priority: i32, callback: extern "C" fn(f64)) -> u64 {
|
|
37
|
+
let id = self.next_id;
|
|
38
|
+
self.next_id += 1;
|
|
39
|
+
self.callbacks.push(FrameCallback {
|
|
40
|
+
priority,
|
|
41
|
+
callback,
|
|
42
|
+
active: true,
|
|
43
|
+
});
|
|
44
|
+
self.ids.push(id);
|
|
45
|
+
self.sorted = false;
|
|
46
|
+
id
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// Unregister a callback by ID.
|
|
50
|
+
pub fn unregister(&mut self, id: u64) {
|
|
51
|
+
if let Some(idx) = self.ids.iter().position(|&i| i == id) {
|
|
52
|
+
self.callbacks.remove(idx);
|
|
53
|
+
self.ids.remove(idx);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn ensure_sorted(&mut self) {
|
|
58
|
+
if !self.sorted {
|
|
59
|
+
// Sort by priority (stable sort preserves insertion order for equal priorities)
|
|
60
|
+
let mut indices: Vec<usize> = (0..self.callbacks.len()).collect();
|
|
61
|
+
indices.sort_by_key(|&i| self.callbacks[i].priority);
|
|
62
|
+
|
|
63
|
+
let old_callbacks: Vec<_> = self.callbacks.drain(..).collect();
|
|
64
|
+
let old_ids: Vec<_> = self.ids.drain(..).collect();
|
|
65
|
+
for &i in &indices {
|
|
66
|
+
self.callbacks.push(FrameCallback {
|
|
67
|
+
priority: old_callbacks[i].priority,
|
|
68
|
+
callback: old_callbacks[i].callback,
|
|
69
|
+
active: old_callbacks[i].active,
|
|
70
|
+
});
|
|
71
|
+
self.ids.push(old_ids[i]);
|
|
72
|
+
}
|
|
73
|
+
self.sorted = true;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/// Snapshot active callbacks in priority order so the caller can release
|
|
78
|
+
/// any engine-state lock before invoking foreign callbacks.
|
|
79
|
+
pub fn callbacks_for_frame(&mut self) -> Vec<extern "C" fn(f64)> {
|
|
80
|
+
self.ensure_sorted();
|
|
81
|
+
self.callbacks
|
|
82
|
+
.iter()
|
|
83
|
+
.filter(|cb| cb.active)
|
|
84
|
+
.map(|cb| cb.callback)
|
|
85
|
+
.collect()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// Run all active callbacks in priority order.
|
|
89
|
+
pub fn run_all(&mut self, delta_time: f64) {
|
|
90
|
+
self.ensure_sorted();
|
|
91
|
+
|
|
92
|
+
for cb in &self.callbacks {
|
|
93
|
+
if cb.active {
|
|
94
|
+
(cb.callback)(delta_time);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// Get number of registered callbacks.
|
|
100
|
+
pub fn count(&self) -> usize {
|
|
101
|
+
self.callbacks.len()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[cfg(test)]
|
|
106
|
+
mod tests {
|
|
107
|
+
use super::FrameCallbackSystem;
|
|
108
|
+
|
|
109
|
+
extern "C" fn callback(_: f64) {}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn callback_snapshot_is_independent_of_registry_changes() {
|
|
113
|
+
let mut callbacks = FrameCallbackSystem::new();
|
|
114
|
+
let id = callbacks.register(0, callback);
|
|
115
|
+
|
|
116
|
+
let batch = callbacks.callbacks_for_frame();
|
|
117
|
+
callbacks.unregister(id);
|
|
118
|
+
|
|
119
|
+
assert_eq!(batch.len(), 1);
|
|
120
|
+
assert_eq!(callbacks.count(), 0);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
//! Geometry generation for Bloom Engine.
|
|
2
|
+
//!
|
|
3
|
+
//! Provides polygon extrusion (for walls and slabs) and CSG box subtraction
|
|
4
|
+
//! (for door/window cutouts). These mirror the Three.js ExtrudeGeometry and
|
|
5
|
+
//! three-bvh-csg functionality used by the Pascal Editor.
|
|
6
|
+
|
|
7
|
+
use crate::renderer::Vertex3D;
|
|
8
|
+
|
|
9
|
+
/// Result of geometry generation.
|
|
10
|
+
pub struct GeometryData {
|
|
11
|
+
pub vertices: Vec<Vertex3D>,
|
|
12
|
+
pub indices: Vec<u32>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/// Extrude a 2D polygon along the Y axis by the given depth.
|
|
16
|
+
///
|
|
17
|
+
/// - `polygon`: flat array of 2D points [x0, z0, x1, z1, ...]
|
|
18
|
+
/// - `holes`: list of hole polygons, each as flat [x0, z0, x1, z1, ...]
|
|
19
|
+
/// - `depth`: extrusion height (in Y direction)
|
|
20
|
+
///
|
|
21
|
+
/// Returns vertices and indices. The geometry extends from Y=0 to Y=depth.
|
|
22
|
+
/// Normals and UVs are computed automatically.
|
|
23
|
+
pub fn extrude_polygon(
|
|
24
|
+
polygon: &[f64],
|
|
25
|
+
holes: &[Vec<f64>],
|
|
26
|
+
depth: f64,
|
|
27
|
+
) -> GeometryData {
|
|
28
|
+
let depth = depth as f32;
|
|
29
|
+
|
|
30
|
+
// Build earcutr input: flatten polygon + holes, track hole starts
|
|
31
|
+
let n_poly = polygon.len() / 2;
|
|
32
|
+
let mut flat_coords: Vec<f64> = polygon.to_vec();
|
|
33
|
+
let mut hole_indices: Vec<usize> = Vec::new();
|
|
34
|
+
|
|
35
|
+
for hole in holes {
|
|
36
|
+
hole_indices.push(flat_coords.len() / 2);
|
|
37
|
+
flat_coords.extend_from_slice(hole);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Triangulate the 2D polygon
|
|
41
|
+
let triangles = earcutr::earcut(&flat_coords, &hole_indices, 2)
|
|
42
|
+
.unwrap_or_default();
|
|
43
|
+
|
|
44
|
+
let n_points = flat_coords.len() / 2;
|
|
45
|
+
let mut vertices = Vec::new();
|
|
46
|
+
let mut indices = Vec::new();
|
|
47
|
+
|
|
48
|
+
// ---- Bottom face (Y = 0, normal pointing down) ----
|
|
49
|
+
let base_bottom = 0u32;
|
|
50
|
+
for i in 0..n_points {
|
|
51
|
+
let x = flat_coords[i * 2] as f32;
|
|
52
|
+
let z = flat_coords[i * 2 + 1] as f32;
|
|
53
|
+
vertices.push(Vertex3D {
|
|
54
|
+
position: [x, 0.0, z],
|
|
55
|
+
normal: [0.0, -1.0, 0.0],
|
|
56
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
57
|
+
uv: [x, z], // planar UV
|
|
58
|
+
joints: [0.0; 4],
|
|
59
|
+
weights: [0.0; 4],
|
|
60
|
+
tangent: [0.0; 4],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// Bottom triangles (reversed winding for downward-facing)
|
|
64
|
+
for tri in triangles.chunks(3) {
|
|
65
|
+
indices.push(base_bottom + tri[0] as u32);
|
|
66
|
+
indices.push(base_bottom + tri[2] as u32);
|
|
67
|
+
indices.push(base_bottom + tri[1] as u32);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---- Top face (Y = depth, normal pointing up) ----
|
|
71
|
+
let base_top = vertices.len() as u32;
|
|
72
|
+
for i in 0..n_points {
|
|
73
|
+
let x = flat_coords[i * 2] as f32;
|
|
74
|
+
let z = flat_coords[i * 2 + 1] as f32;
|
|
75
|
+
vertices.push(Vertex3D {
|
|
76
|
+
position: [x, depth, z],
|
|
77
|
+
normal: [0.0, 1.0, 0.0],
|
|
78
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
79
|
+
uv: [x, z],
|
|
80
|
+
joints: [0.0; 4],
|
|
81
|
+
weights: [0.0; 4],
|
|
82
|
+
tangent: [0.0; 4],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
for tri in triangles.chunks(3) {
|
|
86
|
+
indices.push(base_top + tri[0] as u32);
|
|
87
|
+
indices.push(base_top + tri[1] as u32);
|
|
88
|
+
indices.push(base_top + tri[2] as u32);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---- Side faces (walls of the extrusion) ----
|
|
92
|
+
// Process each edge of the outer polygon and each hole
|
|
93
|
+
let mut edge_loops: Vec<Vec<usize>> = Vec::new();
|
|
94
|
+
|
|
95
|
+
// Outer polygon edges
|
|
96
|
+
let outer_loop: Vec<usize> = (0..n_poly).collect();
|
|
97
|
+
edge_loops.push(outer_loop);
|
|
98
|
+
|
|
99
|
+
// Hole edges
|
|
100
|
+
for (hi, hole) in holes.iter().enumerate() {
|
|
101
|
+
let n_hole = hole.len() / 2;
|
|
102
|
+
let start = hole_indices[hi];
|
|
103
|
+
let hole_loop: Vec<usize> = (start..start + n_hole).collect();
|
|
104
|
+
edge_loops.push(hole_loop);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for edge_loop in &edge_loops {
|
|
108
|
+
let n = edge_loop.len();
|
|
109
|
+
for i in 0..n {
|
|
110
|
+
let i0 = edge_loop[i];
|
|
111
|
+
let i1 = edge_loop[(i + 1) % n];
|
|
112
|
+
|
|
113
|
+
let x0 = flat_coords[i0 * 2] as f32;
|
|
114
|
+
let z0 = flat_coords[i0 * 2 + 1] as f32;
|
|
115
|
+
let x1 = flat_coords[i1 * 2] as f32;
|
|
116
|
+
let z1 = flat_coords[i1 * 2 + 1] as f32;
|
|
117
|
+
|
|
118
|
+
// Edge direction and outward normal
|
|
119
|
+
let dx = x1 - x0;
|
|
120
|
+
let dz = z1 - z0;
|
|
121
|
+
let len = (dx * dx + dz * dz).sqrt();
|
|
122
|
+
if len < 1e-6 { continue; }
|
|
123
|
+
let nx = -dz / len;
|
|
124
|
+
let nz = dx / len;
|
|
125
|
+
|
|
126
|
+
// 4 vertices for this side quad
|
|
127
|
+
let base = vertices.len() as u32;
|
|
128
|
+
|
|
129
|
+
// Bottom-left, bottom-right, top-right, top-left
|
|
130
|
+
let u_len = len;
|
|
131
|
+
vertices.push(Vertex3D {
|
|
132
|
+
position: [x0, 0.0, z0],
|
|
133
|
+
normal: [nx, 0.0, nz],
|
|
134
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
135
|
+
uv: [0.0, 0.0],
|
|
136
|
+
joints: [0.0; 4],
|
|
137
|
+
weights: [0.0; 4],
|
|
138
|
+
tangent: [0.0; 4],
|
|
139
|
+
});
|
|
140
|
+
vertices.push(Vertex3D {
|
|
141
|
+
position: [x1, 0.0, z1],
|
|
142
|
+
normal: [nx, 0.0, nz],
|
|
143
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
144
|
+
uv: [u_len, 0.0],
|
|
145
|
+
joints: [0.0; 4],
|
|
146
|
+
weights: [0.0; 4],
|
|
147
|
+
tangent: [0.0; 4],
|
|
148
|
+
});
|
|
149
|
+
vertices.push(Vertex3D {
|
|
150
|
+
position: [x1, depth, z1],
|
|
151
|
+
normal: [nx, 0.0, nz],
|
|
152
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
153
|
+
uv: [u_len, depth],
|
|
154
|
+
joints: [0.0; 4],
|
|
155
|
+
weights: [0.0; 4],
|
|
156
|
+
tangent: [0.0; 4],
|
|
157
|
+
});
|
|
158
|
+
vertices.push(Vertex3D {
|
|
159
|
+
position: [x0, depth, z0],
|
|
160
|
+
normal: [nx, 0.0, nz],
|
|
161
|
+
color: [1.0, 1.0, 1.0, 1.0],
|
|
162
|
+
uv: [0.0, depth],
|
|
163
|
+
joints: [0.0; 4],
|
|
164
|
+
weights: [0.0; 4],
|
|
165
|
+
tangent: [0.0; 4],
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
indices.push(base);
|
|
169
|
+
indices.push(base + 1);
|
|
170
|
+
indices.push(base + 2);
|
|
171
|
+
indices.push(base);
|
|
172
|
+
indices.push(base + 2);
|
|
173
|
+
indices.push(base + 3);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
GeometryData { vertices, indices }
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// Subtract an axis-aligned box from existing geometry.
|
|
181
|
+
///
|
|
182
|
+
/// This is a simplified CSG operation that clips triangles against a box.
|
|
183
|
+
/// For Phase 2 MVP, we use a simple approach: remove triangles fully inside
|
|
184
|
+
/// the box and clip intersecting ones. This handles the common case of
|
|
185
|
+
/// door/window cutouts in walls.
|
|
186
|
+
///
|
|
187
|
+
/// - `min`: box minimum corner [x, y, z]
|
|
188
|
+
/// - `max`: box maximum corner [x, y, z]
|
|
189
|
+
pub fn subtract_box(
|
|
190
|
+
geo: &GeometryData,
|
|
191
|
+
min: [f32; 3],
|
|
192
|
+
max: [f32; 3],
|
|
193
|
+
) -> GeometryData {
|
|
194
|
+
let mut out_vertices = Vec::new();
|
|
195
|
+
let mut out_indices = Vec::new();
|
|
196
|
+
|
|
197
|
+
// Process each triangle
|
|
198
|
+
for tri in geo.indices.chunks(3) {
|
|
199
|
+
if tri.len() < 3 { continue; }
|
|
200
|
+
let v0 = &geo.vertices[tri[0] as usize];
|
|
201
|
+
let v1 = &geo.vertices[tri[1] as usize];
|
|
202
|
+
let v2 = &geo.vertices[tri[2] as usize];
|
|
203
|
+
|
|
204
|
+
// Check if any vertex is inside the box
|
|
205
|
+
let in0 = point_in_box(&v0.position, &min, &max);
|
|
206
|
+
let in1 = point_in_box(&v1.position, &min, &max);
|
|
207
|
+
let in2 = point_in_box(&v2.position, &min, &max);
|
|
208
|
+
|
|
209
|
+
if in0 && in1 && in2 {
|
|
210
|
+
// Triangle fully inside box — discard
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Triangle fully outside or partially intersecting — keep for now
|
|
215
|
+
// (Full CSG clipping would split partial triangles, but for MVP
|
|
216
|
+
// removing fully-interior triangles handles most cutout cases)
|
|
217
|
+
let base = out_vertices.len() as u32;
|
|
218
|
+
out_vertices.push(*v0);
|
|
219
|
+
out_vertices.push(*v1);
|
|
220
|
+
out_vertices.push(*v2);
|
|
221
|
+
out_indices.push(base);
|
|
222
|
+
out_indices.push(base + 1);
|
|
223
|
+
out_indices.push(base + 2);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
GeometryData {
|
|
227
|
+
vertices: out_vertices,
|
|
228
|
+
indices: out_indices,
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
fn point_in_box(p: &[f32; 3], min: &[f32; 3], max: &[f32; 3]) -> bool {
|
|
233
|
+
p[0] >= min[0] && p[0] <= max[0]
|
|
234
|
+
&& p[1] >= min[1] && p[1] <= max[1]
|
|
235
|
+
&& p[2] >= min[2] && p[2] <= max[2]
|
|
236
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
//! Generic handle registry: maps f64 handles to owned values.
|
|
2
|
+
//!
|
|
3
|
+
//! Handles are **generational**: the f64 encodes a 1-based slot index in
|
|
4
|
+
//! the low 32 bits and a generation counter above it. Freeing a slot
|
|
5
|
+
//! bumps its generation, so a stale handle held by game code after a
|
|
6
|
+
//! free/realloc cycle fails the lookup instead of silently aliasing
|
|
7
|
+
//! whatever object reused the slot — the classic use-after-free-by-index
|
|
8
|
+
//! bug this registry used to permit.
|
|
9
|
+
//!
|
|
10
|
+
//! Encoding: `value = (generation << 32) | (slot + 1)`, stored in an f64.
|
|
11
|
+
//! f64 represents integers exactly up to 2^53, generations wrap at 2^21
|
|
12
|
+
//! (a slot must be freed two million times, and the stale handle
|
|
13
|
+
//! retained throughout, to produce a false positive). Generation 0
|
|
14
|
+
//! handles are plain small integers — the representation games saw
|
|
15
|
+
//! before generations existed.
|
|
16
|
+
//!
|
|
17
|
+
//! `0` is never a valid handle.
|
|
18
|
+
|
|
19
|
+
const SLOT_MASK: u64 = 0xFFFF_FFFF;
|
|
20
|
+
const GEN_SHIFT: u32 = 32;
|
|
21
|
+
/// Generations wrap below 2^21 so the full encoded value stays under
|
|
22
|
+
/// 2^53 (exact f64 integer range).
|
|
23
|
+
const GEN_WRAP: u64 = 1 << 21;
|
|
24
|
+
|
|
25
|
+
pub struct HandleRegistry<T> {
|
|
26
|
+
items: Vec<Option<T>>,
|
|
27
|
+
generations: Vec<u64>,
|
|
28
|
+
free_list: Vec<usize>,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
impl<T> Default for HandleRegistry<T> {
|
|
32
|
+
fn default() -> Self {
|
|
33
|
+
Self::new()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl<T> HandleRegistry<T> {
|
|
38
|
+
pub fn new() -> Self {
|
|
39
|
+
Self {
|
|
40
|
+
items: Vec::new(),
|
|
41
|
+
generations: Vec::new(),
|
|
42
|
+
free_list: Vec::new(),
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fn encode(&self, idx: usize) -> f64 {
|
|
47
|
+
((self.generations[idx] << GEN_SHIFT) | (idx as u64 + 1)) as f64
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// Decode a handle to a live slot index, or None if the handle is
|
|
51
|
+
/// invalid, out of range, freed, or from a previous generation.
|
|
52
|
+
fn decode(&self, handle: f64) -> Option<usize> {
|
|
53
|
+
if !(handle.is_finite() && handle > 0.0) {
|
|
54
|
+
return None;
|
|
55
|
+
}
|
|
56
|
+
let v = handle as u64;
|
|
57
|
+
let slot = v & SLOT_MASK;
|
|
58
|
+
let gen = v >> GEN_SHIFT;
|
|
59
|
+
if slot == 0 || slot as usize > self.items.len() {
|
|
60
|
+
return None;
|
|
61
|
+
}
|
|
62
|
+
let idx = slot as usize - 1;
|
|
63
|
+
if self.generations[idx] != gen {
|
|
64
|
+
return None; // stale handle from before a free()
|
|
65
|
+
}
|
|
66
|
+
self.items[idx].as_ref()?;
|
|
67
|
+
Some(idx)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// Allocate a handle for the given item.
|
|
71
|
+
pub fn alloc(&mut self, item: T) -> f64 {
|
|
72
|
+
if let Some(idx) = self.free_list.pop() {
|
|
73
|
+
self.items[idx] = Some(item);
|
|
74
|
+
self.encode(idx)
|
|
75
|
+
} else {
|
|
76
|
+
self.items.push(Some(item));
|
|
77
|
+
self.generations.push(0);
|
|
78
|
+
self.encode(self.items.len() - 1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Get a reference to the item at the given handle.
|
|
83
|
+
pub fn get(&self, handle: f64) -> Option<&T> {
|
|
84
|
+
self.decode(handle).and_then(|idx| self.items[idx].as_ref())
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Get a mutable reference to the item at the given handle.
|
|
88
|
+
pub fn get_mut(&mut self, handle: f64) -> Option<&mut T> {
|
|
89
|
+
let idx = self.decode(handle)?;
|
|
90
|
+
self.items[idx].as_mut()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/// Free the item at the given handle. The slot's generation is bumped
|
|
94
|
+
/// so every outstanding copy of this handle becomes invalid.
|
|
95
|
+
pub fn free(&mut self, handle: f64) -> Option<T> {
|
|
96
|
+
let idx = self.decode(handle)?;
|
|
97
|
+
let item = self.items[idx].take();
|
|
98
|
+
if item.is_some() {
|
|
99
|
+
self.generations[idx] = (self.generations[idx] + 1) % GEN_WRAP;
|
|
100
|
+
self.free_list.push(idx);
|
|
101
|
+
}
|
|
102
|
+
item
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Number of slots (including freed). Use for iteration bounds.
|
|
106
|
+
pub fn capacity(&self) -> usize {
|
|
107
|
+
self.items.len()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Iterate over all live (handle, &T) pairs. Handles carry their
|
|
111
|
+
/// current generation — they compare equal to what alloc() returned.
|
|
112
|
+
pub fn iter(&self) -> impl Iterator<Item = (f64, &T)> {
|
|
113
|
+
self.items.iter().enumerate().filter_map(|(idx, slot)| {
|
|
114
|
+
slot.as_ref()
|
|
115
|
+
.map(|item| (((self.generations[idx] << GEN_SHIFT) | (idx as u64 + 1)) as f64, item))
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Iterate over all live (handle, &mut T) pairs.
|
|
120
|
+
pub fn iter_mut(&mut self) -> impl Iterator<Item = (f64, &mut T)> {
|
|
121
|
+
let generations = &self.generations;
|
|
122
|
+
self.items.iter_mut().enumerate().filter_map(move |(idx, slot)| {
|
|
123
|
+
slot.as_mut()
|
|
124
|
+
.map(|item| (((generations[idx] << GEN_SHIFT) | (idx as u64 + 1)) as f64, item))
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#[cfg(test)]
|
|
130
|
+
mod tests {
|
|
131
|
+
use super::*;
|
|
132
|
+
|
|
133
|
+
#[test]
|
|
134
|
+
fn alloc_get_free() {
|
|
135
|
+
let mut r = HandleRegistry::new();
|
|
136
|
+
let h = r.alloc("a");
|
|
137
|
+
assert_eq!(r.get(h), Some(&"a"));
|
|
138
|
+
assert_eq!(r.free(h), Some("a"));
|
|
139
|
+
assert_eq!(r.get(h), None);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[test]
|
|
143
|
+
fn first_generation_handles_are_small_integers() {
|
|
144
|
+
let mut r = HandleRegistry::new();
|
|
145
|
+
assert_eq!(r.alloc("a"), 1.0);
|
|
146
|
+
assert_eq!(r.alloc("b"), 2.0);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
#[test]
|
|
150
|
+
fn stale_handle_does_not_alias_reused_slot() {
|
|
151
|
+
let mut r = HandleRegistry::new();
|
|
152
|
+
let old = r.alloc("old");
|
|
153
|
+
r.free(old);
|
|
154
|
+
let new = r.alloc("new"); // reuses slot 0 with generation 1
|
|
155
|
+
assert_ne!(old.to_bits(), new.to_bits());
|
|
156
|
+
assert_eq!(r.get(old), None, "stale handle resolved after slot reuse");
|
|
157
|
+
assert_eq!(r.get(new), Some(&"new"));
|
|
158
|
+
// double-free through the stale handle is a no-op
|
|
159
|
+
assert_eq!(r.free(old), None);
|
|
160
|
+
assert_eq!(r.get(new), Some(&"new"));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn iter_returns_current_generation_handles() {
|
|
165
|
+
let mut r = HandleRegistry::new();
|
|
166
|
+
let a = r.alloc(1);
|
|
167
|
+
r.free(a);
|
|
168
|
+
let b = r.alloc(2);
|
|
169
|
+
let collected: Vec<(f64, &i32)> = r.iter().collect();
|
|
170
|
+
assert_eq!(collected.len(), 1);
|
|
171
|
+
assert_eq!(collected[0].0.to_bits(), b.to_bits());
|
|
172
|
+
assert_eq!(r.get(collected[0].0), Some(&2));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn garbage_handles_rejected() {
|
|
177
|
+
let r: HandleRegistry<i32> = HandleRegistry::new();
|
|
178
|
+
for h in [0.0, -1.0, 0.5, f64::NAN, f64::INFINITY, 1e300] {
|
|
179
|
+
assert!(r.get(h).is_none());
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|