Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions native/shared/src/ffi_core/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,81 @@ macro_rules! __bloom_ffi_models {
$crate::ffi::feature_off_warn_once("bloom_draw_model_rotated", "models3d");
}

// bloom_draw_model_transform16 — EN-039. [gated: models3d]
//
// The immediate-mode twin of bloom_scene_set_transform16: a full
// column-major 4x4, so an immediate draw can finally pitch and roll.
// bloom_draw_model_rotated only takes a Y rotation, which is why the
// shooter's gun cannot tilt with the aim.
//
// The ticket proposed routing this through the mesh scratch on the
// grounds that "Perry can't pass 16 f64 args". It can:
// bloom_scene_set_transform16 already passes 17 and works. Spelling the
// matrix out keeps this STATELESS — no scratch to reset, no ordering
// hazard between a reset and a draw — which is the same reasoning
// recorded on the scene twin.
//
// SKINNED models are rejected rather than silently mis-drawn: their
// joint matrices already bake world orientation, so a caller-supplied
// model matrix would double-transform them. bloom_draw_model_rotated
// has the same carve-out (it ignores rot_y for skinned).
#[cfg(feature = "models3d")]
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn bloom_draw_model_transform16(
handle: f64,
m0: f64, m1: f64, m2: f64, m3: f64,
m4: f64, m5: f64, m6: f64, m7: f64,
m8: f64, m9: f64, m10: f64, m11: f64,
m12: f64, m13: f64, m14: f64, m15: f64,
color_packed_argb: f64,
) {
$crate::ffi::guard("bloom_draw_model_transform16", move || {
let bits = color_packed_argb as u32;
let a = ((bits >> 24) & 0xff) as f32 / 255.0;
let r = ((bits >> 16) & 0xff) as f32 / 255.0;
let g = ((bits >> 8) & 0xff) as f32 / 255.0;
let b = ( bits & 0xff) as f32 / 255.0;
let s = [
m0, m1, m2, m3,
m4, m5, m6, m7,
m8, m9, m10, m11,
m12, m13, m14, m15,
];
let mut mat = [[0.0f32; 4]; 4];
for col in 0..4 {
for row in 0..4 {
mat[col][row] = s[col * 4 + row] as f32;
}
}
let eng = engine();
if let Some(model) = eng.models.get(handle) {
let handle_bits = handle.to_bits();
if eng.renderer.cache_model_if_static(handle_bits, &model.meshes) {
if eng.renderer.is_model_skinned(handle_bits) {
return; // see the note above
}
eng.renderer.draw_model_cached_transform(
handle_bits, mat, [r, g, b, a],
);
}
}
})
}
#[cfg(not(feature = "models3d"))]
#[no_mangle]
#[allow(clippy::too_many_arguments)]
pub extern "C" fn bloom_draw_model_transform16(
_handle: f64,
_m0: f64, _m1: f64, _m2: f64, _m3: f64,
_m4: f64, _m5: f64, _m6: f64, _m7: f64,
_m8: f64, _m9: f64, _m10: f64, _m11: f64,
_m12: f64, _m13: f64, _m14: f64, _m15: f64,
_color_packed_argb: f64,
) {
$crate::ffi::feature_off_warn_once("bloom_draw_model_transform16", "models3d");
}

// bloom_unload_model [source: linux; gated: models3d]
#[cfg(feature = "models3d")]
#[no_mangle]
Expand Down
45 changes: 45 additions & 0 deletions native/shared/src/renderer/model_draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,51 @@ impl Renderer {
}
}

/// EN-039 — `draw_model_cached` taking the model matrix WHOLE, instead of
/// composing it from a position/scale/yaw. `draw_model_cached_rotated` above
/// can only express a Y rotation, so an immediate-mode caller could not
/// pitch or roll: the shooter's weapon stays level while the camera looks up
/// or down, and the same wall hits any held prop, thrown object or debris
/// chunk. Everything downstream (alpha cutout, normal/MR maps, foliage wind,
/// cutout shadows, planar reflections) is identical — this only skips the
/// composition step and trusts the caller's matrix.
pub fn draw_model_cached_transform(
&mut self,
handle_bits: u64,
model_matrix: [[f32; 4]; 4],
tint: [f32; 4],
) {
let mesh_count = match self.model_gpu_cache.get(&handle_bits) {
Some(Some(meshes)) => meshes.len(),
_ => return,
};

let foliage = self.foliage_wind.get(&handle_bits).copied().unwrap_or(0.0);

for mesh_idx in 0..mesh_count {
let slot = self.next_model_uniform_slot;
self.next_model_uniform_slot += 1;
self.ensure_model_uniform_slot(slot);

let model_mvp = mat4_multiply(self.current_vp_matrix, model_matrix);
self.stage_model_uniform(slot, &Uniforms3D {
mvp: model_mvp, model: model_matrix,
prev_mvp: model_mvp, model_tint: tint,
misc: [0.0, 0.0, foliage, 0.0],
});

self.model_draw_commands.push(CachedModelDraw {
uniform_slot: slot,
cache_handle: handle_bits,
mesh_idx,
model: model_matrix,
skinned: false,
joint_offset: 0.0,
bounds_override: None,
});
}
}

/// Cached-path draw for a SKINNED model: the bind-pose VB/IB stay
/// GPU-resident (raw joint indices) and the scene VS skins from the
/// shared joint buffer — replacing the immediate path's per-frame
Expand Down
24 changes: 24 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,30 @@
],
"returns": "void"
},
{
"name": "bloom_draw_model_transform16",
"params": [
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64",
"f64"
],
"returns": "void"
},
{
"name": "bloom_unload_model",
"params": [
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export {
} from './textures/index';

export {
loadModel, drawModel, drawModelRotated, unloadModel, getModelBounds, genMeshSplineRibbon,
loadModel, drawModel, drawModelRotated, drawModelTransform, unloadModel, getModelBounds, genMeshSplineRibbon,
setModelFoliageWind, setFoliageShadowMotion,
drawCube, drawCubeWires, drawSphere, drawSphereWires,
drawCylinder, drawPlane, drawGrid, drawRay, genMeshCube, genMeshHeightmap,
Expand Down
42 changes: 42 additions & 0 deletions src/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import { Color, Model, Vec3, Mat4, BoundingBox } from '../core/types';
declare function bloom_load_model(path: number): number;
declare function bloom_draw_model(handle: number, x: number, y: number, z: number, scale: number, r: number, g: number, b: number, a: number): void;
declare function bloom_draw_model_rotated(handle: number, x: number, y: number, z: number, scale: number, rotY: number, colorPackedArgb: number): void;
declare function bloom_draw_model_transform16(
handle: number,
m0: number, m1: number, m2: number, m3: number,
m4: number, m5: number, m6: number, m7: number,
m8: number, m9: number, m10: number, m11: number,
m12: number, m13: number, m14: number, m15: number,
colorPackedArgb: number,
): void;
declare function bloom_set_model_foliage_wind(handle: number, amount: number): void;
declare function bloom_set_foliage_shadow_motion(on: number): void;
declare function bloom_unload_model(handle: number): void;
Expand Down Expand Up @@ -182,6 +190,40 @@ export function drawModelRotated(
bloom_draw_model_rotated(model.handle, position.x, position.y, position.z, scale, rotY * Math.PI / 180, packed);
}

/**
* EN-039 — draw a model under a full column-major 4x4 transform, so an
* immediate-mode draw can pitch and roll. `drawModelRotated` above can only
* express a Y rotation: a held weapon cannot tilt with the aim, and neither can
* a thrown prop or a debris chunk.
*
* `m16` is column-major (same layout as `setSceneNodeTransform16` and as
* `mat4_*` in this engine — note the two conventions warning in the PT docs:
* `mat4_multiply` is RAW). It carries translation and scale too, so this
* REPLACES position/scale rather than combining with them.
*
* Skinned models are a no-op here on purpose: their joint matrices already bake
* world orientation, so applying a model matrix on top double-transforms them.
* Use `animUpdate` for those.
*
* Tint components are 0-255. RGBA packs into one f64 (ARGB byte order), as in
* `drawModelRotated`.
*/
export function drawModelTransform(model: Model, m16: number[], tint: Color): void {
const a = (tint.a & 0xff) << 24;
const r = (tint.r & 0xff) << 16;
const g = (tint.g & 0xff) << 8;
const b = tint.b & 0xff;
const packed = (a | r | g | b) >>> 0;
bloom_draw_model_transform16(
model.handle,
m16[0], m16[1], m16[2], m16[3],
m16[4], m16[5], m16[6], m16[7],
m16[8], m16[9], m16[10], m16[11],
m16[12], m16[13], m16[14], m16[15],
packed,
);
}

export function unloadModel(model: Model): void {
bloom_unload_model(model.handle);
}
Expand Down
Loading