TGA-Loader raus, alles PNG
Der einzige verbliebene TGA-Nutzen — UI, Fonts, Platzhalter — läuft jetzt über engine::image::load (derselbe PNG-Decoder wie für die .glb). engine::tga ist damit weg. - image::load(path): PNG von der Platte, Format per Signatur. - Fonts/Cursor/Ornamente/Platzhalter als .png (Fonts + UI neu aus dem Quellmaterial exportiert, placeholder aus der alten .tga konvertiert). - Test loads_the_real_project_assets deckt die fünf Projekt-Assets ab. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0196AM4UyAJEXVtPvp5DdKr8
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 256 KiB |
|
After Width: | Height: | Size: 347 B |
|
Before Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 427 B |
|
Before Width: | Height: | Size: 4.0 KiB |
@@ -10,13 +10,14 @@ Start in der Konsole gemeldet (`[model]`, `[audio]`, `[tex]`, …).
|
|||||||
|
|
||||||
- **Format:** glTF Binary (`.glb`) nach `assets/maps/props/` — alle Dateien
|
- **Format:** glTF Binary (`.glb`) nach `assets/maps/props/` — alle Dateien
|
||||||
dort werden beim Start geladen.
|
dort werden beim Start geladen.
|
||||||
- **Beim Export anhaken:** „Include → Custom Properties" (sonst kommen
|
- **Beim Export anhaken:** „Include → Custom Properties" (sonst fehlen die
|
||||||
keine der Properties unten an!).
|
Properties unten) **und** „Include → Textures" (sonst fehlen die Texturen).
|
||||||
- **Maßstab:** 1 Blender-Meter = 1 Engine-Meter. Augenhöhe 1,6 m,
|
- **Maßstab:** 1 Blender-Meter = 1 Engine-Meter. Augenhöhe 1,6 m,
|
||||||
Türen/Durchgänge ≥ 2 m hoch und ≥ 0,8 m breit denken.
|
Türen/Durchgänge ≥ 2 m hoch und ≥ 0,8 m breit denken.
|
||||||
- **Material-Name = Textur-Name:** Material `holz` → `assets/textures/holz.tga`.
|
- **Texturen:** Bild im Material als Base Color zuweisen — wird beim Export
|
||||||
Fehlt die Textur (oder das Material), erscheint die Platzhalter-Textur.
|
in die `.glb` eingebettet (PNG), kein `assets/textures/` mehr. Fehlt das
|
||||||
- **Texturen:** TGA, unkomprimiert oder RLE, 24/32 bit.
|
Bild, erscheint die Platzhalter-Textur. Kacheln/Skalieren über einen
|
||||||
|
Mapping-Node (`KHR_texture_transform` wird beim Laden in die UVs gebacken).
|
||||||
|
|
||||||
### Custom Properties auf Mesh-Objekten
|
### Custom Properties auf Mesh-Objekten
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
//! UV-Konvention) selbst.
|
//! UV-Konvention) selbst.
|
||||||
//!
|
//!
|
||||||
//! [`decode`] liest die Bilder, die Blender beim glTF-Export in die `.glb`
|
//! [`decode`] liest die Bilder, die Blender beim glTF-Export in die `.glb`
|
||||||
//! packt (PNG). TGA-Dateien (UI, Fonts) laufen über [`super::tga`] und liefern
|
//! packt; [`load`] dieselbe Sorte Bild von der Platte (UI, Fonts,
|
||||||
//! denselben `Image`-Typ.
|
//! Platzhalter). Beides PNG — ein Decoder für alles.
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
@@ -15,6 +15,12 @@ pub struct Image {
|
|||||||
pub rgba: Vec<u8>,
|
pub rgba: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Eine PNG-Datei von der Platte laden.
|
||||||
|
pub fn load(path: &str) -> Result<Image, String> {
|
||||||
|
let bytes = std::fs::read(path).map_err(|e| format!("image load {path}: {e}"))?;
|
||||||
|
decode(&bytes, "").map_err(|e| format!("image {path}: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Ein eingebettetes glTF-Bild dekodieren. `mime` ist der glTF-`mimeType`
|
/// Ein eingebettetes glTF-Bild dekodieren. `mime` ist der glTF-`mimeType`
|
||||||
/// (darf leer sein — dann entscheidet die Datei-Signatur). Unterstützt wird
|
/// (darf leer sein — dann entscheidet die Datei-Signatur). Unterstützt wird
|
||||||
/// nur PNG; JPEG/WebP werden gemeldet, nicht dekodiert.
|
/// nur PNG; JPEG/WebP werden gemeldet, nicht dekodiert.
|
||||||
@@ -94,4 +100,19 @@ mod tests {
|
|||||||
assert!(decode(b"\xff\xd8\xff\xe0junk", "image/jpeg").is_err());
|
assert!(decode(b"\xff\xd8\xff\xe0junk", "image/jpeg").is_err());
|
||||||
assert!(decode(b"whatever", "").is_err());
|
assert!(decode(b"whatever", "").is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loads_the_real_project_assets() {
|
||||||
|
for (path, w, h) in [
|
||||||
|
("assets/textures/placeholder.png", 256, 256),
|
||||||
|
("assets/textures/fonts/oldschool-ega-8x14.png", 256, 112),
|
||||||
|
("assets/textures/fonts/oldschool-cga-8x8.png", 256, 64),
|
||||||
|
("assets/textures/ui/cursors.png", 32, 32),
|
||||||
|
("assets/textures/ui/panel_ornaments.png", 32, 32),
|
||||||
|
] {
|
||||||
|
let img = load(path).unwrap_or_else(|e| panic!("{path}: {e}"));
|
||||||
|
assert_eq!((img.width, img.height), (w, h), "{path}");
|
||||||
|
assert_eq!(img.rgba.len(), (w * h * 4) as usize, "{path}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,11 +14,12 @@
|
|||||||
//! so wie story_ctrl Tags zurückgibt, statt selbst `signals::dispatch` zu
|
//! so wie story_ctrl Tags zurückgibt, statt selbst `signals::dispatch` zu
|
||||||
//! rufen.
|
//! rufen.
|
||||||
//!
|
//!
|
||||||
//! `gltf`, `tga`, `image` und `wav` sind reine Decoder (Bytes → owned Daten);
|
//! `gltf`, `image` und `wav` sind reine Decoder (Bytes → owned Daten);
|
||||||
//! `gltf` ist DER 3D-Pfad und produziert das `model::Model`, den Vertrag
|
//! `gltf` ist DER 3D-Pfad und produziert das `model::Model`, den Vertrag
|
||||||
//! zwischen Loader und seinen Konsumenten (collision, trigger, audio,
|
//! zwischen Loader und seinen Konsumenten (collision, trigger, audio,
|
||||||
//! render::props) — samt der Welt-Texturen, die als PNG in der `.glb` liegen
|
//! render::props) — samt der Welt-Texturen, die als PNG in der `.glb` liegen.
|
||||||
//! (dekodiert über `image`, das den RGBA8-`Image`-Typ auch für `tga` stellt).
|
//! `image` dekodiert PNG (eingebettet wie von der Platte) und stellt den
|
||||||
|
//! RGBA8-`Image`-Typ für alle.
|
||||||
//! `audio` ist die Emitter-Logik zum Frontend-Treiber render::audio, `player`
|
//! `audio` ist die Emitter-Logik zum Frontend-Treiber render::audio, `player`
|
||||||
//! die First-Person-Physik auf der Welt aus `collision`. Post-Effekte kennt
|
//! die First-Person-Physik auf der Welt aus `collision`. Post-Effekte kennt
|
||||||
//! der Kern nur als `game::Action::SetPost` — die Kette lebt in render::post.
|
//! der Kern nur als `game::Action::SetPost` — die Kette lebt in render::post.
|
||||||
@@ -35,6 +36,5 @@ pub mod model;
|
|||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod signals;
|
pub mod signals;
|
||||||
pub mod story_ctrl;
|
pub mod story_ctrl;
|
||||||
pub mod tga;
|
|
||||||
pub mod trigger;
|
pub mod trigger;
|
||||||
pub mod wav;
|
pub mod wav;
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ pub struct Object {
|
|||||||
/// vom Loader eingebacken), Maßstab 1:1 (Blender-Meter = Engine-Unit).
|
/// vom Loader eingebacken), Maßstab 1:1 (Blender-Meter = Engine-Unit).
|
||||||
pub verts: Vec<[f32; 3]>,
|
pub verts: Vec<[f32; 3]>,
|
||||||
/// UVs parallel zu `verts`, V-Ursprung oben links — glTF- und zugleich
|
/// UVs parallel zu `verts`, V-Ursprung oben links — glTF- und zugleich
|
||||||
/// `tga::Image`-Konvention, direkt sampelbar ohne Flip.
|
/// `image::Image`-Konvention, direkt sampelbar ohne Flip.
|
||||||
pub uvs: Vec<[f32; 2]>,
|
pub uvs: Vec<[f32; 2]>,
|
||||||
/// Normalen parallel zu `verts`, normiert, Welt-Raum. Fehlen sie im
|
/// Normalen parallel zu `verts`, normiert, Welt-Raum. Fehlen sie im
|
||||||
/// Modell, rechnet der Loader sie aus den Flächen.
|
/// Modell, rechnet der Loader sie aus den Flächen.
|
||||||
|
|||||||
@@ -1,144 +0,0 @@
|
|||||||
//! TGA-Decoder → fertig dekodiertes RGBA8-[`Image`] (der Typ lebt in
|
|
||||||
//! [`super::image`], geteilt mit dem PNG-Pfad).
|
|
||||||
//!
|
|
||||||
//! Neutral: kennt weder GPU noch UI noch Fonts. Liefert *immer* RGBA8,
|
|
||||||
//! Ursprung oben-links, vollständig konvertiert — der Aufrufer macht keine
|
|
||||||
//! Nachbearbeitung, nur das Sampling (und damit die UV-Konvention) gehört ihm.
|
|
||||||
//! Zuständig für UI- und Font-Assets; Welt-Texturen kommen als PNG aus der
|
|
||||||
//! `.glb` (siehe [`super::gltf`]).
|
|
||||||
//!
|
|
||||||
//! Unterstützt True-Color unkomprimiert (Typ 2) und RLE (Typ 10) mit 24
|
|
||||||
//! oder 32 bpp, sowie Graustufen (Typ 3/11, 8 bpp → zu RGBA expandiert) für
|
|
||||||
//! Font-Maps. Paletten-TGAs gibt es nicht mehr (wds ist 15-bit True-Color).
|
|
||||||
|
|
||||||
use crate::engine::image::Image;
|
|
||||||
|
|
||||||
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> {
|
|
||||||
if d.len() < 18 { return Err("Header zu kurz".into()); }
|
|
||||||
let id_len = d[0] as usize;
|
|
||||||
let cmap_type = d[1];
|
|
||||||
let image_type = d[2];
|
|
||||||
let width = u16::from_le_bytes([d[12], d[13]]) as u32;
|
|
||||||
let height = u16::from_le_bytes([d[14], d[15]]) as u32;
|
|
||||||
let depth = d[16];
|
|
||||||
let descriptor = d[17];
|
|
||||||
|
|
||||||
if cmap_type != 0 {
|
|
||||||
return Err("Color-Map-TGAs nicht unterstützt (True-Color only)".into());
|
|
||||||
}
|
|
||||||
// Pixeldaten beginnen nach Header + ID-Feld (Color-Map ist leer).
|
|
||||||
let off = 18 + id_len;
|
|
||||||
|
|
||||||
let channels = match (image_type, depth) {
|
|
||||||
(2 | 10, 24) => 3,
|
|
||||||
(2 | 10, 32) => 4,
|
|
||||||
(3 | 11, 8) => 1,
|
|
||||||
(t, b) => return Err(format!("nicht unterstützt: Typ {t}, {b} bpp")),
|
|
||||||
};
|
|
||||||
let n = (width as usize) * (height as usize);
|
|
||||||
if n == 0 { return Err("Nullgröße".into()); }
|
|
||||||
|
|
||||||
// Rohpixel (channels Bytes je Pixel), bei RLE entpackt.
|
|
||||||
let raw_len = n * channels;
|
|
||||||
let raw: Vec<u8> = match image_type {
|
|
||||||
2 | 3 => d.get(off..off + raw_len).ok_or("Pixeldaten zu kurz")?.to_vec(),
|
|
||||||
10 | 11 => decode_rle(d.get(off..).ok_or("RLE-Daten fehlen")?, raw_len, channels)?,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// → RGBA8. True-Color liegt im TGA als BGR(A) vor; Graustufen replizieren.
|
|
||||||
let mut rgba = vec![0u8; n * 4];
|
|
||||||
for i in 0..n {
|
|
||||||
let (r, g, b, a) = match channels {
|
|
||||||
1 => { let v = raw[i]; (v, v, v, 255) }
|
|
||||||
3 => (raw[i * 3 + 2], raw[i * 3 + 1], raw[i * 3], 255),
|
|
||||||
4 => (raw[i * 4 + 2], raw[i * 4 + 1], raw[i * 4], raw[i * 4 + 3]),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
let o = i * 4;
|
|
||||||
rgba[o] = r; rgba[o + 1] = g; rgba[o + 2] = b; rgba[o + 3] = a;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ursprung oben-links erzwingen. Descriptor-Bit 5 gesetzt = top-origin.
|
|
||||||
if (descriptor >> 5) & 1 == 0 {
|
|
||||||
flip_vertical(&mut rgba, width as usize, height as usize);
|
|
||||||
}
|
|
||||||
Ok(Image { width, height, rgba })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TGA-RLE: Pakete operieren auf ganzen Pixeln (`ch` Bytes).
|
|
||||||
fn decode_rle(src: &[u8], raw_len: usize, ch: usize) -> Result<Vec<u8>, String> {
|
|
||||||
let mut out = Vec::with_capacity(raw_len);
|
|
||||||
let mut si = 0;
|
|
||||||
while out.len() < raw_len {
|
|
||||||
let header = *src.get(si).ok_or("RLE: Quelle zu kurz")?;
|
|
||||||
si += 1;
|
|
||||||
let count = (header & 0x7f) as usize + 1;
|
|
||||||
if header & 0x80 != 0 {
|
|
||||||
// RLE-Paket: ein Pixel count-mal.
|
|
||||||
let px = src.get(si..si + ch).ok_or("RLE: Pixel zu kurz")?;
|
|
||||||
si += ch;
|
|
||||||
for _ in 0..count { out.extend_from_slice(px); }
|
|
||||||
} else {
|
|
||||||
// Raw-Paket: count Pixel am Stück.
|
|
||||||
let bytes = count * ch;
|
|
||||||
let chunk = src.get(si..si + bytes).ok_or("RLE: Chunk zu kurz")?;
|
|
||||||
si += bytes;
|
|
||||||
out.extend_from_slice(chunk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.truncate(raw_len);
|
|
||||||
Ok(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flip_vertical(rgba: &mut [u8], w: usize, h: usize) {
|
|
||||||
let row = w * 4;
|
|
||||||
for y in 0..h / 2 {
|
|
||||||
let (a, b) = (y * row, (h - 1 - y) * row);
|
|
||||||
for x in 0..row { rgba.swap(a + x, b + x); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bgr_to_rgba_uncompressed() {
|
|
||||||
// Minimal-TGA: Typ 2, 2×1, 24 bpp, bottom-origin (descriptor 0).
|
|
||||||
// Zwei Pixel BGR (10,20,30) und (40,50,60).
|
|
||||||
let mut d = vec![0u8; 18];
|
|
||||||
d[2] = 2; d[12] = 2; d[14] = 1; d[16] = 24;
|
|
||||||
d.extend_from_slice(&[10, 20, 30, 40, 50, 60]);
|
|
||||||
let img = decode(&d).unwrap();
|
|
||||||
assert_eq!((img.width, img.height), (2, 1));
|
|
||||||
assert_eq!(&img.rgba[0..4], &[30, 20, 10, 255]);
|
|
||||||
assert_eq!(&img.rgba[4..8], &[60, 50, 40, 255]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn rle_truecolor_expands() {
|
|
||||||
// Typ 10, 4×1, 24 bpp: ein RLE-Paket (4× BGR 1,2,3).
|
|
||||||
let mut d = vec![0u8; 18];
|
|
||||||
d[2] = 10; d[12] = 4; d[14] = 1; d[16] = 24;
|
|
||||||
d.push(0x80 | 3); // RLE, count = 4
|
|
||||||
d.extend_from_slice(&[1, 2, 3]); // ein Pixel
|
|
||||||
let img = decode(&d).unwrap();
|
|
||||||
assert_eq!(img.rgba.len(), 4 * 4);
|
|
||||||
assert!(img.rgba.chunks(4).all(|p| p == [3, 2, 1, 255]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn decodes_real_placeholder() {
|
|
||||||
let bytes = std::fs::read("assets/textures/placeholder.tga").unwrap();
|
|
||||||
let img = decode(&bytes).unwrap();
|
|
||||||
assert_eq!((img.width, img.height), (256, 256));
|
|
||||||
assert_eq!(img.rgba.len(), 256 * 256 * 4);
|
|
||||||
assert!(img.rgba.chunks(4).all(|p| p[3] == 255)); // 24bpp → Alpha 255
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,11 +47,11 @@ pub(crate) struct Fonts {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Font {
|
impl Font {
|
||||||
/// EGA 8×14 (`assets/textures/fonts/oldschool-ega-8x14.tga`, 256×112).
|
/// EGA 8×14 (`assets/textures/fonts/oldschool-ega-8x14.png`, 256×112).
|
||||||
pub(crate) fn ega(tex: usize, atlas: &Image) -> Font {
|
pub(crate) fn ega(tex: usize, atlas: &Image) -> Font {
|
||||||
Font::measure(8.0, 14.0, 256.0, 112.0, tex, atlas)
|
Font::measure(8.0, 14.0, 256.0, 112.0, tex, atlas)
|
||||||
}
|
}
|
||||||
/// CGA 8×8 (`assets/textures/fonts/oldschool-cga-8x8.tga`, 256×64).
|
/// CGA 8×8 (`assets/textures/fonts/oldschool-cga-8x8.png`, 256×64).
|
||||||
pub(crate) fn cga(tex: usize, atlas: &Image) -> Font {
|
pub(crate) fn cga(tex: usize, atlas: &Image) -> Font {
|
||||||
Font::measure(8.0, 8.0, 256.0, 64.0, tex, atlas)
|
Font::measure(8.0, 8.0, 256.0, 64.0, tex, atlas)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
|
|||||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||||
use winit::window::{CursorGrabMode, Window, WindowId};
|
use winit::window::{CursorGrabMode, Window, WindowId};
|
||||||
|
|
||||||
use crate::engine::image::Image;
|
use crate::engine::image::{self, Image};
|
||||||
use crate::engine::player;
|
use crate::engine::player;
|
||||||
use crate::engine::{assets, gltf, model, tga, wav};
|
use crate::engine::{assets, gltf, model, wav};
|
||||||
use crate::session::{FrameInput, Mode, Session};
|
use crate::session::{FrameInput, Mode, Session};
|
||||||
use gpu::Gpu;
|
use gpu::Gpu;
|
||||||
use math::Mat4;
|
use math::Mat4;
|
||||||
@@ -106,12 +106,12 @@ pub fn run(mut session: Session, maps_dir: &str) {
|
|||||||
// Autorinnen-Content sind sie Projekt-Assets: fehlen sie, ist die
|
// Autorinnen-Content sind sie Projekt-Assets: fehlen sie, ist die
|
||||||
// Installation kaputt und es darf scheitern.
|
// Installation kaputt und es darf scheitern.
|
||||||
let load_keyed = |p: &str| {
|
let load_keyed = |p: &str| {
|
||||||
ui::key_luminance(&tga::load(&assets::path(p)).expect("UI-Textur (Projekt-Asset)"))
|
ui::key_luminance(&image::load(&assets::path(p)).expect("UI-Textur (Projekt-Asset)"))
|
||||||
};
|
};
|
||||||
// Font-Atlanten zuerst keyen, daraus die Glyph-Breiten messen (Font),
|
// Font-Atlanten zuerst keyen, daraus die Glyph-Breiten messen (Font),
|
||||||
// dann dieselben Bilder in die GPU-Texturliste übernehmen.
|
// dann dieselben Bilder in die GPU-Texturliste übernehmen.
|
||||||
let ega_img = load_keyed("assets/textures/fonts/oldschool-ega-8x14.tga");
|
let ega_img = load_keyed("assets/textures/fonts/oldschool-ega-8x14.png");
|
||||||
let cga_img = load_keyed("assets/textures/fonts/oldschool-cga-8x8.tga");
|
let cga_img = load_keyed("assets/textures/fonts/oldschool-cga-8x8.png");
|
||||||
let fonts = font::Fonts {
|
let fonts = font::Fonts {
|
||||||
ega: font::Font::ega(ui::FONT_EGA, &ega_img),
|
ega: font::Font::ega(ui::FONT_EGA, &ega_img),
|
||||||
cga: font::Font::cga(ui::FONT_CGA, &cga_img),
|
cga: font::Font::cga(ui::FONT_CGA, &cga_img),
|
||||||
@@ -120,8 +120,8 @@ pub fn run(mut session: Session, maps_dir: &str) {
|
|||||||
ui::white_pixel(),
|
ui::white_pixel(),
|
||||||
ega_img,
|
ega_img,
|
||||||
cga_img,
|
cga_img,
|
||||||
load_keyed("assets/textures/ui/cursors.tga"),
|
load_keyed("assets/textures/ui/cursors.png"),
|
||||||
load_keyed("assets/textures/ui/panel_ornaments.tga"),
|
load_keyed("assets/textures/ui/panel_ornaments.png"),
|
||||||
];
|
];
|
||||||
|
|
||||||
// stdin auf einem eigenen Thread: er darf blockieren, der Main-Thread
|
// stdin auf einem eigenen Thread: er darf blockieren, der Main-Thread
|
||||||
@@ -203,7 +203,7 @@ fn load_sound(name: &str) -> Option<audio::Sound> {
|
|||||||
/// Projekt-Asset: fehlt sie, ist die Installation kaputt und es darf scheitern.
|
/// Projekt-Asset: fehlt sie, ist die Installation kaputt und es darf scheitern.
|
||||||
fn placeholder() -> Image {
|
fn placeholder() -> Image {
|
||||||
let name = props::FALLBACK_TEXTURE;
|
let name = props::FALLBACK_TEXTURE;
|
||||||
tga::load(&assets::path(&format!("assets/textures/{name}.tga")))
|
image::load(&assets::path(&format!("assets/textures/{name}.png")))
|
||||||
.expect("Platzhalter-Textur (Projekt-Asset)")
|
.expect("Platzhalter-Textur (Projekt-Asset)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::engine::model::{Model, Texture};
|
|||||||
use crate::render::scene::{Batch, Mesh, Vertex};
|
use crate::render::scene::{Batch, Mesh, Vertex};
|
||||||
|
|
||||||
/// Schlüssel für Texturen ohne eigenes Bild; der Aufrufer lädt die Pixel
|
/// Schlüssel für Texturen ohne eigenes Bild; der Aufrufer lädt die Pixel
|
||||||
/// dazu (`assets/textures/placeholder.tga`).
|
/// dazu (`assets/textures/placeholder.png`).
|
||||||
pub const FALLBACK_TEXTURE: &str = "placeholder";
|
pub const FALLBACK_TEXTURE: &str = "placeholder";
|
||||||
|
|
||||||
/// Distinkte Textur-Schlüssel des Modells (sortiert), bildlose bereits auf
|
/// Distinkte Textur-Schlüssel des Modells (sortiert), bildlose bereits auf
|
||||||
|
|||||||