diff --git a/src/scene/image_model.rs b/src/scene/image_model.rs index ea36e2b0c..8755c242c 100644 --- a/src/scene/image_model.rs +++ b/src/scene/image_model.rs @@ -36,12 +36,17 @@ pub struct ImageModel { impl ImageModel { /// Build an ImageModel from a DXF RasterImage entity. /// Returns `None` if the image file cannot be opened or decoded. - pub fn from_raster_image(img: &acadrust::entities::RasterImage) -> Option { + pub fn from_raster_image( + img: &acadrust::entities::RasterImage, + world_offset: [f64; 3], + ) -> Option { let w = img.size.x; let h = img.size.y; - let ox = img.insertion_point.x as f32; - let oy = img.insertion_point.y as f32; - let oz = img.insertion_point.z as f32; + // Model-space geometry is drawn in (WCS - world_offset) so large UTM- + // scale coordinates stay within f32 precision; offset the image too. + let ox = (img.insertion_point.x - world_offset[0]) as f32; + let oy = (img.insertion_point.y - world_offset[1]) as f32; + let oz = (img.insertion_point.z - world_offset[2]) as f32; let ux = (img.u_vector.x * w) as f32; let uy = (img.u_vector.y * w) as f32; let uz = (img.u_vector.z * w) as f32; @@ -74,6 +79,19 @@ impl ImageModel { /// Returns `None` if the file does not exist or cannot be decoded. pub fn load_pixels(path_str: &str) -> Option<(Vec, u32, u32)> { let img = image::open(Path::new(path_str)).ok()?; + // GPUs cap 2-D texture dimensions (8192 with wgpu's default limits). + // Downscale oversized images to fit, preserving aspect ratio, so texture + // creation can't fail — they're displayed scaled-down anyway. + const MAX_DIM: u32 = 8192; + let img = if img.width() > MAX_DIM || img.height() > MAX_DIM { + let longest = img.width().max(img.height()) as f32; + let scale = MAX_DIM as f32 / longest; + let nw = ((img.width() as f32 * scale) as u32).clamp(1, MAX_DIM); + let nh = ((img.height() as f32 * scale) as u32).clamp(1, MAX_DIM); + img.resize(nw, nh, image::imageops::FilterType::Triangle) + } else { + img + }; let rgba = img.to_rgba8(); let (w, h) = rgba.dimensions(); Some((rgba.into_raw(), w, h)) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 15c1e781e..c40ee4095 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -229,7 +229,7 @@ pub fn build_derived_caches(doc: &CadDocument) -> DerivedCaches { .par_iter() .filter_map(|&handle| { if let EntityType::RasterImage(img) = doc.get_entity(handle)? { - ImageModel::from_raster_image(img).map(|m| (handle, m)) + ImageModel::from_raster_image(img, world_offset).map(|m| (handle, m)) } else { None } @@ -3441,7 +3441,7 @@ impl Scene { None }; let image_seed = if let EntityType::RasterImage(img) = &entity { - ImageModel::from_raster_image(img) + ImageModel::from_raster_image(img, self.world_offset) } else { None }; @@ -4252,7 +4252,7 @@ impl Scene { }) .collect(); for (handle, img) in entries { - if let Some(model) = ImageModel::from_raster_image(&img) { + if let Some(model) = ImageModel::from_raster_image(&img, self.world_offset) { self.images.insert(handle, model); } } diff --git a/src/shaders/image.wgsl b/src/shaders/image.wgsl index e41e7d0c5..a27769fda 100644 --- a/src/shaders/image.wgsl +++ b/src/shaders/image.wgsl @@ -2,13 +2,16 @@ // Renders a four-vertex quad (two triangles) with a sampled texture. // ── Bind group 0: shared projection uniforms ───────────────────────────────── +// Must match the shared `Uniforms` struct (scene::pipeline::uniforms, 112 B). struct Uniforms { - mvp: mat4x4, - view: mat4x4, - width: f32, - height: f32, - cam_z: f32, - _pad: f32, + view_proj: mat4x4, + camera_pos: vec4, + viewport_size: vec2, + world_per_pixel: f32, + lwdisplay_enable: f32, + flat_shade: f32, + transparency_enable: f32, + _pad: vec2, }; @group(0) @binding(0) var u: Uniforms; @@ -43,7 +46,7 @@ struct VertOut { @vertex fn vs_main(in: VertIn) -> VertOut { var out: VertOut; - out.clip_pos = u.mvp * vec4(in.pos, 1.0); + out.clip_pos = u.view_proj * vec4(in.pos, 1.0); out.clip_pos.z = out.clip_pos.z - img_params.draw_depth * DRAW_ORDER_BIAS * out.clip_pos.w; out.uv = in.uv; return out;