ship: apply scale + gate approximate external parts behind a toggle
Two changes after user feedback that externals sit near-but-wrong (shield inset into the hull, thruster floating close): - Scale: the joint table's slots 5–7 are per-axis scale (a GN_ShieldG frame ships at 0.5, GN_Jet FX at 2.4–6.5) and were being ignored, rendering scaled parts at the wrong size. ScenePart now carries `s` and applies R·(S·v)+T. - External placement is fundamentally approximate: a GN_* frame is the mount PIVOT (f105's two GN_ShieldG frames are both on the centreline), while the part's outboard/rotational offset lives in a detail sub-rig / runtime code this static pass can't recover. So assemble_ship gains `include_external`: the hull tier (rou_ nodes) is exact and always drawn; the external tier (bridge / shield / engine at GN_ frames) is opt-in via a "Show external parts" checkbox in the Ships browser, off by default so the clean, correct hull is the default view. The module doc is corrected to the scene-graph mechanism (the old "draw parts untransformed" claim was wrong). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1849,15 +1849,19 @@ pub struct ScenePart {
|
||||
pub m: [[f32; 3]; 3],
|
||||
/// World translation, `(X, up→Y, fore/aft→Z)`.
|
||||
pub t: [f32; 3],
|
||||
/// Per-axis local scale (a shield generator ships at 0.5, a jet FX at 2.4).
|
||||
pub s: [f32; 3],
|
||||
}
|
||||
|
||||
impl ScenePart {
|
||||
/// Apply the world transform to a local vertex.
|
||||
/// Apply the world transform to a local vertex: `R·(S·v) + T` (scale is a
|
||||
/// leaf-local factor; the composite's structural nodes are all unit-scale).
|
||||
pub fn apply(&self, v: [f32; 3]) -> [f32; 3] {
|
||||
let sv = [v[0] * self.s[0], v[1] * self.s[1], v[2] * self.s[2]];
|
||||
[
|
||||
self.m[0][0] * v[0] + self.m[0][1] * v[1] + self.m[0][2] * v[2] + self.t[0],
|
||||
self.m[1][0] * v[0] + self.m[1][1] * v[1] + self.m[1][2] * v[2] + self.t[1],
|
||||
self.m[2][0] * v[0] + self.m[2][1] * v[1] + self.m[2][2] * v[2] + self.t[2],
|
||||
self.m[0][0] * sv[0] + self.m[0][1] * sv[1] + self.m[0][2] * sv[2] + self.t[0],
|
||||
self.m[1][0] * sv[0] + self.m[1][1] * sv[1] + self.m[1][2] * sv[2] + self.t[1],
|
||||
self.m[2][0] * sv[0] + self.m[2][1] * sv[1] + self.m[2][2] * sv[2] + self.t[2],
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1916,6 +1920,7 @@ pub fn scene_world_nodes(bytes: &[u8], composite_name: &str) -> Vec<ScenePart> {
|
||||
name: String,
|
||||
local_m: M3,
|
||||
local_t: [f32; 3],
|
||||
local_s: [f32; 3],
|
||||
sib_end: Option<usize>,
|
||||
}
|
||||
let mut recs: Vec<Rec> = Vec::new();
|
||||
@@ -1939,15 +1944,18 @@ pub fn scene_world_nodes(bytes: &[u8], composite_name: &str) -> Vec<ScenePart> {
|
||||
let trs = if t44 != 0 && t44 + 32 <= d.len() {
|
||||
read_trs(t44)
|
||||
} else {
|
||||
[0.0; 8]
|
||||
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
|
||||
};
|
||||
let local_t = [trs[0] as f32, trs[2] as f32, trs[1] as f32];
|
||||
let local_m = m3_mul(rot_z(trs[4] as f32), rot_x(trs[3] as f32));
|
||||
// Slots 5–7 are per-axis scale (a hardpoint part ships at e.g. 0.5).
|
||||
let sc = |v: f64| if v.abs() < 1e-6 { 1.0 } else { v as f32 };
|
||||
let local_s = [sc(trs[5]), sc(trs[6]), sc(trs[7])];
|
||||
// Next sibling begins 4 bytes before its pointer, at a `rou_`/`GN_` name.
|
||||
let sib_end = sib_ptr.checked_sub(4).filter(|&s| {
|
||||
s > i && s + 4 <= head_end && (&d[s..s + 4] == b"rou_" || &d[s..s + 3] == b"GN_")
|
||||
});
|
||||
recs.push(Rec { name_start: i, name, local_m, local_t, sib_end });
|
||||
recs.push(Rec { name_start: i, name, local_m, local_t, local_s, sib_end });
|
||||
i = j.max(i + 1);
|
||||
}
|
||||
|
||||
@@ -1962,7 +1970,7 @@ pub fn scene_world_nodes(bytes: &[u8], composite_name: &str) -> Vec<ScenePart> {
|
||||
let wm = m3_mul(pm, rec.local_m);
|
||||
let r = m3_vec(pm, rec.local_t);
|
||||
let wt = [r[0] + pt[0], r[1] + pt[1], r[2] + pt[2]];
|
||||
out.push(ScenePart { resource: rec.name.clone(), m: wm, t: wt });
|
||||
out.push(ScenePart { resource: rec.name.clone(), m: wm, t: wt, s: rec.local_s });
|
||||
let end = rec
|
||||
.sib_end
|
||||
.unwrap_or_else(|| stack.last().map(|s| s.0).unwrap_or(head_end));
|
||||
|
||||
Reference in New Issue
Block a user