@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,641 @@
|
|
|
1
|
+
//! Model draw submission: cached-model draw commands and the
|
|
2
|
+
//! immediate-mode mesh path (incl. GPU-skinning joint-pose plumbing).
|
|
3
|
+
//! Split from renderer/mod.rs (2000-line file policy — same pattern as
|
|
4
|
+
//! shadow_pass.rs).
|
|
5
|
+
|
|
6
|
+
use super::*;
|
|
7
|
+
|
|
8
|
+
impl Renderer {
|
|
9
|
+
/// Record a cached model draw command. The actual rendering happens in end_frame().
|
|
10
|
+
pub fn draw_model_cached(&mut self, handle_bits: u64, position: [f32; 3], scale: f32, tint: [f32; 4]) {
|
|
11
|
+
let mesh_count = match self.model_gpu_cache.get(&handle_bits) {
|
|
12
|
+
Some(Some(meshes)) => meshes.len(),
|
|
13
|
+
_ => return,
|
|
14
|
+
};
|
|
15
|
+
// Foliage wind amount for this model (0 = not a plant). Rides in misc.z.
|
|
16
|
+
let foliage = self.foliage_wind.get(&handle_bits).copied().unwrap_or(0.0);
|
|
17
|
+
|
|
18
|
+
for mesh_idx in 0..mesh_count {
|
|
19
|
+
let slot = self.next_model_uniform_slot;
|
|
20
|
+
self.next_model_uniform_slot += 1;
|
|
21
|
+
|
|
22
|
+
// Grow uniform pool if needed
|
|
23
|
+
self.ensure_model_uniform_slot(slot);
|
|
24
|
+
|
|
25
|
+
// Compute model MVP: VP * translate(position) * scale(s)
|
|
26
|
+
let model_matrix = mat4_multiply(
|
|
27
|
+
mat4_translate(IDENTITY_MAT4, position),
|
|
28
|
+
mat4_scale(IDENTITY_MAT4, [scale, scale, scale]),
|
|
29
|
+
);
|
|
30
|
+
let model_mvp = mat4_multiply(self.current_vp_matrix, model_matrix);
|
|
31
|
+
|
|
32
|
+
// Stage uniform for this draw (flushed in one write at end-frame)
|
|
33
|
+
self.stage_model_uniform(slot, &Uniforms3D {
|
|
34
|
+
mvp: model_mvp, model: model_matrix,
|
|
35
|
+
prev_mvp: model_mvp, model_tint: tint,
|
|
36
|
+
misc: [0.0, 0.0, foliage, 0.0],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
self.model_draw_commands.push(CachedModelDraw {
|
|
40
|
+
uniform_slot: slot,
|
|
41
|
+
cache_handle: handle_bits,
|
|
42
|
+
mesh_idx,
|
|
43
|
+
model: model_matrix,
|
|
44
|
+
skinned: false,
|
|
45
|
+
joint_offset: 0.0,
|
|
46
|
+
bounds_override: None,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/// `draw_model_cached` with a Y-axis rotation folded into the model
|
|
52
|
+
/// matrix (translate ∘ rotY ∘ scale). Backs `bloom_draw_model_rotated`
|
|
53
|
+
/// for static models — previously that FFI only had the immediate-mode
|
|
54
|
+
/// vertex path, which bypasses the scene pipeline entirely (no alpha
|
|
55
|
+
/// cutout, no normal/MR maps, no foliage wind or transmission, no
|
|
56
|
+
/// cutout shadows) and re-transforms every vertex on the CPU each
|
|
57
|
+
/// frame. Alpha-cutout foliage drawn through it rendered its cards'
|
|
58
|
+
/// transparent texels as opaque.
|
|
59
|
+
pub fn draw_model_cached_rotated(
|
|
60
|
+
&mut self,
|
|
61
|
+
handle_bits: u64,
|
|
62
|
+
position: [f32; 3],
|
|
63
|
+
scale: f32,
|
|
64
|
+
rot_y: f32,
|
|
65
|
+
tint: [f32; 4],
|
|
66
|
+
) {
|
|
67
|
+
let mesh_count = match self.model_gpu_cache.get(&handle_bits) {
|
|
68
|
+
Some(Some(meshes)) => meshes.len(),
|
|
69
|
+
_ => return,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let foliage = self.foliage_wind.get(&handle_bits).copied().unwrap_or(0.0);
|
|
73
|
+
let (s, c) = rot_y.sin_cos();
|
|
74
|
+
// Column-major rotY (matches mat4_translate / mat4_scale layout).
|
|
75
|
+
let rot: [[f32; 4]; 4] = [
|
|
76
|
+
[c, 0.0, -s, 0.0],
|
|
77
|
+
[0.0, 1.0, 0.0, 0.0],
|
|
78
|
+
[s, 0.0, c, 0.0],
|
|
79
|
+
[0.0, 0.0, 0.0, 1.0],
|
|
80
|
+
];
|
|
81
|
+
let model_matrix = mat4_multiply(
|
|
82
|
+
mat4_translate(IDENTITY_MAT4, position),
|
|
83
|
+
mat4_multiply(rot, mat4_scale(IDENTITY_MAT4, [scale, scale, scale])),
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
for mesh_idx in 0..mesh_count {
|
|
87
|
+
let slot = self.next_model_uniform_slot;
|
|
88
|
+
self.next_model_uniform_slot += 1;
|
|
89
|
+
self.ensure_model_uniform_slot(slot);
|
|
90
|
+
|
|
91
|
+
let model_mvp = mat4_multiply(self.current_vp_matrix, model_matrix);
|
|
92
|
+
self.stage_model_uniform(slot, &Uniforms3D {
|
|
93
|
+
mvp: model_mvp, model: model_matrix,
|
|
94
|
+
prev_mvp: model_mvp, model_tint: tint,
|
|
95
|
+
misc: [0.0, 0.0, foliage, 0.0],
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
self.model_draw_commands.push(CachedModelDraw {
|
|
99
|
+
uniform_slot: slot,
|
|
100
|
+
cache_handle: handle_bits,
|
|
101
|
+
mesh_idx,
|
|
102
|
+
model: model_matrix,
|
|
103
|
+
skinned: false,
|
|
104
|
+
joint_offset: 0.0,
|
|
105
|
+
bounds_override: None,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// EN-039 — `draw_model_cached` taking the model matrix WHOLE, instead of
|
|
111
|
+
/// composing it from a position/scale/yaw. `draw_model_cached_rotated` above
|
|
112
|
+
/// can only express a Y rotation, so an immediate-mode caller could not
|
|
113
|
+
/// pitch or roll: the shooter's weapon stays level while the camera looks up
|
|
114
|
+
/// or down, and the same wall hits any held prop, thrown object or debris
|
|
115
|
+
/// chunk. Everything downstream (alpha cutout, normal/MR maps, foliage wind,
|
|
116
|
+
/// cutout shadows, planar reflections) is identical — this only skips the
|
|
117
|
+
/// composition step and trusts the caller's matrix.
|
|
118
|
+
pub fn draw_model_cached_transform(
|
|
119
|
+
&mut self,
|
|
120
|
+
handle_bits: u64,
|
|
121
|
+
model_matrix: [[f32; 4]; 4],
|
|
122
|
+
tint: [f32; 4],
|
|
123
|
+
) {
|
|
124
|
+
let mesh_count = match self.model_gpu_cache.get(&handle_bits) {
|
|
125
|
+
Some(Some(meshes)) => meshes.len(),
|
|
126
|
+
_ => return,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
let foliage = self.foliage_wind.get(&handle_bits).copied().unwrap_or(0.0);
|
|
130
|
+
|
|
131
|
+
for mesh_idx in 0..mesh_count {
|
|
132
|
+
let slot = self.next_model_uniform_slot;
|
|
133
|
+
self.next_model_uniform_slot += 1;
|
|
134
|
+
self.ensure_model_uniform_slot(slot);
|
|
135
|
+
|
|
136
|
+
let model_mvp = mat4_multiply(self.current_vp_matrix, model_matrix);
|
|
137
|
+
self.stage_model_uniform(slot, &Uniforms3D {
|
|
138
|
+
mvp: model_mvp, model: model_matrix,
|
|
139
|
+
prev_mvp: model_mvp, model_tint: tint,
|
|
140
|
+
misc: [0.0, 0.0, foliage, 0.0],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
self.model_draw_commands.push(CachedModelDraw {
|
|
144
|
+
uniform_slot: slot,
|
|
145
|
+
cache_handle: handle_bits,
|
|
146
|
+
mesh_idx,
|
|
147
|
+
model: model_matrix,
|
|
148
|
+
skinned: false,
|
|
149
|
+
joint_offset: 0.0,
|
|
150
|
+
bounds_override: None,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// Cached-path draw for a SKINNED model: the bind-pose VB/IB stay
|
|
156
|
+
/// GPU-resident (raw joint indices) and the scene VS skins from the
|
|
157
|
+
/// shared joint buffer — replacing the immediate path's per-frame
|
|
158
|
+
/// CPU re-transform + whole-batch vertex re-upload. Consumes the
|
|
159
|
+
/// staged pose ONCE for the whole model and shares its joint-buffer
|
|
160
|
+
/// offset across every primitive via the per-draw uniform's misc.x
|
|
161
|
+
/// (per-primitive pops starved multi-primitive models onto offset 0
|
|
162
|
+
/// — the marauder/tyrant blob; see `take_staged_skin_offset`).
|
|
163
|
+
///
|
|
164
|
+
/// Rotation-less by design: joint matrices already bake the model's
|
|
165
|
+
/// world orientation (same contract as the old immediate path).
|
|
166
|
+
pub fn draw_model_cached_skinned(
|
|
167
|
+
&mut self,
|
|
168
|
+
handle_bits: u64,
|
|
169
|
+
position: [f32; 3],
|
|
170
|
+
scale: f32,
|
|
171
|
+
tint: [f32; 4],
|
|
172
|
+
) {
|
|
173
|
+
// Union of the cached meshes' local AABBs = the model's rest-pose
|
|
174
|
+
// AABB (sentinel min > max when every mesh is empty).
|
|
175
|
+
let (mesh_count, rest_min, rest_max) = match self.model_gpu_cache.get(&handle_bits) {
|
|
176
|
+
Some(Some(meshes)) => {
|
|
177
|
+
let mut rmin = [f32::MAX; 3];
|
|
178
|
+
let mut rmax = [f32::MIN; 3];
|
|
179
|
+
for mesh in meshes.iter() {
|
|
180
|
+
if mesh.local_min[0] > mesh.local_max[0] { continue; }
|
|
181
|
+
for a in 0..3 {
|
|
182
|
+
if mesh.local_min[a] < rmin[a] { rmin[a] = mesh.local_min[a]; }
|
|
183
|
+
if mesh.local_max[a] > rmax[a] { rmax[a] = mesh.local_max[a]; }
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
(meshes.len(), rmin, rmax)
|
|
187
|
+
}
|
|
188
|
+
_ => return,
|
|
189
|
+
};
|
|
190
|
+
if mesh_count == 0 { return; }
|
|
191
|
+
|
|
192
|
+
let joint_offset = self.take_staged_skin_offset().unwrap_or(0.0);
|
|
193
|
+
|
|
194
|
+
// Model matrix places the rare rigid (weightless) verts; weighted
|
|
195
|
+
// verts get their world placement from the joint matrices.
|
|
196
|
+
let model_matrix = mat4_multiply(
|
|
197
|
+
mat4_translate(IDENTITY_MAT4, position),
|
|
198
|
+
mat4_scale(IDENTITY_MAT4, [scale, scale, scale]),
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Rigorous world AABB: a skinned vertex is a convex blend of its
|
|
202
|
+
// per-joint transforms of ONE rest position, so the union of every
|
|
203
|
+
// joint matrix applied to the rest AABB bounds all weighted verts
|
|
204
|
+
// (same argument as `finish_model_segment`). Union in the plain
|
|
205
|
+
// model-matrix transform too so rigid verts are covered. The
|
|
206
|
+
// model's joints occupy frame_joint_data[joint_offset..len] right
|
|
207
|
+
// now: its group was staged just above and the next model's group
|
|
208
|
+
// hasn't been staged yet.
|
|
209
|
+
let (mut wmin, mut wmax) = super::transform_aabb(&model_matrix, rest_min, rest_max);
|
|
210
|
+
let jstart = (joint_offset.max(0.0) as usize).min(self.frame_joint_data.len());
|
|
211
|
+
for j in jstart..self.frame_joint_data.len() {
|
|
212
|
+
let (m0, m1) = super::transform_aabb(
|
|
213
|
+
&self.frame_joint_data[j], rest_min, rest_max,
|
|
214
|
+
);
|
|
215
|
+
for a in 0..3 {
|
|
216
|
+
if m0[a] < wmin[a] { wmin[a] = m0[a]; }
|
|
217
|
+
if m1[a] > wmax[a] { wmax[a] = m1[a]; }
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Sentinel (min > max) → no bounds; the passes treat the draw as
|
|
221
|
+
// uncullable rather than culling it away.
|
|
222
|
+
let bounds_override = if wmin[0] <= wmax[0] { Some((wmin, wmax)) } else { None };
|
|
223
|
+
|
|
224
|
+
let vp = self.current_vp_matrix;
|
|
225
|
+
for mesh_idx in 0..mesh_count {
|
|
226
|
+
let slot = self.next_model_uniform_slot;
|
|
227
|
+
self.next_model_uniform_slot += 1;
|
|
228
|
+
self.ensure_model_uniform_slot(slot);
|
|
229
|
+
|
|
230
|
+
// Skinned draws: mvp is the BARE view-projection — the
|
|
231
|
+
// joint matrices bake world placement, so the VS goes
|
|
232
|
+
// joint-space → world → clip without a model term.
|
|
233
|
+
// prev_mvp is the EN-022 velocity-reference VP (previous
|
|
234
|
+
// frame, jitter-cancelled): with the previous palette in
|
|
235
|
+
// the VS this yields REAL skeletal+locomotion velocity
|
|
236
|
+
// (it used to be the current VP -> exactly zero velocity,
|
|
237
|
+
// which is why enemies ghosted under TAA/TSR).
|
|
238
|
+
self.stage_model_uniform(slot, &Uniforms3D {
|
|
239
|
+
mvp: vp, model: model_matrix,
|
|
240
|
+
prev_mvp: self.velocity_ref_vp, model_tint: tint,
|
|
241
|
+
misc: [joint_offset, 1.0, 0.0, 0.0],
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
self.model_draw_commands.push(CachedModelDraw {
|
|
245
|
+
uniform_slot: slot,
|
|
246
|
+
cache_handle: handle_bits,
|
|
247
|
+
mesh_idx,
|
|
248
|
+
model: model_matrix,
|
|
249
|
+
skinned: true,
|
|
250
|
+
joint_offset,
|
|
251
|
+
bounds_override,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// PT-6 — register the mesh for the dynamic TLAS path: a
|
|
255
|
+
// compute pass skins it into the PT megabuffer and a
|
|
256
|
+
// per-frame BLAS build makes it visible to every ray
|
|
257
|
+
// (traced shadows, bounce light, reflections).
|
|
258
|
+
if self.hw_rt_enabled {
|
|
259
|
+
let (wmin, wmax) = bounds_override
|
|
260
|
+
.unwrap_or(([0.0; 3], [-1.0; 3]));
|
|
261
|
+
self.pt_dynamic_draws.push(super::PtDynamicDraw {
|
|
262
|
+
cache_handle: handle_bits,
|
|
263
|
+
mesh_idx,
|
|
264
|
+
joint_offset,
|
|
265
|
+
model: model_matrix,
|
|
266
|
+
wmin,
|
|
267
|
+
wmax,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// Bind group for one 256 B slot of the pooled model uniform buffer.
|
|
274
|
+
pub(super) fn model_uniform_bg_for_slot(
|
|
275
|
+
device: &wgpu::Device,
|
|
276
|
+
layout: &wgpu::BindGroupLayout,
|
|
277
|
+
pool: &wgpu::Buffer,
|
|
278
|
+
slot: usize,
|
|
279
|
+
) -> wgpu::BindGroup {
|
|
280
|
+
device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
281
|
+
label: Some("model_uniform_bg"),
|
|
282
|
+
layout,
|
|
283
|
+
entries: &[wgpu::BindGroupEntry {
|
|
284
|
+
binding: 0,
|
|
285
|
+
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
|
|
286
|
+
buffer: pool,
|
|
287
|
+
offset: (slot * MODEL_UNIFORM_STRIDE) as u64,
|
|
288
|
+
size: std::num::NonZeroU64::new(
|
|
289
|
+
std::mem::size_of::<Uniforms3D>() as u64,
|
|
290
|
+
),
|
|
291
|
+
}),
|
|
292
|
+
}],
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Pack a draw's uniforms into the CPU scratch; `flush_model_uniforms`
|
|
297
|
+
/// uploads the whole used range in one `write_buffer` at end-frame.
|
|
298
|
+
fn stage_model_uniform(&mut self, slot: usize, uniforms: &Uniforms3D) {
|
|
299
|
+
self.ensure_model_uniform_slot(slot);
|
|
300
|
+
let off = slot * MODEL_UNIFORM_STRIDE;
|
|
301
|
+
if self.model_uniform_scratch.len() < off + MODEL_UNIFORM_STRIDE {
|
|
302
|
+
self.model_uniform_scratch.resize(off + MODEL_UNIFORM_STRIDE, 0);
|
|
303
|
+
}
|
|
304
|
+
self.model_uniform_scratch[off..off + std::mem::size_of::<Uniforms3D>()]
|
|
305
|
+
.copy_from_slice(bytemuck::bytes_of(uniforms));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/// Upload every staged cached-model uniform in one write. Called once
|
|
309
|
+
/// per frame from the end-frame paths, before passes execute (queued
|
|
310
|
+
/// writes land at submit, ahead of all encoded passes).
|
|
311
|
+
pub(super) fn flush_model_uniforms(&mut self) {
|
|
312
|
+
if self.next_model_uniform_slot == 0 { return; }
|
|
313
|
+
let used = (self.next_model_uniform_slot * MODEL_UNIFORM_STRIDE)
|
|
314
|
+
.min(self.model_uniform_scratch.len());
|
|
315
|
+
if used > 0 {
|
|
316
|
+
self.queue.write_buffer(
|
|
317
|
+
&self.model_uniform_pool,
|
|
318
|
+
0,
|
|
319
|
+
&self.model_uniform_scratch[..used],
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fn ensure_model_uniform_slot(&mut self, slot: usize) {
|
|
325
|
+
if slot < self.model_uniform_pool_capacity {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
// Grow by doubling: new pool + rebuild every slot bind group
|
|
329
|
+
// (rare — only when a scene exceeds the previous high-water mark).
|
|
330
|
+
let new_cap = (slot + 1)
|
|
331
|
+
.next_power_of_two()
|
|
332
|
+
.max(self.model_uniform_pool_capacity * 2);
|
|
333
|
+
self.model_uniform_pool = self.device.create_buffer(&wgpu::BufferDescriptor {
|
|
334
|
+
label: Some("model_uniform_pool"),
|
|
335
|
+
size: (new_cap * MODEL_UNIFORM_STRIDE) as u64,
|
|
336
|
+
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
337
|
+
mapped_at_creation: false,
|
|
338
|
+
});
|
|
339
|
+
self.model_uniform_bind_groups = (0..new_cap)
|
|
340
|
+
.map(|s| Self::model_uniform_bg_for_slot(
|
|
341
|
+
&self.device, &self.uniform_3d_layout, &self.model_uniform_pool, s,
|
|
342
|
+
))
|
|
343
|
+
.collect();
|
|
344
|
+
self.model_uniform_pool_capacity = new_cap;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
pub fn draw_model_mesh(&mut self, vertices: &[Vertex3D], indices: &[u32], position: [f32; 3], scale: f32) {
|
|
348
|
+
self.draw_model_mesh_tinted(vertices, indices, position, scale, [1.0, 1.0, 1.0, 1.0], 0);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/// True if any vertex carries skin weights (same test the cache and
|
|
352
|
+
/// the per-vertex draw loops use).
|
|
353
|
+
fn mesh_has_skin(vertices: &[Vertex3D]) -> bool {
|
|
354
|
+
vertices.iter().any(|v|
|
|
355
|
+
v.weights[0] + v.weights[1] + v.weights[2] + v.weights[3] > 0.01)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// Pop the next staged skin pose (FIFO) and pack it into the frame
|
|
359
|
+
/// joint accumulator, returning the base slot offset its matrices
|
|
360
|
+
/// were packed at. `None` when nothing is staged.
|
|
361
|
+
///
|
|
362
|
+
/// drawModel-level callers must call this ONCE per model and share
|
|
363
|
+
/// the offset across every primitive: when each primitive popped its
|
|
364
|
+
/// own entry, the second primitive of a multi-primitive skinned model
|
|
365
|
+
/// (marauder, tyrant) found the FIFO empty, fell back to offset 0 and
|
|
366
|
+
/// got skinned by whatever pose lives at the start of the joint
|
|
367
|
+
/// buffer — the player's — rendering as a giant mangled blob glued to
|
|
368
|
+
/// the player's position.
|
|
369
|
+
pub fn take_staged_skin_offset(&mut self) -> Option<f32> {
|
|
370
|
+
if self.pending_skin_groups.is_empty() {
|
|
371
|
+
return None;
|
|
372
|
+
}
|
|
373
|
+
let group = self.pending_skin_groups.remove(0);
|
|
374
|
+
// PT-7 — the prev-palette queue moves in lockstep so both
|
|
375
|
+
// arenas assign the SAME offset to this draw. A length
|
|
376
|
+
// mismatch (or an unpaired group from a legacy caller) falls
|
|
377
|
+
// back to the current pose = zero skeletal velocity.
|
|
378
|
+
let prev_group = if !self.pending_skin_groups_prev.is_empty() {
|
|
379
|
+
let p = self.pending_skin_groups_prev.remove(0);
|
|
380
|
+
if p.len() == group.len() { p } else { group.clone() }
|
|
381
|
+
} else {
|
|
382
|
+
group.clone()
|
|
383
|
+
};
|
|
384
|
+
let start = self.frame_joint_data.len();
|
|
385
|
+
// Cap at the 1024-slot buffer. Overflowing poses land at offset 0,
|
|
386
|
+
// which at least avoids an out-of-range read — the model will look
|
|
387
|
+
// mis-posed but not corrupt memory.
|
|
388
|
+
if start + group.len() <= 1024 {
|
|
389
|
+
self.frame_joint_data.extend_from_slice(&group);
|
|
390
|
+
self.frame_joint_data_prev.extend_from_slice(&prev_group);
|
|
391
|
+
Some(start as f32)
|
|
392
|
+
} else {
|
|
393
|
+
Some(0.0)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/// Same as `draw_model_mesh_tinted` but applies a Y-axis rotation
|
|
398
|
+
/// (radians) to the mesh local space before scale + translate.
|
|
399
|
+
/// Skinned meshes ignore the rotation here — pose joints already
|
|
400
|
+
/// drive their orientation. CPU-side baking mirrors the unrotated
|
|
401
|
+
/// path so callers can mix rotated and unrotated draws freely
|
|
402
|
+
/// without extra GPU state.
|
|
403
|
+
pub fn draw_model_mesh_tinted_rotated(&mut self, vertices: &[Vertex3D], indices: &[u32], position: [f32; 3], scale: f32, tint: [f32; 4], texture_idx: u32, rot_y: f32) {
|
|
404
|
+
// Mirror the joint-pose plumbing in the non-rotated path so a
|
|
405
|
+
// skinned mesh drawn here still consumes its pending pose.
|
|
406
|
+
let joint_offset = if Self::mesh_has_skin(vertices) {
|
|
407
|
+
self.take_staged_skin_offset()
|
|
408
|
+
} else {
|
|
409
|
+
None
|
|
410
|
+
};
|
|
411
|
+
self.draw_model_mesh_tinted_rotated_with_joints(
|
|
412
|
+
vertices, indices, position, scale, tint, texture_idx, rot_y, joint_offset);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/// Rotated-path body with an explicit joint-buffer offset. Callers
|
|
416
|
+
/// that draw multiple primitives of ONE skinned model pop the staged
|
|
417
|
+
/// pose once (`take_staged_skin_offset`) and pass the same offset to
|
|
418
|
+
/// every primitive.
|
|
419
|
+
pub fn draw_model_mesh_tinted_rotated_with_joints(&mut self, vertices: &[Vertex3D], indices: &[u32], position: [f32; 3], scale: f32, tint: [f32; 4], texture_idx: u32, rot_y: f32, joint_offset: Option<f32>) {
|
|
420
|
+
// Own bounded segment (even if the texture matches) so the shadow
|
|
421
|
+
// pass can cull + cache this draw independently of neighbours.
|
|
422
|
+
self.push_draw_call_3d(texture_idx, true);
|
|
423
|
+
let joint_offset: f32 = joint_offset.unwrap_or(0.0);
|
|
424
|
+
|
|
425
|
+
let cos_y = rot_y.cos();
|
|
426
|
+
let sin_y = rot_y.sin();
|
|
427
|
+
let base = self.vertices_3d.len() as u32;
|
|
428
|
+
let mut seg = SegBounds::new();
|
|
429
|
+
for v in vertices {
|
|
430
|
+
let is_skinned = v.weights[0] + v.weights[1] + v.weights[2] + v.weights[3] > 0.01;
|
|
431
|
+
let pos = if is_skinned {
|
|
432
|
+
v.position
|
|
433
|
+
} else {
|
|
434
|
+
// Rotate local-space position around Y, then scale + translate.
|
|
435
|
+
let lx = v.position[0];
|
|
436
|
+
let ly = v.position[1];
|
|
437
|
+
let lz = v.position[2];
|
|
438
|
+
let rx = cos_y * lx + sin_y * lz;
|
|
439
|
+
let rz = -sin_y * lx + cos_y * lz;
|
|
440
|
+
[rx * scale + position[0],
|
|
441
|
+
ly * scale + position[1],
|
|
442
|
+
rz * scale + position[2]]
|
|
443
|
+
};
|
|
444
|
+
// Rotate the surface normal too so lighting matches the new
|
|
445
|
+
// orientation. Y-axis rotation leaves normal.y untouched.
|
|
446
|
+
let n = v.normal;
|
|
447
|
+
let normal = if is_skinned {
|
|
448
|
+
n
|
|
449
|
+
} else {
|
|
450
|
+
[ cos_y * n[0] + sin_y * n[2],
|
|
451
|
+
n[1],
|
|
452
|
+
-sin_y * n[0] + cos_y * n[2] ]
|
|
453
|
+
};
|
|
454
|
+
// Rotate tangent.xyz the same way; preserve handedness in w.
|
|
455
|
+
let t = v.tangent;
|
|
456
|
+
let tangent = if is_skinned {
|
|
457
|
+
t
|
|
458
|
+
} else {
|
|
459
|
+
[ cos_y * t[0] + sin_y * t[2],
|
|
460
|
+
t[1],
|
|
461
|
+
-sin_y * t[0] + cos_y * t[2],
|
|
462
|
+
t[3] ]
|
|
463
|
+
};
|
|
464
|
+
let joints_out = if is_skinned {
|
|
465
|
+
[v.joints[0] + joint_offset,
|
|
466
|
+
v.joints[1] + joint_offset,
|
|
467
|
+
v.joints[2] + joint_offset,
|
|
468
|
+
v.joints[3] + joint_offset]
|
|
469
|
+
} else {
|
|
470
|
+
v.joints
|
|
471
|
+
};
|
|
472
|
+
seg.note(is_skinned, if is_skinned { v.position } else { pos });
|
|
473
|
+
self.vertices_3d.push(Vertex3D {
|
|
474
|
+
position: pos,
|
|
475
|
+
normal,
|
|
476
|
+
color: [
|
|
477
|
+
v.color[0] * tint[0],
|
|
478
|
+
v.color[1] * tint[1],
|
|
479
|
+
v.color[2] * tint[2],
|
|
480
|
+
v.color[3] * tint[3],
|
|
481
|
+
],
|
|
482
|
+
uv: v.uv,
|
|
483
|
+
joints: joints_out,
|
|
484
|
+
weights: v.weights,
|
|
485
|
+
tangent,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
for &idx in indices {
|
|
489
|
+
self.indices_3d.push(base + idx);
|
|
490
|
+
}
|
|
491
|
+
self.finish_model_segment(seg, joint_offset);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
pub fn draw_model_mesh_tinted(&mut self, vertices: &[Vertex3D], indices: &[u32], position: [f32; 3], scale: f32, tint: [f32; 4], texture_idx: u32) {
|
|
495
|
+
// If this mesh is skinned, consume the next pending pose
|
|
496
|
+
// (FIFO) and pack its matrices into the frame accumulator at
|
|
497
|
+
// the current cursor. Each vertex's joint indices then get
|
|
498
|
+
// shifted by that cursor so the shader samples this mesh's
|
|
499
|
+
// slice of the shared joint buffer. With a 1024-slot buffer,
|
|
500
|
+
// multiple skinned models can coexist in one frame.
|
|
501
|
+
let joint_offset = if Self::mesh_has_skin(vertices) {
|
|
502
|
+
self.take_staged_skin_offset()
|
|
503
|
+
} else {
|
|
504
|
+
None
|
|
505
|
+
};
|
|
506
|
+
self.draw_model_mesh_tinted_with_joints(
|
|
507
|
+
vertices, indices, position, scale, tint, texture_idx, joint_offset);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/// Body of `draw_model_mesh_tinted` with an explicit joint-buffer
|
|
511
|
+
/// offset. Callers that draw multiple primitives of ONE skinned model
|
|
512
|
+
/// pop the staged pose once (`take_staged_skin_offset`) and pass the
|
|
513
|
+
/// same offset to every primitive.
|
|
514
|
+
pub fn draw_model_mesh_tinted_with_joints(&mut self, vertices: &[Vertex3D], indices: &[u32], position: [f32; 3], scale: f32, tint: [f32; 4], texture_idx: u32, joint_offset: Option<f32>) {
|
|
515
|
+
// Own bounded segment — see the rotated variant.
|
|
516
|
+
self.push_draw_call_3d(texture_idx, true);
|
|
517
|
+
let joint_offset: f32 = joint_offset.unwrap_or(0.0);
|
|
518
|
+
|
|
519
|
+
let base = self.vertices_3d.len() as u32;
|
|
520
|
+
let mut seg = SegBounds::new();
|
|
521
|
+
for v in vertices {
|
|
522
|
+
// Check if vertex is skinned (has non-zero weights)
|
|
523
|
+
let is_skinned = v.weights[0] + v.weights[1] + v.weights[2] + v.weights[3] > 0.01;
|
|
524
|
+
let pos = if is_skinned {
|
|
525
|
+
// Skinned: pass raw bind-pose positions — joint matrices handle transform
|
|
526
|
+
v.position
|
|
527
|
+
} else {
|
|
528
|
+
// Unskinned: apply CPU-side position + scale
|
|
529
|
+
[v.position[0] * scale + position[0],
|
|
530
|
+
v.position[1] * scale + position[1],
|
|
531
|
+
v.position[2] * scale + position[2]]
|
|
532
|
+
};
|
|
533
|
+
let joints_out = if is_skinned {
|
|
534
|
+
[v.joints[0] + joint_offset,
|
|
535
|
+
v.joints[1] + joint_offset,
|
|
536
|
+
v.joints[2] + joint_offset,
|
|
537
|
+
v.joints[3] + joint_offset]
|
|
538
|
+
} else {
|
|
539
|
+
v.joints
|
|
540
|
+
};
|
|
541
|
+
seg.note(is_skinned, if is_skinned { v.position } else { pos });
|
|
542
|
+
self.vertices_3d.push(Vertex3D {
|
|
543
|
+
position: pos,
|
|
544
|
+
normal: v.normal,
|
|
545
|
+
color: [
|
|
546
|
+
v.color[0] * tint[0],
|
|
547
|
+
v.color[1] * tint[1],
|
|
548
|
+
v.color[2] * tint[2],
|
|
549
|
+
v.color[3] * tint[3],
|
|
550
|
+
],
|
|
551
|
+
uv: v.uv,
|
|
552
|
+
joints: joints_out,
|
|
553
|
+
weights: v.weights,
|
|
554
|
+
tangent: v.tangent,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
for &idx in indices {
|
|
558
|
+
self.indices_3d.push(base + idx);
|
|
559
|
+
}
|
|
560
|
+
self.finish_model_segment(seg, joint_offset);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/// Fold a model draw's accumulated bounds into its (freshly-pushed)
|
|
564
|
+
/// segment. Skinned content is bounded by the union of its joint
|
|
565
|
+
/// matrices applied to the rest-pose AABB — rigorous, because a
|
|
566
|
+
/// skinned vertex is a convex combination of its per-joint
|
|
567
|
+
/// transforms of one rest position, and a convex combination of
|
|
568
|
+
/// points inside a set of AABBs stays inside their union AABB.
|
|
569
|
+
fn finish_model_segment(&mut self, seg: SegBounds, joint_offset: f32) {
|
|
570
|
+
let mut wmin = seg.world_min;
|
|
571
|
+
let mut wmax = seg.world_max;
|
|
572
|
+
if seg.any_skinned && seg.rest_min[0] <= seg.rest_max[0] {
|
|
573
|
+
// The model's joints occupy frame_joint_data[joint_offset..len]
|
|
574
|
+
// at this point: its group was staged immediately before its
|
|
575
|
+
// primitives and the next model's group hasn't been staged yet.
|
|
576
|
+
let jstart = (joint_offset.max(0.0) as usize).min(self.frame_joint_data.len());
|
|
577
|
+
for j in jstart..self.frame_joint_data.len() {
|
|
578
|
+
let (m0, m1) = super::transform_aabb(
|
|
579
|
+
&self.frame_joint_data[j], seg.rest_min, seg.rest_max,
|
|
580
|
+
);
|
|
581
|
+
for a in 0..3 {
|
|
582
|
+
if m0[a] < wmin[a] { wmin[a] = m0[a]; }
|
|
583
|
+
if m1[a] > wmax[a] { wmax[a] = m1[a]; }
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
let call = self.draw_calls_3d.last_mut()
|
|
588
|
+
.expect("finish_model_segment: segment was pushed at fn start");
|
|
589
|
+
call.has_skinned = seg.any_skinned;
|
|
590
|
+
call.content_hash = seg.hash;
|
|
591
|
+
// Sentinel stays (min > max) when nothing contributed bounds —
|
|
592
|
+
// the shadow pass then treats the segment as uncullable.
|
|
593
|
+
if wmin[0] <= wmax[0] {
|
|
594
|
+
call.wmin = wmin;
|
|
595
|
+
call.wmax = wmax;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/// Per-model-draw bounds accumulator: world AABB + content hash for the
|
|
601
|
+
/// non-skinned verts (already world-space at append time), rest-pose
|
|
602
|
+
/// AABB for the skinned ones (their world placement lives in the joint
|
|
603
|
+
/// matrices — see `finish_model_segment`).
|
|
604
|
+
struct SegBounds {
|
|
605
|
+
world_min: [f32; 3],
|
|
606
|
+
world_max: [f32; 3],
|
|
607
|
+
rest_min: [f32; 3],
|
|
608
|
+
rest_max: [f32; 3],
|
|
609
|
+
any_skinned: bool,
|
|
610
|
+
hash: u64,
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
impl SegBounds {
|
|
614
|
+
fn new() -> Self {
|
|
615
|
+
SegBounds {
|
|
616
|
+
world_min: [f32::MAX; 3],
|
|
617
|
+
world_max: [f32::MIN; 3],
|
|
618
|
+
rest_min: [f32::MAX; 3],
|
|
619
|
+
rest_max: [f32::MIN; 3],
|
|
620
|
+
any_skinned: false,
|
|
621
|
+
hash: super::types::FNV_OFFSET,
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#[inline]
|
|
626
|
+
fn note(&mut self, is_skinned: bool, pos: [f32; 3]) {
|
|
627
|
+
if is_skinned {
|
|
628
|
+
self.any_skinned = true;
|
|
629
|
+
for a in 0..3 {
|
|
630
|
+
if pos[a] < self.rest_min[a] { self.rest_min[a] = pos[a]; }
|
|
631
|
+
if pos[a] > self.rest_max[a] { self.rest_max[a] = pos[a]; }
|
|
632
|
+
}
|
|
633
|
+
} else {
|
|
634
|
+
for a in 0..3 {
|
|
635
|
+
if pos[a] < self.world_min[a] { self.world_min[a] = pos[a]; }
|
|
636
|
+
if pos[a] > self.world_max[a] { self.world_max[a] = pos[a]; }
|
|
637
|
+
}
|
|
638
|
+
self.hash = super::types::fnv1a_bytes(self.hash, bytemuck::bytes_of(&pos));
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|