fix(text): grip-drag no longer hides text (#316)
Dragging text by a grip hid it until release. The dragged entity is hidden from the base wire set and shown as a preview overlay, but its SDF glyph quads (text_verts) were never uploaded for the preview, so the text vanished mid-drag. Add a per-frame preview_text GPU buffer (sibling to preview_wires). A rigid whole-entity move of pure text (TEXT/MTEXT, Square insertion grip) slides the drag-start glyphs by the drag delta instead of re-shaping the run every move; reshape grips (MTEXT width), dimensions and wire entities re-tessellate and feed the same buffer. Also fold preview_text into render_signature: a pure-text slide leaves preview_wires empty, so without it the scene-render cache froze the dragged text at its first frame (grip moved but text stayed, then jumped to place on release). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
88450f0cd1
commit
67fe363775
9 changed files with 176 additions and 5 deletions
|
|
@ -377,6 +377,15 @@ pub(super) struct OpenCADStudio {
|
||||||
/// the document live (so grips / properties track), so cancel reverts from
|
/// the document live (so grips / properties track), so cancel reverts from
|
||||||
/// this backup. Dropped (kept) on a normal commit.
|
/// this backup. Dropped (kept) on a normal commit.
|
||||||
grip_original: Option<acadrust::EntityType>,
|
grip_original: Option<acadrust::EntityType>,
|
||||||
|
/// Drag-start snapshot of the dragged entity's SDF glyph quads. A whole-
|
||||||
|
/// entity text move slides these each frame (translating the already-shaped
|
||||||
|
/// glyphs) instead of re-tessellating the run every cursor move (issue #316).
|
||||||
|
grip_text_verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||||
|
/// True when the current grip drag is a rigid whole-entity move of pure text
|
||||||
|
/// (TEXT / MTEXT, no wire geometry, insertion grip) — the case where the
|
||||||
|
/// glyphs can just be slid. A reshape grip (e.g. an MTEXT width handle) or an
|
||||||
|
/// entity with wires re-tessellates instead so the preview stays exact.
|
||||||
|
grip_text_slide: bool,
|
||||||
/// Open Quick Select panel state. `None` = panel closed. Filters are
|
/// Open Quick Select panel state. `None` = panel closed. Filters are
|
||||||
/// applied via `Message::QSelectApply`; the panel is dismissed on
|
/// applied via `Message::QSelectApply`; the panel is dismissed on
|
||||||
/// Apply / Cancel / Esc / outside-click.
|
/// Apply / Cancel / Esc / outside-click.
|
||||||
|
|
@ -2101,6 +2110,8 @@ impl OpenCADStudio {
|
||||||
grip_preview_handle: None,
|
grip_preview_handle: None,
|
||||||
hover_dwell: None,
|
hover_dwell: None,
|
||||||
grip_original: None,
|
grip_original: None,
|
||||||
|
grip_text_verts: Vec::new(),
|
||||||
|
grip_text_slide: false,
|
||||||
qselect: None,
|
qselect: None,
|
||||||
show_ucs_icon: true,
|
show_ucs_icon: true,
|
||||||
ucs_icon_at_origin: true,
|
ucs_icon_at_origin: true,
|
||||||
|
|
|
||||||
|
|
@ -493,6 +493,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
// once, and drop the preview.
|
// once, and drop the preview.
|
||||||
if let Some(h) = self.grip_preview_handle.take() {
|
if let Some(h) = self.grip_preview_handle.take() {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
|
self.grip_text_verts = Vec::new();
|
||||||
|
self.grip_text_slide = false;
|
||||||
if let Some(orig) = self.grip_original.take() {
|
if let Some(orig) = self.grip_original.take() {
|
||||||
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
|
if let Some(e) = self.tabs[i].scene.document.get_entity_mut(h) {
|
||||||
*e = orig;
|
*e = orig;
|
||||||
|
|
|
||||||
|
|
@ -721,6 +721,25 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
// block cache so the hide doesn't re-tessellate blocks.
|
// block cache so the hide doesn't re-tessellate blocks.
|
||||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||||
self.grip_preview_handle = Some(grip.handle);
|
self.grip_preview_handle = Some(grip.handle);
|
||||||
|
// Snapshot the entity's glyph quads once so each move can
|
||||||
|
// slide the already-shaped text rather than re-shaping it
|
||||||
|
// (issue #316). The fast slide path only runs for a rigid
|
||||||
|
// whole-entity move of pure text: no wire geometry (so a
|
||||||
|
// dimension re-tessellates) and a Square insertion grip (so
|
||||||
|
// an MTEXT width handle, a Triangle, still re-tessellates so
|
||||||
|
// the re-wrap is exact).
|
||||||
|
let snap = self.tabs[i].scene.wire_models_for(&[grip.handle]);
|
||||||
|
self.grip_text_verts =
|
||||||
|
snap.iter().flat_map(|w| w.text_verts.iter().copied()).collect();
|
||||||
|
let square_grip = self.tabs[i]
|
||||||
|
.selected_grips
|
||||||
|
.iter()
|
||||||
|
.find(|g| g.id == grip.grip_id)
|
||||||
|
.map(|g| g.shape == crate::scene::model::object::GripShape::Square)
|
||||||
|
.unwrap_or(false);
|
||||||
|
self.grip_text_slide = !self.grip_text_verts.is_empty()
|
||||||
|
&& snap.iter().all(|w| w.points.is_empty())
|
||||||
|
&& square_grip;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The edited entity is hidden, so it's already absent from
|
// The edited entity is hidden, so it's already absent from
|
||||||
|
|
@ -778,10 +797,29 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
.apply_grip(grip.handle, grip.grip_id, apply);
|
.apply_grip(grip.handle, grip.grip_id, apply);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
self.tabs[i].active_grip.as_mut().unwrap().last_world = snapped;
|
||||||
// Overlay the moved entity (hidden from the base) — no base
|
// Overlay the moved entity (hidden from the base). Pure text
|
||||||
// re-tessellation, just a one-entity preview tessellation.
|
// moved as a whole slides its drag-start glyphs — no re-shaping
|
||||||
|
// and no wire re-tess. Anything else (wire geometry, or a point
|
||||||
|
// grip that reshapes text) is re-tessellated for an exact
|
||||||
|
// preview; either way the glyphs ride the preview-text buffer
|
||||||
|
// so dragged text never vanishes mid-drag (issue #316).
|
||||||
|
if self.grip_text_slide {
|
||||||
|
let d = snapped - grip.origin_world;
|
||||||
|
let slid = crate::scene::pipeline::text_gpu::translate_verts(
|
||||||
|
&self.grip_text_verts,
|
||||||
|
[d.x, d.y, d.z],
|
||||||
|
);
|
||||||
|
self.tabs[i].scene.set_preview_text(slid);
|
||||||
|
self.tabs[i].scene.set_preview_wires(Vec::new());
|
||||||
|
} else {
|
||||||
let preview = self.tabs[i].scene.wire_models_for(&[grip.handle]);
|
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.tabs[i].scene.set_preview_wires(preview);
|
||||||
|
}
|
||||||
self.refresh_selected_grips();
|
self.refresh_selected_grips();
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -1729,6 +1767,8 @@ pub(super) fn on_tick(&mut self, t: Instant) -> Task<Message> {
|
||||||
// re-tessellate the base once, dropping the overlay preview.
|
// re-tessellate the base once, dropping the overlay preview.
|
||||||
if let Some(h) = self.grip_preview_handle.take() {
|
if let Some(h) = self.grip_preview_handle.take() {
|
||||||
self.grip_original = None;
|
self.grip_original = None;
|
||||||
|
self.grip_text_verts = Vec::new();
|
||||||
|
self.grip_text_slide = false;
|
||||||
self.tabs[i].scene.hidden.remove(&h);
|
self.tabs[i].scene.hidden.remove(&h);
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
// Only the dragged entity changed — re-tessellate just it.
|
// Only the dragged entity changed — re-tessellate just it.
|
||||||
|
|
|
||||||
|
|
@ -1649,6 +1649,7 @@ impl Scene {
|
||||||
self.document = CadDocument::new();
|
self.document = CadDocument::new();
|
||||||
self.selected = HashSet::default();
|
self.selected = HashSet::default();
|
||||||
self.preview_wires = vec![];
|
self.preview_wires = vec![];
|
||||||
|
self.preview_text = vec![];
|
||||||
self.current_layout = "Model".to_string();
|
self.current_layout = "Model".to_string();
|
||||||
self.hatches = HashMap::default();
|
self.hatches = HashMap::default();
|
||||||
self.meshes = HashMap::default();
|
self.meshes = HashMap::default();
|
||||||
|
|
|
||||||
|
|
@ -752,6 +752,10 @@ pub struct Scene {
|
||||||
pub selection_filter: HashSet<String>,
|
pub selection_filter: HashSet<String>,
|
||||||
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
/// In-progress preview wires while a command is active (rubber-band + object ghosts).
|
||||||
pub preview_wires: Vec<WireModel>,
|
pub preview_wires: Vec<WireModel>,
|
||||||
|
/// In-progress preview SDF glyph quads (grip drag / command preview). Rides
|
||||||
|
/// a per-frame GPU buffer separate from the epoch-cached base text so the
|
||||||
|
/// dragged text stays visible while it's hidden from the base set (#316).
|
||||||
|
pub preview_text: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||||
/// Committed-segment wire drawn during multi-point commands (normal colour).
|
/// Committed-segment wire drawn during multi-point commands (normal colour).
|
||||||
pub interim_wire: Option<WireModel>,
|
pub interim_wire: Option<WireModel>,
|
||||||
pub camera_generation: u64,
|
pub camera_generation: u64,
|
||||||
|
|
@ -995,6 +999,7 @@ impl Scene {
|
||||||
transparency_display: true,
|
transparency_display: true,
|
||||||
selection_filter: HashSet::default(),
|
selection_filter: HashSet::default(),
|
||||||
preview_wires: vec![],
|
preview_wires: vec![],
|
||||||
|
preview_text: vec![],
|
||||||
interim_wire: None,
|
interim_wire: None,
|
||||||
camera_generation: 0,
|
camera_generation: 0,
|
||||||
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,14 @@ pub struct Pipeline {
|
||||||
/// analogue of the selected-wire xray overlay). Rebuilt on selection change.
|
/// analogue of the selected-wire xray overlay). Rebuilt on selection change.
|
||||||
text_highlight_vbuf: Option<wgpu::Buffer>,
|
text_highlight_vbuf: Option<wgpu::Buffer>,
|
||||||
text_highlight_vcount: u32,
|
text_highlight_vcount: u32,
|
||||||
|
/// Live grip-drag / command-preview SDF glyph quads. The base text buffer
|
||||||
|
/// only re-uploads on a geometry-epoch change, but a grip drag hides the
|
||||||
|
/// dragged entity from the base wire set and shows it as a per-frame
|
||||||
|
/// overlay — so its glyphs ride here, re-uploaded every frame like
|
||||||
|
/// `gpu_preview_wires`, or the dragged text vanishes until release re-tesses
|
||||||
|
/// it back into the base set (issue #316).
|
||||||
|
text_preview_vbuf: Option<wgpu::Buffer>,
|
||||||
|
text_preview_vcount: u32,
|
||||||
/// Last requested render size (the full viewport rect, in pixels). The
|
/// Last requested render size (the full viewport rect, in pixels). The
|
||||||
/// geometry passes render at this size; the blit UV is scaled by
|
/// geometry passes render at this size; the blit UV is scaled by
|
||||||
/// `depth_texture_size / alloc_size` so it samples only the filled region.
|
/// `depth_texture_size / alloc_size` so it samples only the filled region.
|
||||||
|
|
@ -1161,6 +1169,8 @@ impl Pipeline {
|
||||||
text_vcount: 0,
|
text_vcount: 0,
|
||||||
text_highlight_vbuf: None,
|
text_highlight_vbuf: None,
|
||||||
text_highlight_vcount: 0,
|
text_highlight_vcount: 0,
|
||||||
|
text_preview_vbuf: None,
|
||||||
|
text_preview_vcount: 0,
|
||||||
mesh_pipeline,
|
mesh_pipeline,
|
||||||
mesh_transparent_pipeline,
|
mesh_transparent_pipeline,
|
||||||
mesh_highlight_pipeline,
|
mesh_highlight_pipeline,
|
||||||
|
|
@ -1383,6 +1393,39 @@ impl Pipeline {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upload the live grip-drag / command-preview SDF glyph quads. Re-uploaded
|
||||||
|
/// every frame they're present, kept separate from the epoch-cached base
|
||||||
|
/// text buffer so a drag never re-uploads the (potentially huge) resident
|
||||||
|
/// text set — and so the dragged entity's glyphs (hidden from the base set
|
||||||
|
/// while the drag is live) still reach the GPU each frame (issue #316).
|
||||||
|
pub fn upload_preview_text(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
verts: &[text_gpu::TextVertex],
|
||||||
|
) {
|
||||||
|
if verts.is_empty() {
|
||||||
|
self.text_preview_vbuf = None;
|
||||||
|
self.text_preview_vcount = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Ensure the glyph atlas exists / is current even when the base text
|
||||||
|
// pass didn't run this frame (e.g. the dragged text is the only text).
|
||||||
|
if let Ok(mut atlas) = crate::scene::text::sdf_atlas::text_atlas().lock() {
|
||||||
|
if self.text_atlas_gpu.is_none() || atlas.is_dirty() {
|
||||||
|
self.text_atlas_gpu = Some(text_gpu::TextAtlasGpu::upload(
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
|
&atlas,
|
||||||
|
&self.text_atlas_bgl,
|
||||||
|
));
|
||||||
|
atlas.clear_dirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.text_preview_vbuf = text_gpu::upload_vertices(device, verts);
|
||||||
|
self.text_preview_vcount = verts.len() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
/// Recompute pixel scissor rects for viewport-clipped wires from the current view_proj.
|
/// Recompute pixel scissor rects for viewport-clipped wires from the current view_proj.
|
||||||
/// Called every frame from prepare() because scissor pixels shift with pan/zoom.
|
/// Called every frame from prepare() because scissor pixels shift with pan/zoom.
|
||||||
pub fn compute_wire_scissors(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) {
|
pub fn compute_wire_scissors(&mut self, view_rot: glam::Mat4, eye: glam::DVec3, clip_w: u32, clip_h: u32) {
|
||||||
|
|
@ -2143,7 +2186,9 @@ impl Pipeline {
|
||||||
if let Some(atlas) = &self.text_atlas_gpu {
|
if let Some(atlas) = &self.text_atlas_gpu {
|
||||||
let have_base = self.text_vbuf.is_some() && self.text_vcount > 0;
|
let have_base = self.text_vbuf.is_some() && self.text_vcount > 0;
|
||||||
let have_hl = self.text_highlight_vbuf.is_some() && self.text_highlight_vcount > 0;
|
let have_hl = self.text_highlight_vbuf.is_some() && self.text_highlight_vcount > 0;
|
||||||
if have_base || have_hl {
|
let have_preview =
|
||||||
|
self.text_preview_vbuf.is_some() && self.text_preview_vcount > 0;
|
||||||
|
if have_base || have_hl || have_preview {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("text.render_pass"),
|
label: Some("text.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
|
@ -2182,6 +2227,13 @@ impl Pipeline {
|
||||||
pass.draw(0..self.text_highlight_vcount, 0..1);
|
pass.draw(0..self.text_highlight_vcount, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Grip-drag / command-preview glyphs, drawn over the base text.
|
||||||
|
if let Some(pbuf) = &self.text_preview_vbuf {
|
||||||
|
if self.text_preview_vcount > 0 {
|
||||||
|
pass.set_vertex_buffer(0, pbuf.slice(..));
|
||||||
|
pass.draw(0..self.text_preview_vcount, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,29 @@ pub fn push_glyph_vertices(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Slide every glyph vertex by a world-space delta, re-splitting the
|
||||||
|
/// double-single position. Lets a grip drag move already-shaped text by
|
||||||
|
/// translating the drag-start glyphs each frame instead of re-tessellating
|
||||||
|
/// (re-shaping) the run on every cursor move (issue #316).
|
||||||
|
pub fn translate_verts(verts: &[TextVertex], delta: [f64; 3]) -> Vec<TextVertex> {
|
||||||
|
verts
|
||||||
|
.iter()
|
||||||
|
.map(|v| {
|
||||||
|
let wx = v.pos[0] as f64 + v.pos_low[0] as f64 + delta[0];
|
||||||
|
let wy = v.pos[1] as f64 + v.pos_low[1] as f64 + delta[1];
|
||||||
|
let wz = v.pos[2] as f64 + v.pos_low[2] as f64 + delta[2];
|
||||||
|
let (xh, xl) = split_ds(wx);
|
||||||
|
let (yh, yl) = split_ds(wy);
|
||||||
|
let (zh, zl) = split_ds(wz);
|
||||||
|
TextVertex {
|
||||||
|
pos: [xh, yh, zh],
|
||||||
|
pos_low: [xl, yl, zl],
|
||||||
|
..*v
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
// ── Atlas texture ───────────────────────────────────────────────────────────
|
// ── Atlas texture ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// The shared glyph atlas on the GPU: a single-channel (R8) SDF texture plus
|
/// The shared glyph atlas on the GPU: a single-channel (R8) SDF texture plus
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,18 @@ impl Scene {
|
||||||
self.preview_wires = wires;
|
self.preview_wires = wires;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_preview_text(&mut self, verts: Vec<crate::scene::pipeline::text_gpu::TextVertex>) {
|
||||||
|
// Overlay glyphs — same reasoning as `set_preview_wires`: no geometry
|
||||||
|
// bump. Uploaded to a dedicated per-frame text buffer in `prepare`.
|
||||||
|
self.preview_text = verts;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear_preview_wire(&mut self) {
|
pub fn clear_preview_wire(&mut self) {
|
||||||
// No geometry bump — see `set_preview_wires`. Dropping the overlay
|
// No geometry bump — see `set_preview_wires`. Dropping the overlay
|
||||||
// flips the wire content id back to the base tessellation id, which
|
// flips the wire content id back to the base tessellation id, which
|
||||||
// re-uploads the base wires (without the preview) on the next frame.
|
// re-uploads the base wires (without the preview) on the next frame.
|
||||||
self.preview_wires = vec![];
|
self.preview_wires = vec![];
|
||||||
|
self.preview_text = vec![];
|
||||||
self.interim_wire = None;
|
self.interim_wire = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,11 @@ pub struct ViewportData {
|
||||||
pub(in crate::scene) face3d_wires: Arc<Vec<WireModel>>,
|
pub(in crate::scene) face3d_wires: Arc<Vec<WireModel>>,
|
||||||
/// SDF text-quad vertices (Phase 2b). Empty unless `OCS_TEXT_SDF` is set.
|
/// SDF text-quad vertices (Phase 2b). Empty unless `OCS_TEXT_SDF` is set.
|
||||||
pub(in crate::scene) text_verts: Arc<Vec<crate::scene::pipeline::text_gpu::TextVertex>>,
|
pub(in crate::scene) text_verts: Arc<Vec<crate::scene::pipeline::text_gpu::TextVertex>>,
|
||||||
|
/// Live grip-drag / command-preview glyph quads. Kept out of the epoch-cached
|
||||||
|
/// `text_verts` and uploaded to a per-frame buffer, so text dragged by a grip
|
||||||
|
/// stays visible even though it's hidden from the base text set (issue #316).
|
||||||
|
pub(in crate::scene) preview_text_verts:
|
||||||
|
Arc<Vec<crate::scene::pipeline::text_gpu::TextVertex>>,
|
||||||
/// Per-entity normalized draw-order depth (handle.value() → (0,1)), used
|
/// Per-entity normalized draw-order depth (handle.value() → (0,1)), used
|
||||||
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
|
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
|
||||||
/// depth field (84 construction sites); the bias is looked up by handle
|
/// depth field (84 construction sites); the bias is looked up by handle
|
||||||
|
|
@ -380,6 +385,7 @@ impl shader::Primitive for Primitive {
|
||||||
// refreshed every frame it's present, so a drag never re-uploads
|
// refreshed every frame it's present, so a drag never re-uploads
|
||||||
// the resident base wire buffer.
|
// the resident base wire buffer.
|
||||||
inner.upload_preview_wires(device, &vp.preview_wires[..], &vp.draw_depths);
|
inner.upload_preview_wires(device, &vp.preview_wires[..], &vp.draw_depths);
|
||||||
|
inner.upload_preview_text(device, queue, &vp.preview_text_verts[..]);
|
||||||
// Cull / scissor / LOD project AABBs relative-to-eye (matching the
|
// Cull / scissor / LOD project AABBs relative-to-eye (matching the
|
||||||
// GPU's RTE path) so the math stays precise at UTM-scale coords.
|
// GPU's RTE path) so the math stays precise at UTM-scale coords.
|
||||||
let view_rot = vp.uniforms.view_rot;
|
let view_rot = vp.uniforms.view_rot;
|
||||||
|
|
@ -524,6 +530,20 @@ fn render_signature(vp: &ViewportData, clip_w: u32, clip_h: u32) -> u64 {
|
||||||
p[2].to_bits().hash(&mut h);
|
p[2].to_bits().hash(&mut h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Grip-drag / command-preview glyph quads (issue #316). A pure-text slide
|
||||||
|
// leaves `preview_wires` empty and moves ONLY these, so a signature that
|
||||||
|
// ignored them would let the scene-render cache freeze the dragged text at
|
||||||
|
// its first frame (re-blitting a stale texture) until release. Small — one
|
||||||
|
// dragged entity — so hashing every vertex is cheap. Hash the high AND low
|
||||||
|
// halves of the double-single position: a sub-unit slide at UTM scale shifts
|
||||||
|
// only the low residual, so hashing the high f32 alone would miss it.
|
||||||
|
vp.preview_text_verts.len().hash(&mut h);
|
||||||
|
for v in vp.preview_text_verts.iter() {
|
||||||
|
v.pos[0].to_bits().hash(&mut h);
|
||||||
|
v.pos[1].to_bits().hash(&mut h);
|
||||||
|
v.pos_low[0].to_bits().hash(&mut h);
|
||||||
|
v.pos_low[1].to_bits().hash(&mut h);
|
||||||
|
}
|
||||||
h.finish()
|
h.finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1106,11 +1126,21 @@ impl Scene {
|
||||||
// draws only the text that belongs to it. Cached on the wire content
|
// 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.
|
// id so an unchanged wire set is not re-walked every frame.
|
||||||
let text_verts = self.gather_text_verts(&all_wires, wire_content_id);
|
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())
|
||||||
|
};
|
||||||
Some(ViewportData {
|
Some(ViewportData {
|
||||||
wires: all_wires,
|
wires: all_wires,
|
||||||
preview_wires,
|
preview_wires,
|
||||||
face3d_wires,
|
face3d_wires,
|
||||||
text_verts,
|
text_verts,
|
||||||
|
preview_text_verts,
|
||||||
draw_depths: self.draw_depth_map(),
|
draw_depths: self.draw_depth_map(),
|
||||||
hatches,
|
hatches,
|
||||||
wipeout_hatches,
|
wipeout_hatches,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue