Hausputz: UV-Doppel-Flip raus, tga::load → Result, hide_object entfernt
UVs laufen jetzt durchgehend in glTF-/tga-Konvention (der Flip-Hin-und- Zurück stammte vom gelöschten OBJ-Pfad). Kaputte Welt-Texturen fallen auf den Platzhalter statt zu panicken. Player::eye() statt vierfacher Augenpunkt-Rechnung. hide_object war ein irl3d-Demo-Relikt ohne Konsument. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+1
-3
@@ -26,9 +26,7 @@ pub type Signals = HashMap<String, Vec<String>>;
|
||||
/// geleert (konsumiert).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Action {
|
||||
/// Instance unsichtbar + kollisionslos + nicht mehr interagierbar machen.
|
||||
HideObject(String),
|
||||
/// WAV unter `assets/audio/<name>` als SFX abspielen.
|
||||
/// `assets/sounds/{name}.wav` als Einmal-SFX abspielen.
|
||||
PlaySound(String),
|
||||
/// Anzeige-/Eingabemodus wechseln (Spiel, Flycam, Menü). Vom Frontend
|
||||
/// auf seinen `Mode` gemappt; der Dialog-Modus ist hier bewusst nicht
|
||||
|
||||
+3
-7
@@ -139,10 +139,6 @@ fn append_mesh(
|
||||
o.verts.push(transform(world, *p));
|
||||
o.uvs.push(*uv);
|
||||
}
|
||||
// glTF-UVs haben den Ursprung oben links — auf die Modell-Konvention
|
||||
// (unten links, wie OBJ) spiegeln, damit der Render-Konsument beide
|
||||
// Pfade gleich behandelt.
|
||||
for uv in &mut o.uvs[base..] { uv[1] = 1.0 - uv[1]; }
|
||||
|
||||
let idxs: Vec<usize> = match prim["indices"].as_u64() {
|
||||
Some(a) => read_indices(doc, bin, a as usize)?,
|
||||
@@ -410,10 +406,10 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uv_origin_is_flipped_to_model_convention() {
|
||||
// glTF-UV (1,1) (oben-links-Ursprung) → Modell-Konvention (1,0).
|
||||
fn uvs_pass_through_unchanged() {
|
||||
// glTF-UVs (Ursprung oben links) sind die Modell-Konvention — kein Flip.
|
||||
let m = parse_glb(&sample_glb()).unwrap();
|
||||
assert_eq!(m.objects[0].uvs[2], [1.0, 0.0]);
|
||||
assert_eq!(m.objects[0].uvs[2], [1.0, 1.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-2
@@ -39,8 +39,8 @@ pub struct Object {
|
||||
/// Positionen in Engine-Koords (Y-up, Blick −Z), Welt-Raum (Transforms
|
||||
/// vom Loader eingebacken), Maßstab 1:1 (Blender-Meter = Engine-Unit).
|
||||
pub verts: Vec<[f32; 3]>,
|
||||
/// UVs parallel zu `verts`, V-Ursprung unten links (OBJ-/glTF-Rohform;
|
||||
/// den Flip auf Bild-Konvention macht der Render-Konsument).
|
||||
/// UVs parallel zu `verts`, V-Ursprung oben links — glTF- und zugleich
|
||||
/// `tga::Image`-Konvention, direkt sampelbar ohne Flip.
|
||||
pub uvs: Vec<[f32; 2]>,
|
||||
pub tris: Vec<[usize; 3]>,
|
||||
/// Material-Index je Dreieck, parallel zu `tris` (→ `Model::materials`).
|
||||
|
||||
@@ -71,6 +71,12 @@ impl Player {
|
||||
self.pitch = (self.pitch - dy * sens).clamp(-PITCH_LIMIT, PITCH_LIMIT);
|
||||
}
|
||||
|
||||
/// Augenpunkt (Fußpunkt + [`EYE_HEIGHT`]) — Kamera- und Hörposition,
|
||||
/// Startpunkt des Point-and-Click-Strahls.
|
||||
pub fn eye(&self) -> [f32; 3] {
|
||||
[self.pos[0], self.pos[1] + EYE_HEIGHT, self.pos[2]]
|
||||
}
|
||||
|
||||
/// Blickrichtung inkl. Pitch — dieselbe Formel wie `camera::forward`/
|
||||
/// `math::view` (yaw=0 → −Z, positiver Pitch hebt). Für den
|
||||
/// Point-and-Click-Strahl aus der Bildmitte.
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
//! `set <name> <value>` KV setzen (true/false/i32/f32/string)
|
||||
//! `inc <name> [<delta>]` KV-Integer inkrementieren (Default +1)
|
||||
//! `clear <name>` KV-Eintrag entfernen
|
||||
//! `hide_object <name>` → Action::HideObject (deferred)
|
||||
//! `play_sound <name>` → Action::PlaySound (deferred): SFX aus
|
||||
//! `assets/sounds/{name}.wav`
|
||||
//! `mode <play|free|menu>` → Action::SetMode (deferred): Anzeige-/
|
||||
@@ -22,7 +21,7 @@
|
||||
//!
|
||||
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
||||
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
||||
//! wie `hide_object $self` für viele duplizierte Instances wirken, ohne pro
|
||||
//! (z.B. `set $self true`) für viele duplizierte Instances wirken, ohne pro
|
||||
//! Instance einen Eintrag in signals.toml zu brauchen.
|
||||
//!
|
||||
//! Dispatch-Reihenfolge: erst Signal-Lookup; steht der Name in der Table,
|
||||
@@ -79,7 +78,7 @@ pub fn dispatch(signal: &str, ctx: &mut ActionCtx) {
|
||||
/// entscheidet, ob das eine Meldung wert ist — siehe `dispatch`).
|
||||
fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
|
||||
// $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten,
|
||||
// damit Tokens wie `hide_object $self` einheitlich funktionieren. Ohne
|
||||
// damit Tokens wie `set $self true` einheitlich funktionieren. Ohne
|
||||
// instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action
|
||||
// zielt dann ins Leere, was für deferred Actions ein No-Op beim
|
||||
// Konsumenten ist.
|
||||
@@ -98,7 +97,6 @@ fn execute(cmd: &str, ctx: &mut ActionCtx) -> bool {
|
||||
"set" => kv::apply_set(args, ctx.kv),
|
||||
"inc" => kv::apply_inc(args, ctx.kv),
|
||||
"clear" => kv::apply_clear(args, ctx.kv),
|
||||
"hide_object" => ctx.actions.push(Action::HideObject(args.to_string())),
|
||||
"play_sound" => ctx.actions.push(Action::PlaySound(args.to_string())),
|
||||
"mode" => if let Some(t) = ModeTarget::parse(args) {
|
||||
ctx.actions.push(Action::SetMode(t));
|
||||
@@ -183,8 +181,7 @@ mod tests {
|
||||
let mut signals = Signals::new();
|
||||
signals.insert("pickup".into(), vec![
|
||||
"inc items".into(),
|
||||
"play_sound pickup".into(),
|
||||
"hide_object $self".into(),
|
||||
"play_sound $self".into(), // $self-Substitution in deferred Action
|
||||
]);
|
||||
let mut game = Game::new(signals);
|
||||
|
||||
@@ -193,8 +190,7 @@ mod tests {
|
||||
|
||||
assert_eq!(game.kv["items"].coerce_to_int().unwrap(), 1);
|
||||
assert_eq!(game.actions, vec![
|
||||
Action::PlaySound("pickup".into()),
|
||||
Action::HideObject("Mushroom.005".into()),
|
||||
Action::PlaySound("Mushroom.005".into()),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -19,9 +19,9 @@ pub struct Image {
|
||||
pub rgba: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn load(path: &str) -> Image {
|
||||
let bytes = std::fs::read(path).unwrap_or_else(|e| panic!("tga load {path}: {e}"));
|
||||
decode(&bytes).unwrap_or_else(|e| panic!("tga decode {path}: {e}"))
|
||||
pub fn load(path: &str) -> Result<Image, String> {
|
||||
let bytes = std::fs::read(path).map_err(|e| format!("tga load {path}: {e}"))?;
|
||||
decode(&bytes).map_err(|e| format!("tga {path}: {e}"))
|
||||
}
|
||||
|
||||
pub fn decode(d: &[u8]) -> Result<Image, String> {
|
||||
|
||||
Reference in New Issue
Block a user