From 242e8870c3fc9e67e0dd5302bcb998aaa733f6fb Mon Sep 17 00:00:00 2001 From: Hakan Date: Thu, 14 May 2026 14:49:59 +0300 Subject: [PATCH] perf(scene): greek top-level Text/MText below 4px height When a Text/MText entity's anno-scaled glyph height projects to fewer than 4 screen pixels, drop the per-glyph stroke tessellation and emit a single filled rectangle in the text's color instead. The user still sees "text lives here" at the original color and footprint, but the expensive font-stroke generation is skipped for thousands of unreadable labels at fit-all / zoom-out views. Pre-boosts the fill color to compensate for the 0.45 ambient-occlusion dim the face3d pipeline applies (it's meant for 3D-shaded mesh solids, not 2D text greeking). Note: applies only to top-level entities; Text/MText *inside* block defs is already tessellated into the block cache at build time, so greeking those would require either a cache rebuild on zoom or a parallel "low-detail" cache. Left for later if real-world test suggests it's needed. Co-Authored-By: Claude Opus 4.7 --- src/scene/mod.rs | 68 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/scene/mod.rs b/src/scene/mod.rs index d9069126f..89404dbbe 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -3901,7 +3901,8 @@ fn tessellate_entity( if let Some(wpp) = world_per_pixel { let w = (ab[2] - ab[0]).abs(); let h = (ab[3] - ab[1]).abs(); - if w.max(h) / wpp < 0.5 { + // Keep in sync with `block_cache::MIN_PIXEL_SIZE`. + if w.max(h) / wpp < 5.0 { return vec![]; } } @@ -4102,6 +4103,71 @@ fn tessellate_entity( } let aabb = entity_aabb(e, world_offset); + + // Text greeking: when a Text/MText entity's projected glyph height + // drops below a readable size, replace the per-glyph stroke geometry + // with a single filled rectangle in the text's color. Same visual + // hint ("text lives here") at a fraction of the tessellation cost. + if let Some(wpp) = world_per_pixel { + let text_height: Option = match e { + EntityType::Text(t) => Some(t.height * anno_scale as f64), + EntityType::MText(m) => Some(m.height * anno_scale as f64), + _ => None, + }; + if let Some(h_world) = text_height { + let h_px = (h_world as f32) / wpp; + // < 4 px: glyphs aren't readable; emit a greeked rect instead. + // We still drew through the LOD floor (entity AABB > 2 px), so + // this rect is bigger than 2 px in at least one dimension. + if h_px < 4.0 && aabb != WireModel::UNBOUNDED_AABB { + let [x0, y0, x1, y1] = aabb; + let z = match e { + EntityType::Text(t) => (t.insertion_point.z - world_offset[2]) as f32, + EntityType::MText(m) => (m.insertion_point.z - world_offset[2]) as f32, + _ => 0.0, + }; + // Two CCW triangles spanning the AABB. + let fill_tris = vec![ + [x0, y0, z], + [x1, y0, z], + [x1, y1, z], + [x0, y0, z], + [x1, y1, z], + [x0, y1, z], + ]; + // The face3d pipeline dims fill_tris colors to 45% to fake + // ambient occlusion on PolyfaceMesh / 3DFACE solids. Greeked + // text doesn't want that shading — pre-boost the color so it + // emerges close to the original text color after the dim. + let boost = 1.0 / 0.45; + let [r, g, b, a] = entity_color; + let greek_color = [ + (r * boost).min(1.0), + (g * boost).min(1.0), + (b * boost).min(1.0), + a, + ]; + return vec![WireModel { + name: h.value().to_string(), + points: vec![], + color: greek_color, + selected: sel, + aci, + pattern_length: 0.0, + pattern: [0.0; 8], + line_weight_px: 1.0, + snap_pts: vec![], + tangent_geoms: vec![], + key_vertices: vec![], + aabb, + plinegen: true, + vp_scissor: None, + fill_tris, + }]; + } + } + } + let mut base = tessellate::tessellate( document, h,