forked from 1b-agent/opencadstudio-upstream
fix(text): show text in command previews (move/copy/etc) (#316)
Command previews (MOVE, COPY, ROTATE, SCALE, STRETCH, MIRROR, ARRAY, PASTE, INSERT) build ghost WireModels via WireModel::translated/rotated/ scaled/stretched/mirrored, but those helpers moved only `points`, never `text_verts`, and the ghosts' glyphs were never uploaded — so text vanished (or froze at its source) during the preview, same root cause as the grip-drag case. Transform text_verts alongside points in all five helpers (shared map_text_verts, reconstructing + re-splitting the double-single world position), and gather preview_wires' glyphs into the per-frame preview-text buffer in the render path. This covers every command preview automatically. The grip-drag else branch no longer sets preview_text explicitly (the render gather handles it); the grip-slide fast path keeps its own scene.preview_text channel (empty preview_wires, so no double-draw). MIRROR honours MIRRTEXT: on -> true glyph mirror via mirrored(); off -> readable relocation via translated(). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
67fe363775
commit
f5870b011d
4 files changed
+95
-14
No files matched your search
@@ -812,12 +812,11 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||
self.tabs[i].scene.set_preview_text(slid);
|
||||
self.tabs[i].scene.set_preview_wires(Vec::new());
|
||||
} else {
|
||||
// Wire geometry, or a reshape grip: re-tessellate. The
|
||||
// preview WireModels carry the entity's glyphs (gathered
|
||||
// for the preview-text buffer in the render path), so a
|
||||
// dimension / MTEXT-width drag keeps its text visible too.
|
||||
let preview = self.tabs[i].scene.wire_models_for(&[grip.handle]);
|
||||
let ptext = preview
|
||||
.iter()
|
||||
.flat_map(|w| w.text_verts.iter().copied())
|
||||
.collect();
|
||||
self.tabs[i].scene.set_preview_text(ptext);
|
||||
self.tabs[i].scene.set_preview_wires(preview);
|
||||
}
|
||||
self.refresh_selected_grips();
|
||||
|
||||
@@ -168,6 +168,11 @@ impl WireModel {
|
||||
p[1] += delta.y;
|
||||
p[2] += delta.z;
|
||||
}
|
||||
if !out.text_verts.is_empty() {
|
||||
let (dx, dy, dz) = (delta.x as f64, delta.y as f64, delta.z as f64);
|
||||
out.text_verts =
|
||||
map_text_verts(&self.text_verts, |x, y, z| (x + dx, y + dy, z + dz));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -184,6 +189,14 @@ impl WireModel {
|
||||
p[0] = center.x + dx * c - dy * s;
|
||||
p[1] = center.y + dx * s + dy * c;
|
||||
}
|
||||
if !out.text_verts.is_empty() {
|
||||
let (cx, cy) = (center.x as f64, center.y as f64);
|
||||
let (s, c) = (s as f64, c as f64);
|
||||
out.text_verts = map_text_verts(&self.text_verts, |x, y, z| {
|
||||
let (dx, dy) = (x - cx, y - cy);
|
||||
(cx + dx * c - dy * s, cy + dx * s + dy * c, z)
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -198,6 +211,13 @@ impl WireModel {
|
||||
p[1] = center.y + (p[1] - center.y) * factor;
|
||||
p[2] = center.z + (p[2] - center.z) * factor;
|
||||
}
|
||||
if !out.text_verts.is_empty() {
|
||||
let (cx, cy, cz) = (center.x as f64, center.y as f64, center.z as f64);
|
||||
let f = factor as f64;
|
||||
out.text_verts = map_text_verts(&self.text_verts, |x, y, z| {
|
||||
(cx + (x - cx) * f, cy + (y - cy) * f, cz + (z - cz) * f)
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -218,6 +238,18 @@ impl WireModel {
|
||||
p[2] += delta.z;
|
||||
}
|
||||
}
|
||||
if !out.text_verts.is_empty() {
|
||||
let (mnx, mny) = (win_min.x as f64, win_min.y as f64);
|
||||
let (mxx, mxy) = (win_max.x as f64, win_max.y as f64);
|
||||
let (dx, dy, dz) = (delta.x as f64, delta.y as f64, delta.z as f64);
|
||||
out.text_verts = map_text_verts(&self.text_verts, |x, y, z| {
|
||||
if x >= mnx && x <= mxx && y >= mny && y <= mxy {
|
||||
(x + dx, y + dy, z + dz)
|
||||
} else {
|
||||
(x, y, z)
|
||||
}
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -248,6 +280,18 @@ impl WireModel {
|
||||
p[0] = 2.0 * t * ax - p[0];
|
||||
p[1] = 2.0 * t * ay - p[1];
|
||||
}
|
||||
// Glyph quads reflect wholesale (true mirror) — the caller only routes
|
||||
// text through here for MIRRTEXT-on; MIRRTEXT-off relocates via
|
||||
// `translated` so glyphs stay readable.
|
||||
if !out.text_verts.is_empty() {
|
||||
let (ax, ay, len2) = (ax as f64, ay as f64, len2 as f64);
|
||||
let (p1x, p1y) = (p1.x as f64, p1.y as f64);
|
||||
out.text_verts = map_text_verts(&self.text_verts, |x, y, z| {
|
||||
let (dx, dy) = (x - p1x, y - p1y);
|
||||
let t = (dx * ax + dy * ay) / len2;
|
||||
(p1x + 2.0 * t * ax - dx, p1y + 2.0 * t * ay - dy, z)
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -266,6 +310,36 @@ impl WireModel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Map every glyph vertex's double-single world position through `f`, re-
|
||||
/// splitting the result. The preview transforms above move `points`, but SDF
|
||||
/// glyph quads live in `text_verts` (absolute-world double-single) — so a text
|
||||
/// ghost (MOVE / COPY / ROTATE / SCALE / STRETCH / MIRROR preview) must carry
|
||||
/// these along or the dragged text renders frozen at its source (issue #316).
|
||||
fn map_text_verts(
|
||||
verts: &[crate::scene::pipeline::text_gpu::TextVertex],
|
||||
f: impl Fn(f64, f64, f64) -> (f64, f64, f64),
|
||||
) -> Vec<crate::scene::pipeline::text_gpu::TextVertex> {
|
||||
use crate::scene::pipeline::text_gpu::split_ds;
|
||||
verts
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let (nx, ny, nz) = f(
|
||||
v.pos[0] as f64 + v.pos_low[0] as f64,
|
||||
v.pos[1] as f64 + v.pos_low[1] as f64,
|
||||
v.pos[2] as f64 + v.pos_low[2] as f64,
|
||||
);
|
||||
let (xh, xl) = split_ds(nx);
|
||||
let (yh, yl) = split_ds(ny);
|
||||
let (zh, zl) = split_ds(nz);
|
||||
crate::scene::pipeline::text_gpu::TextVertex {
|
||||
pos: [xh, yh, zh],
|
||||
pos_low: [xl, yl, zl],
|
||||
..*v
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
impl Default for WireModel {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
||||
@@ -70,7 +70,7 @@ impl TextVertex {
|
||||
|
||||
/// Split an f64 into the double-single (high f32, low residual f32) pair the
|
||||
/// shaders reconstruct relative-to-eye.
|
||||
fn split_ds(v: f64) -> (f32, f32) {
|
||||
pub(crate) fn split_ds(v: f64) -> (f32, f32) {
|
||||
let high = v as f32;
|
||||
(high, (v - high as f64) as f32)
|
||||
}
|
||||
|
||||
@@ -1126,14 +1126,22 @@ impl Scene {
|
||||
// draws only the text that belongs to it. Cached on the wire content
|
||||
// id so an unchanged wire set is not re-walked every frame.
|
||||
let text_verts = self.gather_text_verts(&all_wires, wire_content_id);
|
||||
// Grip-drag / command-preview glyphs live on `self.preview_text` (set by
|
||||
// the edit handlers), excluded from the epoch-cached base gather above.
|
||||
// Clone the (tiny — one dragged entity) set per frame so dragged text
|
||||
// stays on screen without re-uploading the resident base buffer (#316).
|
||||
let preview_text_verts = if self.preview_text.is_empty() {
|
||||
Arc::new(Vec::new())
|
||||
} else {
|
||||
Arc::new(self.preview_text.clone())
|
||||
// Grip-drag / command-preview glyphs, excluded from the epoch-cached base
|
||||
// gather above. Two sources, both tiny (one operation's worth) and walked
|
||||
// per frame: the overlay wires' own glyphs (MOVE / COPY / ROTATE / SCALE /
|
||||
// STRETCH / MIRROR ghosts carry text_verts) and `self.preview_text` (the
|
||||
// grip-slide fast path, which emits bare glyphs with empty preview_wires).
|
||||
// The two never overlap — a slide leaves preview_wires empty — so a plain
|
||||
// concat is correct, no double-draw (issue #316).
|
||||
let preview_text_verts = {
|
||||
let mut pv: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||
for w in preview_wires.iter() {
|
||||
if !w.text_verts.is_empty() {
|
||||
pv.extend_from_slice(&w.text_verts);
|
||||
}
|
||||
}
|
||||
pv.extend_from_slice(&self.preview_text);
|
||||
Arc::new(pv)
|
||||
};
|
||||
Some(ViewportData {
|
||||
wires: all_wires,
|
||||
|
||||
Reference in new issue
Block a user