forked from 1b-agent/opencadstudio-upstream
feat(text): render TEXT as SDF quads behind OCS_TEXT_SDF flag
Wires the SDF glyph atlas into the render loop end-to-end for top-level TEXT entities, gated by the OCS_TEXT_SDF env var so the default path is unchanged. Verified rendering on a real drawing: glyph quads land exactly over the existing stroke text (position, size, rotation), confirming the atlas → shader → pipeline → screen path and the RTE / UV math. - text::text_run_placement — extracted from to_truck (behaviour-preserved) so the stroke and SDF-quad paths compute an identical run placement. - sdf_atlas::text_atlas — process-wide GlyphAtlas (Mutex/OnceLock), shared by the collector (bakes glyphs) and the GPU upload (reads texels). - Scene::sdf_text_vertices — collects per-glyph TextVertex for the frame's TEXT entities via layout_glyph_quads + push_glyph_vertices. Empty unless OCS_TEXT_SDF is set. - ViewportData.text_verts + Pipeline text_pipeline/atlas/vbuf, upload_text (rebuilds the R8 atlas texture when it grows), and a text render pass drawn over the wires. Still additive over the stroke text; only top-level TEXT is wired (MTEXT, attributes, and block-internal text are next), and the collector re-runs per frame (vertex caching by geometry epoch is a follow-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
374cf7e89f
commit
896b6c385c
5 files changed
+208
-20
No files matched your search
+55
-19
@@ -47,7 +47,55 @@ pub(crate) fn sync_text_alignment_point(t: &mut Text) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolved placement of a TEXT run: the baseline-anchored run origin (WCS xy)
|
||||||
|
/// plus every parameter needed to lay the glyphs out. Shared by `to_truck` (the
|
||||||
|
/// stroke path) and the SDF-quad text collector so both place text identically.
|
||||||
|
pub struct TextPlacement {
|
||||||
|
/// Run-local origin (glyph space `[0,0]` maps here), WCS xy, kept f64.
|
||||||
|
pub origin: [f64; 2],
|
||||||
|
/// Entity elevation (WCS z).
|
||||||
|
pub elevation: f64,
|
||||||
|
pub height: f32,
|
||||||
|
pub rotation: f32,
|
||||||
|
pub width_factor: f32,
|
||||||
|
pub oblique_angle: f32,
|
||||||
|
pub font: String,
|
||||||
|
/// Raw entity value (as passed to the tessellator).
|
||||||
|
pub value: String,
|
||||||
|
/// Full WCS insertion point, for the Insertion snap.
|
||||||
|
pub wcs_insertion: [f64; 3],
|
||||||
|
}
|
||||||
|
|
||||||
fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
||||||
|
let p = text_run_placement(t, document);
|
||||||
|
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
|
||||||
|
// Strokes are in glyph-local space (origin = [0,0]).
|
||||||
|
let (strokes, fill_tris) = lff::tessellate_text_ex(
|
||||||
|
[0.0, 0.0],
|
||||||
|
p.height,
|
||||||
|
p.rotation,
|
||||||
|
p.width_factor,
|
||||||
|
p.oblique_angle,
|
||||||
|
&p.font,
|
||||||
|
&p.value,
|
||||||
|
);
|
||||||
|
TruckEntity {
|
||||||
|
object: TruckObject::Text(vec![TextStroke {
|
||||||
|
strokes,
|
||||||
|
origin: p.origin,
|
||||||
|
color: None,
|
||||||
|
fill_tris,
|
||||||
|
}]),
|
||||||
|
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||||
|
tangent_geoms: vec![],
|
||||||
|
key_vertices: vec![],
|
||||||
|
fill_tris: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute a TEXT entity's run placement (origin + layout params). Extracted
|
||||||
|
/// from `to_truck` verbatim so the stroke and SDF-quad paths agree exactly.
|
||||||
|
pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPlacement {
|
||||||
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
||||||
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
||||||
(
|
(
|
||||||
@@ -57,7 +105,6 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
|||||||
),
|
),
|
||||||
normal,
|
normal,
|
||||||
);
|
);
|
||||||
let snap_pt = glam::DVec3::new(wsx, wsy, wsz);
|
|
||||||
let resolved_style = resolve_text_style(&t.style, document);
|
let resolved_style = resolve_text_style(&t.style, document);
|
||||||
let font_name = resolved_style.font_name;
|
let font_name = resolved_style.font_name;
|
||||||
// AutoCAD text geometry rule: the entity stores the FINAL width factor /
|
// AutoCAD text geometry rule: the entity stores the FINAL width factor /
|
||||||
@@ -142,27 +189,16 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
|||||||
anchor_f64[0] - (anchor_local_x as f64 * cos_r - anchor_local_y as f64 * sin_r),
|
anchor_f64[0] - (anchor_local_x as f64 * cos_r - anchor_local_y as f64 * sin_r),
|
||||||
anchor_f64[1] - (anchor_local_x as f64 * sin_r + anchor_local_y as f64 * cos_r),
|
anchor_f64[1] - (anchor_local_x as f64 * sin_r + anchor_local_y as f64 * cos_r),
|
||||||
];
|
];
|
||||||
// Strokes are in glyph-local space (origin = [0,0]).
|
TextPlacement {
|
||||||
let (strokes, fill_tris) = lff::tessellate_text_ex(
|
origin,
|
||||||
[0.0, 0.0],
|
elevation: wsz,
|
||||||
t.height as f32,
|
height: t.height as f32,
|
||||||
rotation,
|
rotation,
|
||||||
width_factor,
|
width_factor,
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
&font_name,
|
font: font_name,
|
||||||
&t.value,
|
value: t.value.clone(),
|
||||||
);
|
wcs_insertion: [wsx, wsy, wsz],
|
||||||
TruckEntity {
|
|
||||||
object: TruckObject::Text(vec![TextStroke {
|
|
||||||
strokes,
|
|
||||||
origin,
|
|
||||||
color: None,
|
|
||||||
fill_tris,
|
|
||||||
}]),
|
|
||||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
|
||||||
tangent_geoms: vec![],
|
|
||||||
key_vertices: vec![],
|
|
||||||
fill_tris: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ pub struct Pipeline {
|
|||||||
/// vertex-stage storage buffers.
|
/// vertex-stage storage buffers.
|
||||||
hatch_batched_pipeline: Option<wgpu::RenderPipeline>,
|
hatch_batched_pipeline: Option<wgpu::RenderPipeline>,
|
||||||
image_pipeline: wgpu::RenderPipeline,
|
image_pipeline: wgpu::RenderPipeline,
|
||||||
|
/// SDF text-quad pipeline (Phase 2b): draws per-glyph quads sampling the
|
||||||
|
/// shared glyph atlas. Fed only when `OCS_TEXT_SDF` is set (else no verts).
|
||||||
|
text_pipeline: wgpu::RenderPipeline,
|
||||||
mesh_pipeline: wgpu::RenderPipeline,
|
mesh_pipeline: wgpu::RenderPipeline,
|
||||||
/// Depth-write-disabled variant of `mesh_pipeline` for non-opaque solids.
|
/// Depth-write-disabled variant of `mesh_pipeline` for non-opaque solids.
|
||||||
mesh_transparent_pipeline: wgpu::RenderPipeline,
|
mesh_transparent_pipeline: wgpu::RenderPipeline,
|
||||||
@@ -74,6 +77,14 @@ pub struct Pipeline {
|
|||||||
/// for instances / boundary / families / dashes). `None` on WebGL2.
|
/// for instances / boundary / families / dashes). `None` on WebGL2.
|
||||||
hatch_batched_bgl1: Option<wgpu::BindGroupLayout>,
|
hatch_batched_bgl1: Option<wgpu::BindGroupLayout>,
|
||||||
image_bgl1: wgpu::BindGroupLayout,
|
image_bgl1: wgpu::BindGroupLayout,
|
||||||
|
/// Group-1 layout for the text pipeline (atlas texture + sampler).
|
||||||
|
text_atlas_bgl: wgpu::BindGroupLayout,
|
||||||
|
/// GPU glyph atlas (texture + sampler + bind group). Rebuilt when the shared
|
||||||
|
/// CPU atlas grows (new glyphs baked). `None` until the first text upload.
|
||||||
|
text_atlas_gpu: Option<text_gpu::TextAtlasGpu>,
|
||||||
|
/// All glyph-quad vertices for the frame, one buffer, `None` when empty.
|
||||||
|
text_vbuf: Option<wgpu::Buffer>,
|
||||||
|
text_vcount: u32,
|
||||||
depth_texture_size: Size<u32>,
|
depth_texture_size: Size<u32>,
|
||||||
depth_view: wgpu::TextureView,
|
depth_view: wgpu::TextureView,
|
||||||
/// 4× MSAA color buffer for the main drawing passes.
|
/// 4× MSAA color buffer for the main drawing passes.
|
||||||
@@ -986,6 +997,11 @@ impl Pipeline {
|
|||||||
cache: None,
|
cache: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Text (SDF glyph quads) ─────────────────────────────────────────
|
||||||
|
let text_atlas_bgl = text_gpu::TextAtlasGpu::bind_group_layout(device);
|
||||||
|
let text_pipeline =
|
||||||
|
text_gpu::create_pipeline(device, &frame_bgl, &text_atlas_bgl, format, MSAA_SAMPLES);
|
||||||
|
|
||||||
let viewcube = ViewCubePipeline::new(device, queue, format);
|
let viewcube = ViewCubePipeline::new(device, queue, format);
|
||||||
|
|
||||||
let init_size = Size::new(1, 1);
|
let init_size = Size::new(1, 1);
|
||||||
@@ -1125,6 +1141,11 @@ impl Pipeline {
|
|||||||
hatch_pipeline,
|
hatch_pipeline,
|
||||||
hatch_batched_pipeline,
|
hatch_batched_pipeline,
|
||||||
image_pipeline,
|
image_pipeline,
|
||||||
|
text_pipeline,
|
||||||
|
text_atlas_bgl,
|
||||||
|
text_atlas_gpu: None,
|
||||||
|
text_vbuf: None,
|
||||||
|
text_vcount: 0,
|
||||||
mesh_pipeline,
|
mesh_pipeline,
|
||||||
mesh_transparent_pipeline,
|
mesh_transparent_pipeline,
|
||||||
mesh_highlight_pipeline,
|
mesh_highlight_pipeline,
|
||||||
@@ -1528,6 +1549,30 @@ impl Pipeline {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upload the frame's SDF text-quad vertices, and (re)build the GPU glyph
|
||||||
|
/// atlas from the shared CPU atlas when it grew (new glyphs baked by the
|
||||||
|
/// text collector). `verts` empty (flag off) leaves nothing to draw.
|
||||||
|
pub fn upload_text(
|
||||||
|
&mut self,
|
||||||
|
device: &wgpu::Device,
|
||||||
|
queue: &wgpu::Queue,
|
||||||
|
verts: &[text_gpu::TextVertex],
|
||||||
|
) {
|
||||||
|
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_vbuf = text_gpu::upload_vertices(device, verts);
|
||||||
|
self.text_vcount = verts.len() as u32;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn upload_uniforms(&self, queue: &wgpu::Queue, uniforms: &Uniforms) {
|
pub fn upload_uniforms(&self, queue: &wgpu::Queue, uniforms: &Uniforms) {
|
||||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(uniforms));
|
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(uniforms));
|
||||||
}
|
}
|
||||||
@@ -2020,6 +2065,40 @@ impl Pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Pass 5c: SDF text quads (drawn over wires) ────────────────────
|
||||||
|
if let (Some(vbuf), Some(atlas)) = (&self.text_vbuf, &self.text_atlas_gpu) {
|
||||||
|
if self.text_vcount > 0 {
|
||||||
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
label: Some("text.render_pass"),
|
||||||
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
|
view: msaa,
|
||||||
|
depth_slice: None,
|
||||||
|
resolve_target: None,
|
||||||
|
ops: wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
},
|
||||||
|
})],
|
||||||
|
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||||
|
view: &self.depth_view,
|
||||||
|
depth_ops: Some(wgpu::Operations {
|
||||||
|
load: wgpu::LoadOp::Load,
|
||||||
|
store: wgpu::StoreOp::Store,
|
||||||
|
}),
|
||||||
|
stencil_ops: None,
|
||||||
|
}),
|
||||||
|
timestamp_writes: None,
|
||||||
|
occlusion_query_set: None,
|
||||||
|
});
|
||||||
|
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||||
|
pass.set_pipeline(&self.text_pipeline);
|
||||||
|
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||||
|
pass.set_bind_group(1, &atlas.bind_group, &[]);
|
||||||
|
pass.set_vertex_buffer(0, vbuf.slice(..));
|
||||||
|
pass.draw(0..self.text_vcount, 0..1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Pass 6: wipeout fills (drawn after wires to mask them) ────────
|
// ── Pass 6: wipeout fills (drawn after wires to mask them) ────────
|
||||||
if !self.gpu_wipeouts.is_empty() {
|
if !self.gpu_wipeouts.is_empty() {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use crate::scene::text::sdf_atlas::GlyphAtlas;
|
|||||||
// ── Vertex ────────────────────────────────────────────────────────────────
|
// ── Vertex ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
||||||
pub struct TextVertex {
|
pub struct TextVertex {
|
||||||
pub pos: [f32; 3],
|
pub pos: [f32; 3],
|
||||||
pub pos_low: [f32; 3],
|
pub pos_low: [f32; 3],
|
||||||
|
|||||||
@@ -26,10 +26,19 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
use crate::scene::text::font_face::Face;
|
use crate::scene::text::font_face::Face;
|
||||||
use crate::scene::text::lff::Glyph;
|
use crate::scene::text::lff::Glyph;
|
||||||
|
|
||||||
|
/// Process-wide glyph atlas, shared by the text collector (which bakes glyphs
|
||||||
|
/// while building draw data) and the GPU upload (which reads the texels).
|
||||||
|
/// Mirrors the existing global font-glyph caches (see `ttf_glyph`).
|
||||||
|
pub fn text_atlas() -> &'static Mutex<GlyphAtlas> {
|
||||||
|
static ATLAS: OnceLock<Mutex<GlyphAtlas>> = OnceLock::new();
|
||||||
|
ATLAS.get_or_init(|| Mutex::new(GlyphAtlas::new(1024, 1024)))
|
||||||
|
}
|
||||||
|
|
||||||
// ── Bake configuration ──────────────────────────────────────────────────────
|
// ── Bake configuration ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// Texels per glyph unit. Glyphs live in a 9-unit cap-height space, so a cap
|
/// Texels per glyph unit. Glyphs live in a 9-unit cap-height space, so a cap
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ pub struct ViewportData {
|
|||||||
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
/// 3DFACE entity wires — separated so they are uploaded to the dedicated
|
||||||
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
/// face3d pipeline (fill + batched edges) instead of N individual WireGpu.
|
||||||
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.
|
||||||
|
pub(in crate::scene) 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
|
||||||
@@ -298,6 +300,7 @@ impl shader::Primitive for Primitive {
|
|||||||
}
|
}
|
||||||
if geo_changed {
|
if geo_changed {
|
||||||
inner.upload_images(device, queue, &vp.images[..]);
|
inner.upload_images(device, queue, &vp.images[..]);
|
||||||
|
inner.upload_text(device, queue, &vp.text_verts[..]);
|
||||||
}
|
}
|
||||||
inner.cached_epoch = cur_key;
|
inner.cached_epoch = cur_key;
|
||||||
}
|
}
|
||||||
@@ -772,6 +775,59 @@ pub(crate) fn adapt_to_bg(color: [f32; 4], bg: [f32; 4]) -> [f32; 4] {
|
|||||||
// ── Primitive builder helpers (called by ViewportPane's shader::Program impl) ──
|
// ── Primitive builder helpers (called by ViewportPane's shader::Program impl) ──
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
|
/// SDF text-quad vertices for the current document's TEXT entities. Behind
|
||||||
|
/// the `OCS_TEXT_SDF` env flag (Phase 2b bring-up) so the default render
|
||||||
|
/// path is untouched; empty otherwise. Glyphs are baked into the shared
|
||||||
|
/// atlas as a side effect. Both the stroke path and this share
|
||||||
|
/// `text_run_placement`, so quads land exactly where the strokes do.
|
||||||
|
#[allow(dead_code)] // consumed by the text render pass in the GPU integration step
|
||||||
|
pub(in crate::scene) fn sdf_text_vertices(
|
||||||
|
&self,
|
||||||
|
) -> Vec<crate::scene::pipeline::text_gpu::TextVertex> {
|
||||||
|
self.sdf_text_vertices_enabled(std::env::var_os("OCS_TEXT_SDF").is_some())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn sdf_text_vertices_enabled(
|
||||||
|
&self,
|
||||||
|
enabled: bool,
|
||||||
|
) -> Vec<crate::scene::pipeline::text_gpu::TextVertex> {
|
||||||
|
use crate::scene::pipeline::text_gpu;
|
||||||
|
use crate::scene::text::{glyph_quads, sdf_atlas};
|
||||||
|
if !enabled {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Ok(mut atlas) = sdf_atlas::text_atlas().lock() else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let mut out = Vec::new();
|
||||||
|
for e in self.document.entities() {
|
||||||
|
if let acadrust::EntityType::Text(t) = e {
|
||||||
|
let p = crate::entities::text::text_run_placement(t, &self.document);
|
||||||
|
let color = self.render_style(e).0;
|
||||||
|
let quads = glyph_quads::layout_glyph_quads(
|
||||||
|
&mut atlas,
|
||||||
|
p.height,
|
||||||
|
p.rotation,
|
||||||
|
p.width_factor,
|
||||||
|
p.oblique_angle,
|
||||||
|
1.0,
|
||||||
|
&p.font,
|
||||||
|
&p.value,
|
||||||
|
);
|
||||||
|
text_gpu::push_glyph_vertices(
|
||||||
|
&mut out,
|
||||||
|
&quads,
|
||||||
|
[p.origin[0], p.origin[1], p.elevation],
|
||||||
|
1.0,
|
||||||
|
color,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
/// Build the unified multi-viewport `Primitive` for the current layout.
|
/// Build the unified multi-viewport `Primitive` for the current layout.
|
||||||
/// Model layout → one full-window viewport (more once tiled); paper
|
/// Model layout → one full-window viewport (more once tiled); paper
|
||||||
/// layout → one viewport per floating content viewport. Each entry is
|
/// layout → one viewport per floating content viewport. Each entry is
|
||||||
@@ -1053,10 +1109,18 @@ impl Scene {
|
|||||||
self.meshes_arc()
|
self.meshes_arc()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SDF text quads (Phase 2b, behind OCS_TEXT_SDF). Model/content only —
|
||||||
|
// the paper sheet must not draw model-space text onto the sheet.
|
||||||
|
let text_verts = if inst.paper_sheet {
|
||||||
|
Arc::new(Vec::new())
|
||||||
|
} else {
|
||||||
|
Arc::new(self.sdf_text_vertices())
|
||||||
|
};
|
||||||
Some(ViewportData {
|
Some(ViewportData {
|
||||||
wires: all_wires,
|
wires: all_wires,
|
||||||
preview_wires,
|
preview_wires,
|
||||||
face3d_wires,
|
face3d_wires,
|
||||||
|
text_verts,
|
||||||
draw_depths: self.draw_depth_map(),
|
draw_depths: self.draw_depth_map(),
|
||||||
hatches,
|
hatches,
|
||||||
wipeout_hatches,
|
wipeout_hatches,
|
||||||
|
|||||||
Reference in new issue
Block a user