1use crate::canvas::{CanvasDimensions, TextBuildCtor};
2use crate::error::AvengerWgpuError;
3
4use crate::marks::gradient::{to_color_or_gradient_coord, GradientAtlasBuilder};
5use crate::marks::image::ImageAtlasBuilder;
6use avenger::marks::area::{AreaMark, AreaOrientation};
7use avenger::marks::image::ImageMark;
8use avenger::marks::line::LineMark;
9use avenger::marks::path::{PathMark, PathTransform};
10use avenger::marks::rect::RectMark;
11use avenger::marks::rule::RuleMark;
12use avenger::marks::symbol::SymbolMark;
13use avenger::marks::trail::TrailMark;
14use avenger::marks::value::{ColorOrGradient, ImageAlign, ImageBaseline, StrokeCap, StrokeJoin};
15use etagere::euclid::UnknownUnit;
16use image::DynamicImage;
17use itertools::izip;
18use lyon::algorithms::aabb::bounding_box;
19use lyon::algorithms::measure::{PathMeasurements, PathSampler, SampleType};
20use lyon::geom::euclid::{Point2D, Vector2D};
21use lyon::geom::{Angle, Box2D, Point};
22use lyon::lyon_tessellation::{
23 AttributeIndex, BuffersBuilder, FillOptions, FillTessellator, FillVertex,
24 FillVertexConstructor, LineCap, LineJoin, StrokeOptions, StrokeTessellator, StrokeVertex,
25 StrokeVertexConstructor, VertexBuffers,
26};
27use lyon::path::builder::SvgPathBuilder;
28use lyon::path::builder::{BorderRadii, WithSvg};
29use lyon::path::path::BuilderImpl;
30use lyon::path::{Path, Winding};
31use std::ops::{Mul, Neg, Range};
32
33use wgpu::util::DeviceExt;
34use wgpu::{
35 BindGroup, BindGroupLayout, CommandBuffer, Device, Extent3d, Queue, TextureFormat, TextureView,
36 VertexBufferLayout,
37};
38
39use crate::marks::text::{TextAtlasBuilderTrait, TextInstance};
41
42use avenger::marks::arc::ArcMark;
43use avenger::marks::group::Clip;
44use avenger::marks::text::TextMark;
45
46#[cfg(feature = "rayon")]
47use {crate::par_izip, rayon::prelude::*};
48
49pub const GRADIENT_TEXTURE_CODE: f32 = -1.0;
50pub const IMAGE_TEXTURE_CODE: f32 = -2.0;
51pub const TEXT_TEXTURE_CODE: f32 = -3.0;
52
53const NORMALIZED_SYMBOL_STROKE_WIDTH: f32 = 0.1;
54
55#[repr(C)]
56#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
57pub struct MultiUniform {
58 pub size: [f32; 2],
59 pub scale: f32,
60 _pad: [f32; 1],
61}
62
63#[repr(C)]
64#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
65pub struct MultiVertex {
66 pub position: [f32; 2],
67 pub color: [f32; 4],
68 pub top_left: [f32; 2],
69 pub bottom_right: [f32; 2],
70}
71
72const VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 4] = wgpu::vertex_attr_array![
73 0 => Float32x2, 1 => Float32x4, 2 => Float32x2, 3 => Float32x2, ];
78
79impl MultiVertex {
80 pub fn desc() -> VertexBufferLayout<'static> {
81 VertexBufferLayout {
82 array_stride: std::mem::size_of::<MultiVertex>() as wgpu::BufferAddress,
83 step_mode: wgpu::VertexStepMode::Vertex,
84 attributes: &VERTEX_ATTRIBUTES,
85 }
86 }
87}
88
89#[derive(Clone)]
90pub struct MultiMarkBatch {
91 pub indices_range: Range<u32>,
92 pub clip: Clip,
93 pub clip_indices_range: Option<Range<u32>>,
94 pub image_atlas_index: Option<usize>,
95 pub gradient_atlas_index: Option<usize>,
96 pub text_atlas_index: Option<usize>,
97}
98
99pub struct MultiMarkRenderer {
100 verts_inds: Vec<(Vec<MultiVertex>, Vec<u32>)>,
101 clip_verts_inds: Vec<(Vec<MultiVertex>, Vec<u32>)>,
102 batches: Vec<MultiMarkBatch>,
103 uniform: MultiUniform,
104 gradient_atlas_builder: GradientAtlasBuilder,
105 image_atlas_builder: ImageAtlasBuilder,
106 text_atlas_builder: Box<dyn TextAtlasBuilderTrait>,
107 dimensions: CanvasDimensions,
108}
109
110impl MultiMarkRenderer {
111 pub fn new(
112 dimensions: CanvasDimensions,
113 text_atlas_builder_ctor: Option<TextBuildCtor>,
114 ) -> Self {
115 let text_atlas_builder = if let Some(text_atlas_builder_ctor) = text_atlas_builder_ctor {
116 text_atlas_builder_ctor()
117 } else {
118 cfg_if::cfg_if! {
119 if #[cfg(feature = "cosmic-text")] {
120 use crate::marks::cosmic::CosmicTextRasterizer;
121 use crate::marks::text::TextAtlasBuilder;
122 use std::sync::Arc;
123 let inner_text_atlas_builder: Box<dyn TextAtlasBuilderTrait> = Box::new(TextAtlasBuilder::new(Arc::new(CosmicTextRasterizer)));
124 } else {
125 use crate::marks::text::NullTextAtlasBuilder;
126 let inner_text_atlas_builder: Box<dyn TextAtlasBuilderTrait> = Box::new(NullTextAtlasBuilder);
127 }
128 };
129 inner_text_atlas_builder
130 };
131
132 Self {
133 verts_inds: vec![],
134 clip_verts_inds: vec![],
135 batches: vec![],
136 dimensions,
137 uniform: MultiUniform {
138 size: dimensions.size,
139 scale: dimensions.scale,
140 _pad: [0.0],
141 },
142 gradient_atlas_builder: GradientAtlasBuilder::new(),
143 image_atlas_builder: ImageAtlasBuilder::new(),
144 text_atlas_builder,
145 }
146 }
147
148 fn add_clip_path(
149 &mut self,
150 clip: &Clip,
151 should_clip: bool,
152 ) -> Result<Option<Range<u32>>, AvengerWgpuError> {
153 if !should_clip {
154 return Ok(None);
155 }
156
157 if let Clip::Path(path) = &clip {
158 let bbox = bounding_box(path);
160
161 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
163 let mut builder = BuffersBuilder::new(
164 &mut buffers,
165 crate::marks::multi::VertexPositions {
166 fill: [0.0, 0.0, 0.0, 1.0],
167 stroke: [0.0, 0.0, 0.0, 0.0],
168 top_left: bbox.min.to_array(),
169 bottom_right: bbox.max.to_array(),
170 },
171 );
172
173 let mut fill_tessellator = FillTessellator::new();
175 let fill_options = FillOptions::default().with_tolerance(0.05);
176 fill_tessellator.tessellate_path(path, &fill_options, &mut builder)?;
177
178 let start_index = self.num_clip_indices() as u32;
179 self.clip_verts_inds
180 .push((buffers.vertices, buffers.indices));
181 let end_index = self.num_clip_indices() as u32;
182 Ok(Some(start_index..end_index))
183 } else {
184 Ok(None)
185 }
186 }
187
188 #[tracing::instrument(skip_all)]
189 pub fn add_rule_mark(
190 &mut self,
191 mark: &RuleMark,
192 origin: [f32; 2],
193 clip: &Clip,
194 ) -> Result<(), AvengerWgpuError> {
195 let (gradient_atlas_index, grad_coords) = self
196 .gradient_atlas_builder
197 .register_gradients(&mark.gradients);
198
199 let verts_inds = if let Some(stroke_dash_iter) = mark.stroke_dash_iter() {
200 izip!(
201 stroke_dash_iter,
202 mark.x0_iter(),
203 mark.y0_iter(),
204 mark.x1_iter(),
205 mark.y1_iter(),
206 mark.stroke_iter(),
207 mark.stroke_width_iter(),
208 mark.stroke_cap_iter(),
209 ).map(|(stroke_dash, x0, y0, x1, y1, stroke, stroke_width, cap)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
210 let mut dash_idx = 0;
212
213 let mut start_dash_dist: f32 = 0.0;
215
216 let rule_len = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
218
219 let xhat = (x1 - x0) / rule_len;
221 let yhat = (y1 - y0) / rule_len;
222
223 let mut draw = true;
226
227 let mut path_builder = lyon::path::Path::builder().with_svg();
229
230 while start_dash_dist < rule_len {
231 let end_dash_dist = if start_dash_dist + stroke_dash[dash_idx] >= rule_len {
232 rule_len
234 } else {
235 start_dash_dist + stroke_dash[dash_idx]
237 };
238
239 if draw {
240 let dash_x0 = x0 + xhat * start_dash_dist;
241 let dash_y0 = y0 + yhat * start_dash_dist;
242 let dash_x1 = x0 + xhat * end_dash_dist;
243 let dash_y1 = y0 + yhat * end_dash_dist;
244
245 path_builder.move_to(Point::new(dash_x0 + origin[0], dash_y0 + origin[1]));
246 path_builder.line_to(Point::new(dash_x1 + origin[0], dash_y1 + origin[1]));
247 }
248
249 start_dash_dist = end_dash_dist;
251
252 dash_idx = (dash_idx + 1) % stroke_dash.len();
254
255 draw = !draw;
257 }
258
259 let path = path_builder.build();
260 let bbox = bounding_box(&path);
261
262 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
264 let mut builder = BuffersBuilder::new(
265 &mut buffers,
266 VertexPositions {
267 fill: [0.0, 0.0, 0.0, 0.0],
268 stroke: to_color_or_gradient_coord(stroke, &grad_coords),
269 top_left: bbox.min.to_array(),
270 bottom_right: bbox.max.to_array(),
271 },
272 );
273
274 let mut stroke_tessellator = StrokeTessellator::new();
276 let stroke_options = StrokeOptions::default()
277 .with_tolerance(0.05)
278 .with_line_join(LineJoin::Miter)
279 .with_line_cap(match cap {
280 StrokeCap::Butt => LineCap::Butt,
281 StrokeCap::Round => LineCap::Round,
282 StrokeCap::Square => LineCap::Square,
283 })
284 .with_line_width(*stroke_width);
285 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
286 Ok((buffers.vertices, buffers.indices))
287 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
288 } else {
289 izip!(
290 mark.x0_iter(),
291 mark.y0_iter(),
292 mark.x1_iter(),
293 mark.y1_iter(),
294 mark.stroke_iter(),
295 mark.stroke_width_iter(),
296 mark.stroke_cap_iter(),
297 ).map(|(x0, y0, x1, y1, stroke, stroke_width, cap)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
298 let mut path_builder = lyon::path::Path::builder().with_svg();
299 path_builder.move_to(Point::new(*x0 + origin[0], *y0 + origin[1]));
300 path_builder.line_to(Point::new(*x1 + origin[0], *y1 + origin[1]));
301 let path = path_builder.build();
302 let bbox = bounding_box(&path);
303
304 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
306 let mut builder = BuffersBuilder::new(
307 &mut buffers,
308 VertexPositions {
309 fill: [0.0, 0.0, 0.0, 0.0],
310 stroke: to_color_or_gradient_coord(stroke, &grad_coords),
311 top_left: bbox.min.to_array(),
312 bottom_right: bbox.max.to_array(),
313 },
314 );
315
316 let mut stroke_tessellator = StrokeTessellator::new();
318 let stroke_options = StrokeOptions::default()
319 .with_tolerance(0.05)
320 .with_line_join(LineJoin::Miter)
321 .with_line_cap(match cap {
322 StrokeCap::Butt => LineCap::Butt,
323 StrokeCap::Round => LineCap::Round,
324 StrokeCap::Square => LineCap::Square,
325 })
326 .with_line_width(*stroke_width);
327 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
328 Ok((buffers.vertices, buffers.indices))
329
330 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
331 };
332
333 let start_ind = self.num_indices();
334 let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
335 let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
336
337 let batch = MultiMarkBatch {
338 indices_range,
339 clip: clip.maybe_clip(mark.clip),
340 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
341 image_atlas_index: None,
342 gradient_atlas_index,
343 text_atlas_index: None,
344 };
345
346 self.verts_inds.extend(verts_inds);
347 self.batches.push(batch);
348 Ok(())
349 }
350
351 #[tracing::instrument(skip_all)]
352 pub fn add_rect_mark(
353 &mut self,
354 mark: &RectMark,
355 origin: [f32; 2],
356 clip: &Clip,
357 ) -> Result<(), AvengerWgpuError> {
358 let (gradient_atlas_index, grad_coords) = self
359 .gradient_atlas_builder
360 .register_gradients(&mark.gradients);
361
362 let verts_inds = if mark.gradients.is_empty()
363 && mark.stroke_width.equals_scalar(0.0)
364 && mark.corner_radius.equals_scalar(0.0)
365 {
366 let mut verts: Vec<MultiVertex> = Vec::with_capacity((mark.len * 4) as usize);
370 let mut indicies: Vec<u32> = Vec::with_capacity((mark.len * 6) as usize);
371
372 for (i, x, y, width, height, fill) in izip!(
373 0..mark.len,
374 mark.x_iter(),
375 mark.y_iter(),
376 mark.width_iter(),
377 mark.height_iter(),
378 mark.fill_iter()
379 ) {
380 let x0 = *x + origin[0];
381 let y0 = *y + origin[1];
382 let x1 = x0 + width;
383 let y1 = y0 + height;
384 let top_left = [x0, y0];
385 let bottom_right = [x1, y1];
386 let color = fill.color_or_transparent();
387 verts.push(MultiVertex {
388 position: [x0, y0],
389 color,
390 top_left,
391 bottom_right,
392 });
393 verts.push(MultiVertex {
394 position: [x0, y1],
395 color,
396 top_left,
397 bottom_right,
398 });
399 verts.push(MultiVertex {
400 position: [x1, y1],
401 color,
402 top_left,
403 bottom_right,
404 });
405 verts.push(MultiVertex {
406 position: [x1, y0],
407 color,
408 top_left,
409 bottom_right,
410 });
411 let offset = i * 4;
412 indicies.extend([
413 offset,
414 offset + 1,
415 offset + 2,
416 offset,
417 offset + 2,
418 offset + 3,
419 ])
420 }
421
422 vec![(verts, indicies)]
423 } else {
424 let build_verts_inds =
426 |x: &f32,
427 y: &f32,
428 width: &f32,
429 height: &f32,
430 fill: &ColorOrGradient,
431 stroke: &ColorOrGradient,
432 stroke_width: &f32,
433 corner_radius: &f32|
434 -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
435 let mut path_builder = lyon::path::Path::builder();
437 let x0 = *x + origin[0];
438 let y0 = *y + origin[1];
439 let x1 = x0 + width;
440 let y1 = y0 + height;
441 if *corner_radius > 0.0 {
442 path_builder.add_rounded_rectangle(
443 &Box2D::new(Point2D::new(x0, y0), Point2D::new(x1, y1)),
444 &BorderRadii {
445 top_left: *corner_radius,
446 top_right: *corner_radius,
447 bottom_left: *corner_radius,
448 bottom_right: *corner_radius,
449 },
450 Winding::Positive,
451 );
452 } else {
453 path_builder.add_rectangle(
454 &Box2D::new(Point2D::new(x0, y0), Point2D::new(x1, y1)),
455 Winding::Positive,
456 );
457 }
458
459 let path = path_builder.build();
461 let bbox = bounding_box(&path);
462
463 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
465 let mut builder = BuffersBuilder::new(
466 &mut buffers,
467 VertexPositions {
468 fill: to_color_or_gradient_coord(fill, &grad_coords),
469 stroke: to_color_or_gradient_coord(stroke, &grad_coords),
470 top_left: bbox.min.to_array(),
471 bottom_right: bbox.max.to_array(),
472 },
473 );
474
475 let mut fill_tessellator = FillTessellator::new();
477 let fill_options = FillOptions::default().with_tolerance(0.05);
478
479 fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
480
481 if *stroke_width > 0.0 {
483 let mut stroke_tessellator = StrokeTessellator::new();
484 let stroke_options = StrokeOptions::default()
485 .with_tolerance(0.05)
486 .with_line_width(*stroke_width);
487 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
488 }
489
490 Ok((buffers.vertices, buffers.indices))
491 };
492
493 cfg_if::cfg_if! {
494 if #[cfg(feature = "rayon")] {
495 par_izip!(
496 mark.x_vec(),
497 mark.y_vec(),
498 mark.width_vec(),
499 mark.height_vec(),
500 mark.fill_vec(),
501 mark.stroke_vec(),
502 mark.stroke_width_vec(),
503 mark.corner_radius_vec(),
504 ).map(|(x, y, width, height, fill, stroke, stroke_width, corner_radius)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
505 build_verts_inds(x, &y, &width, &height, &fill, &stroke, &stroke_width, &corner_radius)
506 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
507 } else {
508 izip!(
509 mark.x_iter(),
510 mark.y_iter(),
511 mark.width_iter(),
512 mark.height_iter(),
513 mark.fill_iter(),
514 mark.stroke_iter(),
515 mark.stroke_width_iter(),
516 mark.corner_radius_iter(),
517 ).map(|(x, y, width, height, fill, stroke, stroke_width, corner_radius)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
518 build_verts_inds(x, y, width, height, fill, stroke, stroke_width, corner_radius)
519 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?
520 }
521 }
522 };
523
524 let start_ind = self.num_indices();
525 let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
526 let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
527
528 let batch = MultiMarkBatch {
529 indices_range,
530 clip: clip.maybe_clip(mark.clip),
531 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
532 image_atlas_index: None,
533 gradient_atlas_index,
534 text_atlas_index: None,
535 };
536
537 self.verts_inds.extend(verts_inds);
538 self.batches.push(batch);
539 Ok(())
540 }
541
542 #[tracing::instrument(skip_all)]
543 pub fn add_path_mark(
544 &mut self,
545 mark: &PathMark,
546 origin: [f32; 2],
547 clip: &Clip,
548 ) -> Result<(), AvengerWgpuError> {
549 let (gradient_atlas_index, grad_coords) = self
550 .gradient_atlas_builder
551 .register_gradients(&mark.gradients);
552
553 let build_verts_inds = |path: &lyon::path::Path,
554 fill: &ColorOrGradient,
555 stroke: &ColorOrGradient,
556 transform: &PathTransform|
557 -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
558 let path = path
560 .clone()
561 .transformed(&transform.then_translate(Vector2D::new(origin[0], origin[1])));
562 let bbox = bounding_box(&path);
563
564 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
566 let mut builder = BuffersBuilder::new(
567 &mut buffers,
568 crate::marks::multi::VertexPositions {
569 fill: to_color_or_gradient_coord(fill, &grad_coords),
570 stroke: to_color_or_gradient_coord(stroke, &grad_coords),
571 top_left: bbox.min.to_array(),
572 bottom_right: bbox.max.to_array(),
573 },
574 );
575
576 let mut fill_tessellator = FillTessellator::new();
578 let fill_options = FillOptions::default().with_tolerance(0.05);
579
580 fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
581
582 if let Some(stroke_width) = mark.stroke_width {
584 let mut stroke_tessellator = StrokeTessellator::new();
585 let stroke_options = StrokeOptions::default()
586 .with_tolerance(0.05)
587 .with_line_join(match mark.stroke_join {
588 StrokeJoin::Miter => LineJoin::Miter,
589 StrokeJoin::Round => LineJoin::Round,
590 StrokeJoin::Bevel => LineJoin::Bevel,
591 })
592 .with_line_cap(match mark.stroke_cap {
593 StrokeCap::Butt => LineCap::Butt,
594 StrokeCap::Round => LineCap::Round,
595 StrokeCap::Square => LineCap::Square,
596 })
597 .with_line_width(stroke_width);
598 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
599 }
600
601 Ok((buffers.vertices, buffers.indices))
602 };
603
604 cfg_if::cfg_if! {
605 if #[cfg(feature = "rayon")] {
606 let verts_inds = par_izip!(
607 mark.path_vec(),
608 mark.fill_vec(),
609 mark.stroke_vec(),
610 mark.transform_vec(),
611 ).map(|(path, fill, stroke, transform)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
612 build_verts_inds(path, &fill, &stroke, &transform)
613 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
614 } else {
615 let verts_inds = izip!(
616 mark.path_iter(),
617 mark.fill_iter(),
618 mark.stroke_iter(),
619 mark.transform_iter(),
620 ).map(|(path, fill, stroke, transform)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
621 build_verts_inds(path, fill, stroke, transform)
622 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
623 }
624 }
625
626 let start_ind = self.num_indices();
627 let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
628 let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
629
630 let batch = MultiMarkBatch {
631 indices_range,
632 clip: clip.maybe_clip(mark.clip),
633 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
634 image_atlas_index: None,
635 gradient_atlas_index,
636 text_atlas_index: None,
637 };
638
639 self.verts_inds.extend(verts_inds);
640 self.batches.push(batch);
641 Ok(())
642 }
643
644 #[tracing::instrument(skip_all)]
645 pub fn add_symbol_mark(
646 &mut self,
647 mark: &SymbolMark,
648 origin: [f32; 2],
649 clip: &Clip,
650 ) -> Result<(), AvengerWgpuError> {
651 let paths = mark.shapes.iter().map(|s| s.as_path()).collect::<Vec<_>>();
652
653 let (gradient_atlas_index, grad_coords) = self
655 .gradient_atlas_builder
656 .register_gradients(&mark.gradients);
657
658 let max_scale = mark.max_size().sqrt();
660
661 let stroke_width = mark.stroke_width.unwrap_or(0.0);
663
664 let mut shape_verts_inds: Vec<(Vec<SymbolVertex>, Vec<u32>)> = Vec::new();
666 for path in paths {
667 let path = path
669 .as_ref()
670 .clone()
671 .transformed(&PathTransform::scale(max_scale, max_scale));
672
673 let mut buffers: VertexBuffers<SymbolVertex, u32> = VertexBuffers::new();
675 let mut builder =
676 BuffersBuilder::new(&mut buffers, SymbolVertexPositions { scale: max_scale });
677
678 let mut fill_tessellator = FillTessellator::new();
680 let fill_options = FillOptions::default().with_tolerance(0.1);
681
682 fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
683
684 if stroke_width > 0.0 {
686 let mut stroke_tessellator = StrokeTessellator::new();
687 let stroke_options = StrokeOptions::default()
688 .with_tolerance(0.1)
689 .with_line_join(LineJoin::Miter)
690 .with_line_cap(LineCap::Butt)
691 .with_line_width(NORMALIZED_SYMBOL_STROKE_WIDTH);
692 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
693 }
694
695 shape_verts_inds.push((buffers.vertices, buffers.indices));
696 }
697
698 let build_verts_inds = |x: &f32,
700 y: &f32,
701 fill: &ColorOrGradient,
702 size: &f32,
703 stroke: &ColorOrGradient,
704 angle: &f32,
705 shape_index: &usize|
706 -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
707 let (symbol_verts, indices) = &shape_verts_inds[*shape_index];
708 let fill = to_color_or_gradient_coord(fill, &grad_coords);
709 let stroke = to_color_or_gradient_coord(stroke, &grad_coords);
710
711 let multi_verts = symbol_verts
712 .iter()
713 .map(|sv| {
714 sv.as_multi_vertex(
715 *size,
716 *x + origin[0],
717 *y + origin[1],
718 *angle,
719 fill,
720 stroke,
721 stroke_width,
722 )
723 })
724 .collect::<Vec<_>>();
725 Ok((multi_verts, indices.clone()))
726 };
727
728 cfg_if::cfg_if! {
729 if #[cfg(feature = "rayon")] {
730 let verts_inds = par_izip!(
731 mark.x_vec(),
732 mark.y_vec(),
733 mark.fill_vec(),
734 mark.size_vec(),
735 mark.stroke_vec(),
736 mark.angle_vec(),
737 mark.shape_index_vec(),
738 ).map(|(x, y, fill, size, stroke, angle, shape_index)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
739 build_verts_inds(x, &y, &fill, &size, &stroke, &angle, &shape_index)
740 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
741 } else {
742 let verts_inds = izip!(
743 mark.x_iter(),
744 mark.y_iter(),
745 mark.fill_iter(),
746 mark.size_iter(),
747 mark.stroke_iter(),
748 mark.angle_iter(),
749 mark.shape_index_iter(),
750 ).map(|(x, y, fill, size, stroke, angle, shape_index)| -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
751 build_verts_inds(x, y, fill, size, stroke, angle, shape_index)
752 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
753 }
754 };
755
756 let start_ind = self.num_indices();
757 let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
758 let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
759
760 let batch = MultiMarkBatch {
761 indices_range,
762 clip: clip.maybe_clip(mark.clip),
763 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
764 image_atlas_index: None,
765 gradient_atlas_index,
766 text_atlas_index: None,
767 };
768
769 self.verts_inds.extend(verts_inds);
770 self.batches.push(batch);
771 Ok(())
772 }
773
774 #[tracing::instrument(skip_all)]
775 pub fn add_line_mark(
776 &mut self,
777 mark: &LineMark,
778 origin: [f32; 2],
779 clip: &Clip,
780 ) -> Result<(), AvengerWgpuError> {
781 let (gradient_atlas_index, grad_coords) = self
782 .gradient_atlas_builder
783 .register_gradients(&mark.gradients);
784
785 let mut defined_paths: Vec<Path> = Vec::new();
786
787 let mut path_builder = lyon::path::Path::builder().with_svg();
789 let mut path_len = 0;
790 for (x, y, defined) in izip!(mark.x_iter(), mark.y_iter(), mark.defined_iter()) {
791 if *defined {
792 if path_len > 0 {
793 path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
795 } else {
796 path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
798 }
799 path_len += 1;
800 } else {
801 if path_len == 1 {
802 path_builder.close();
805 }
806 defined_paths.push(path_builder.build());
807 path_builder = lyon::path::Path::builder().with_svg();
808 path_len = 0;
809 }
810 }
811 defined_paths.push(path_builder.build());
812
813 let defined_paths = if let Some(stroke_dash) = &mark.stroke_dash {
814 let mut dashed_paths: Vec<Path> = Vec::new();
816 for path in defined_paths.iter() {
817 let mut dash_path_builder = lyon::path::Path::builder();
818 let path_measurements = PathMeasurements::from_path(path, 0.1);
819 let mut sampler =
820 PathSampler::new(&path_measurements, path, &(), SampleType::Distance);
821
822 let mut dash_idx = 0;
824
825 let mut start_dash_dist: f32 = 0.0;
827
828 let line_len = sampler.length();
830
831 let mut draw = true;
834
835 while start_dash_dist < line_len {
836 let end_dash_dist = if start_dash_dist + stroke_dash[dash_idx] >= line_len {
837 line_len
839 } else {
840 start_dash_dist + stroke_dash[dash_idx]
842 };
843
844 if draw {
845 sampler.split_range(start_dash_dist..end_dash_dist, &mut dash_path_builder);
846 }
847
848 start_dash_dist = end_dash_dist;
850
851 dash_idx = (dash_idx + 1) % stroke_dash.len();
853
854 draw = !draw;
856 }
857 dashed_paths.push(dash_path_builder.build())
858 }
859 dashed_paths
860 } else {
861 defined_paths
862 };
863
864 let mut verts: Vec<MultiVertex> = Vec::new();
865 let mut indices: Vec<u32> = Vec::new();
866
867 for path in &defined_paths {
868 let bbox = bounding_box(path);
869 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
871 let mut buffers_builder = BuffersBuilder::new(
872 &mut buffers,
873 VertexPositions {
874 fill: [0.0, 0.0, 0.0, 0.0],
875 stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
876 top_left: bbox.min.to_array(),
877 bottom_right: bbox.max.to_array(),
878 },
879 );
880
881 let mut stroke_tessellator = StrokeTessellator::new();
883 let stroke_options = StrokeOptions::default()
884 .with_tolerance(0.05)
885 .with_line_join(match mark.stroke_join {
886 StrokeJoin::Miter => LineJoin::Miter,
887 StrokeJoin::Round => LineJoin::Round,
888 StrokeJoin::Bevel => LineJoin::Bevel,
889 })
890 .with_line_cap(match mark.stroke_cap {
891 StrokeCap::Butt => LineCap::Butt,
892 StrokeCap::Round => LineCap::Round,
893 StrokeCap::Square => LineCap::Square,
894 })
895 .with_line_width(mark.stroke_width);
896 stroke_tessellator.tessellate_path(path, &stroke_options, &mut buffers_builder)?;
897
898 let index_offset = verts.len() as u32;
899 verts.extend(buffers.vertices);
900 indices.extend(buffers.indices.into_iter().map(|i| i + index_offset));
901 }
902
903 let start_ind = self.num_indices();
904 let indices_range = (start_ind as u32)..((start_ind + indices.len()) as u32);
905
906 let batch = MultiMarkBatch {
907 indices_range,
908 clip: clip.maybe_clip(mark.clip),
909 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
910 image_atlas_index: None,
911 gradient_atlas_index,
912 text_atlas_index: None,
913 };
914
915 self.verts_inds.push((verts, indices));
916 self.batches.push(batch);
917 Ok(())
918 }
919
920 #[tracing::instrument(skip_all)]
921 pub fn add_area_mark(
922 &mut self,
923 mark: &AreaMark,
924 origin: [f32; 2],
925 clip: &Clip,
926 ) -> Result<(), AvengerWgpuError> {
927 let (gradient_atlas_index, grad_coords) = self
928 .gradient_atlas_builder
929 .register_gradients(&mark.gradients);
930
931 let mut path_builder = lyon::path::Path::builder().with_svg();
932 let mut tail: Vec<(f32, f32)> = Vec::new();
933
934 fn close_area(b: &mut WithSvg<BuilderImpl>, tail: &mut Vec<(f32, f32)>) {
935 if tail.is_empty() {
936 return;
937 }
938 for (x, y) in tail.iter().rev() {
939 b.line_to(lyon::geom::point(*x, *y));
940 }
941
942 tail.clear();
943 b.close();
944 }
945
946 if mark.orientation == AreaOrientation::Vertical {
947 for (x, y, y2, defined) in izip!(
948 mark.x_iter(),
949 mark.y_iter(),
950 mark.y2_iter(),
951 mark.defined_iter(),
952 ) {
953 if *defined {
954 if !tail.is_empty() {
955 path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
957 } else {
958 path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
960 }
961 tail.push((*x + origin[0], *y2 + origin[1]));
962 } else {
963 close_area(&mut path_builder, &mut tail);
964 }
965 }
966 } else {
967 for (y, x, x2, defined) in izip!(
968 mark.y_iter(),
969 mark.x_iter(),
970 mark.x2_iter(),
971 mark.defined_iter(),
972 ) {
973 if *defined {
974 if !tail.is_empty() {
975 path_builder.line_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
977 } else {
978 path_builder.move_to(lyon::geom::point(*x + origin[0], *y + origin[1]));
980 }
981 tail.push((*x2 + origin[0], *y + origin[1]));
982 } else {
983 close_area(&mut path_builder, &mut tail);
984 }
985 }
986 }
987
988 close_area(&mut path_builder, &mut tail);
989 let path = path_builder.build();
990 let bbox = bounding_box(&path);
991
992 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
994 let mut buffers_builder = BuffersBuilder::new(
995 &mut buffers,
996 VertexPositions {
997 fill: to_color_or_gradient_coord(&mark.fill, &grad_coords),
998 stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
999 top_left: bbox.min.to_array(),
1000 bottom_right: bbox.max.to_array(),
1001 },
1002 );
1003
1004 let mut fill_tessellator = FillTessellator::new();
1006 let fill_options = FillOptions::default().with_tolerance(0.05);
1007 fill_tessellator.tessellate_path(&path, &fill_options, &mut buffers_builder)?;
1008
1009 if mark.stroke_width > 0.0 {
1011 let mut stroke_tessellator = StrokeTessellator::new();
1012 let stroke_options = StrokeOptions::default()
1013 .with_tolerance(0.05)
1014 .with_line_join(match mark.stroke_join {
1015 StrokeJoin::Miter => LineJoin::Miter,
1016 StrokeJoin::Round => LineJoin::Round,
1017 StrokeJoin::Bevel => LineJoin::Bevel,
1018 })
1019 .with_line_cap(match mark.stroke_cap {
1020 StrokeCap::Butt => LineCap::Butt,
1021 StrokeCap::Round => LineCap::Round,
1022 StrokeCap::Square => LineCap::Square,
1023 })
1024 .with_line_width(mark.stroke_width);
1025 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut buffers_builder)?;
1026 }
1027
1028 let start_ind = self.num_indices();
1029 let indices_range = (start_ind as u32)..((start_ind + buffers.indices.len()) as u32);
1030
1031 let batch = MultiMarkBatch {
1032 indices_range,
1033 clip: clip.maybe_clip(mark.clip),
1034 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1035 image_atlas_index: None,
1036 gradient_atlas_index,
1037 text_atlas_index: None,
1038 };
1039
1040 self.verts_inds.push((buffers.vertices, buffers.indices));
1041 self.batches.push(batch);
1042 Ok(())
1043 }
1044
1045 #[tracing::instrument(skip_all)]
1046 pub fn add_trail_mark(
1047 &mut self,
1048 mark: &TrailMark,
1049 origin: [f32; 2],
1050 clip: &Clip,
1051 ) -> Result<(), AvengerWgpuError> {
1052 let (gradient_atlas_index, grad_coords) = self
1053 .gradient_atlas_builder
1054 .register_gradients(&mark.gradients);
1055
1056 let size_idx: AttributeIndex = 0;
1057 let mut path_builder = lyon::path::Path::builder_with_attributes(1);
1058 let mut path_len = 0;
1059 for (x, y, size, defined) in izip!(
1060 mark.x_iter(),
1061 mark.y_iter(),
1062 mark.size_iter(),
1063 mark.defined_iter()
1064 ) {
1065 if *defined {
1066 if path_len > 0 {
1067 path_builder
1069 .line_to(lyon::geom::point(*x + origin[0], *y + origin[1]), &[*size]);
1070 } else {
1071 path_builder.begin(lyon::geom::point(*x + origin[0], *y + origin[1]), &[*size]);
1073 }
1074 path_len += 1;
1075 } else {
1076 if path_len == 1 {
1077 path_builder.end(true);
1080 } else {
1081 path_builder.end(false);
1082 }
1083 path_len = 0;
1084 }
1085 }
1086 path_builder.end(false);
1087
1088 let path = path_builder.build();
1091 let bbox = bounding_box(&path);
1092
1093 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
1095 let mut buffers_builder = BuffersBuilder::new(
1096 &mut buffers,
1097 VertexPositions {
1098 fill: [0.0, 0.0, 0.0, 0.0],
1099 stroke: to_color_or_gradient_coord(&mark.stroke, &grad_coords),
1100 top_left: bbox.min.to_array(),
1101 bottom_right: bbox.max.to_array(),
1102 },
1103 );
1104
1105 let mut stroke_tessellator = StrokeTessellator::new();
1107 let stroke_options = StrokeOptions::default()
1108 .with_tolerance(0.05)
1109 .with_line_join(LineJoin::Round)
1110 .with_line_cap(LineCap::Round)
1111 .with_variable_line_width(size_idx);
1112 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut buffers_builder)?;
1113
1114 let start_ind = self.num_indices();
1115 let indices_range = (start_ind as u32)..((start_ind + buffers.indices.len()) as u32);
1116
1117 let batch = MultiMarkBatch {
1118 indices_range,
1119 clip: clip.maybe_clip(mark.clip),
1120 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1121 image_atlas_index: None,
1122 gradient_atlas_index,
1123 text_atlas_index: None,
1124 };
1125
1126 self.verts_inds.push((buffers.vertices, buffers.indices));
1127 self.batches.push(batch);
1128 Ok(())
1129 }
1130
1131 #[tracing::instrument(skip_all)]
1132 pub fn add_arc_mark(
1133 &mut self,
1134 mark: &ArcMark,
1135 origin: [f32; 2],
1136 clip: &Clip,
1137 ) -> Result<(), AvengerWgpuError> {
1138 let (gradient_atlas_index, grad_coords) = self
1139 .gradient_atlas_builder
1140 .register_gradients(&mark.gradients);
1141
1142 let verts_inds = izip!(
1143 mark.x_iter(),
1144 mark.y_iter(),
1145 mark.start_angle_iter(),
1146 mark.end_angle_iter(),
1147 mark.outer_radius_iter(),
1148 mark.inner_radius_iter(),
1149 mark.fill_iter(),
1150 mark.stroke_iter(),
1151 mark.stroke_width_iter(),
1152 )
1153 .map(
1154 |(
1155 x,
1156 y,
1157 start_angle,
1158 end_angle,
1159 outer_radius,
1160 inner_radius,
1161 fill,
1162 stroke,
1163 stroke_width,
1164 )|
1165 -> Result<(Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
1166 let total_angle = end_angle - start_angle;
1168
1169 let (inner_radius, outer_radius) = if *inner_radius > *outer_radius {
1171 (*outer_radius, *inner_radius)
1172 } else {
1173 (*inner_radius, *outer_radius)
1174 };
1175
1176 let mut path_builder = lyon::path::Path::builder().with_svg();
1177
1178 path_builder.move_to(lyon::geom::Point::new(0.0, -inner_radius));
1180 path_builder.line_to(lyon::geom::Point::new(0.0, -outer_radius));
1181
1182 path_builder.arc(
1184 lyon::geom::Point::new(0.0, 0.0),
1185 lyon::math::Vector::new(outer_radius, outer_radius),
1186 lyon::geom::Angle::radians(total_angle),
1187 lyon::geom::Angle::radians(0.0),
1188 );
1189
1190 if inner_radius != 0.0 {
1191 let inner_radius_vec = path_builder
1193 .current_position()
1194 .to_vector()
1195 .neg()
1196 .normalize()
1197 .mul(outer_radius - inner_radius);
1198 path_builder.relative_line_to(inner_radius_vec);
1199
1200 path_builder.arc(
1202 lyon::geom::Point::new(0.0, 0.0),
1203 lyon::math::Vector::new(inner_radius, inner_radius),
1204 lyon::geom::Angle::radians(-total_angle),
1205 lyon::geom::Angle::radians(0.0),
1206 );
1207 } else {
1208 path_builder.line_to(lyon::geom::Point::new(0.0, 0.0));
1210 }
1211
1212 path_builder.close();
1213 let path = path_builder.build();
1214
1215 let path = path.transformed(
1217 &PathTransform::rotation(lyon::geom::Angle::radians(*start_angle))
1218 .then_translate(Vector2D::new(*x + origin[0], *y + origin[1])),
1219 );
1220
1221 let bbox = bounding_box(&path);
1223
1224 let mut buffers: VertexBuffers<MultiVertex, u32> = VertexBuffers::new();
1226 let mut builder = BuffersBuilder::new(
1227 &mut buffers,
1228 VertexPositions {
1229 fill: to_color_or_gradient_coord(fill, &grad_coords),
1230 stroke: to_color_or_gradient_coord(stroke, &grad_coords),
1231 top_left: bbox.min.to_array(),
1232 bottom_right: bbox.max.to_array(),
1233 },
1234 );
1235
1236 let mut fill_tessellator = FillTessellator::new();
1238 let fill_options = FillOptions::default().with_tolerance(0.05);
1239 fill_tessellator.tessellate_path(&path, &fill_options, &mut builder)?;
1240
1241 if *stroke_width > 0.0 {
1243 let mut stroke_tessellator = StrokeTessellator::new();
1244 let stroke_options = StrokeOptions::default()
1245 .with_tolerance(0.05)
1246 .with_line_join(LineJoin::Miter)
1247 .with_line_cap(LineCap::Butt)
1248 .with_line_width(*stroke_width);
1249 stroke_tessellator.tessellate_path(&path, &stroke_options, &mut builder)?;
1250 }
1251
1252 Ok((buffers.vertices, buffers.indices))
1253 },
1254 )
1255 .collect::<Result<Vec<_>, AvengerWgpuError>>()?;
1256
1257 let start_ind = self.num_indices();
1258 let inds_len: usize = verts_inds.iter().map(|(_, i)| i.len()).sum();
1259 let indices_range = (start_ind as u32)..((start_ind + inds_len) as u32);
1260
1261 let batch = MultiMarkBatch {
1262 indices_range,
1263 clip: clip.maybe_clip(mark.clip),
1264 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1265 image_atlas_index: None,
1266 gradient_atlas_index,
1267 text_atlas_index: None,
1268 };
1269
1270 self.verts_inds.extend(verts_inds);
1271 self.batches.push(batch);
1272 Ok(())
1273 }
1274
1275 #[tracing::instrument(skip_all)]
1276 pub fn add_image_mark(
1277 &mut self,
1278 mark: &ImageMark,
1279 origin: [f32; 2],
1280 clip: &Clip,
1281 ) -> Result<(), AvengerWgpuError> {
1282 let verts_inds = izip!(
1283 mark.image_iter(),
1284 mark.x_iter(),
1285 mark.y_iter(),
1286 mark.width_iter(),
1287 mark.height_iter(),
1288 mark.baseline_iter(),
1289 mark.align_iter(),
1290 ).map(|(img, x, y, width, height, baseline, align)| -> Result<(usize, Vec<MultiVertex>, Vec<u32>), AvengerWgpuError> {
1291 let x = *x + origin[0];
1292 let y = *y + origin[1];
1293
1294 let Some(rgba_image) = img.to_image() else {
1295 return Err(AvengerWgpuError::ConversionError("Failed to convert raw image to rgba image".to_string()))
1296 };
1297
1298 let (atlas_index, tex_coords) = self.image_atlas_builder.register_image(&rgba_image)?;
1299
1300 let left = match *align {
1302 ImageAlign::Left => x,
1303 ImageAlign::Center => x - *width / 2.0,
1304 ImageAlign::Right => x - *width,
1305 };
1306
1307 let top = match *baseline {
1309 ImageBaseline::Top => y,
1310 ImageBaseline::Middle => y - *height / 2.0,
1311 ImageBaseline::Bottom => y - *height,
1312 };
1313
1314 let (left, top, width, height) = if mark.aspect {
1316 let img_aspect = img.width as f32 / img.height as f32;
1317 let outline_aspect = *width / *height;
1318 if img_aspect > outline_aspect {
1319 let aspect_height = *width / img_aspect;
1322 let aspect_top = top + (*height - aspect_height) / 2.0;
1323 (left, aspect_top, *width, aspect_height)
1324 } else if img_aspect < outline_aspect {
1325 let aspect_width = *height * img_aspect;
1328 let aspect_left = left + (*width - aspect_width) / 2.0;
1329 (aspect_left, top, aspect_width, *height)
1330 } else {
1331 (left, top, *width, *height)
1332 }
1333 } else {
1334 (left, top, *width, *height)
1335 };
1336
1337 let top_left = [top, left];
1338 let bottom_right = [top + height, left + width];
1339 let verts = vec![
1340 MultiVertex {
1342 color: [IMAGE_TEXTURE_CODE, tex_coords.x0, tex_coords.y0, 0.0],
1343 position: [left, top],
1344 top_left,
1345 bottom_right,
1346 },
1347 MultiVertex {
1349 color: [IMAGE_TEXTURE_CODE, tex_coords.x0, tex_coords.y1, 0.0],
1350 position: [left, top + height],
1351 top_left,
1352 bottom_right,
1353 },
1354 MultiVertex {
1356 color: [IMAGE_TEXTURE_CODE, tex_coords.x1, tex_coords.y1, 0.0],
1357 position: [left + width, top + height],
1358 top_left,
1359 bottom_right,
1360 },
1361 MultiVertex {
1363 color: [IMAGE_TEXTURE_CODE, tex_coords.x1, tex_coords.y0, 0.0],
1364 position: [left + width, top],
1365 top_left,
1366 bottom_right,
1367 },
1368 ];
1369 let indices: Vec<u32> = vec![0, 1, 2, 0, 2, 3];
1370 Ok((atlas_index, verts, indices))
1371 }).collect::<Result<Vec<_>, AvengerWgpuError>>()?;
1372
1373 let start_ind = self.num_indices() as u32;
1375 let mut next_batch = MultiMarkBatch {
1376 indices_range: start_ind..start_ind,
1377 clip: clip.maybe_clip(mark.clip),
1378 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1379 image_atlas_index: None,
1380 gradient_atlas_index: None,
1381 text_atlas_index: None,
1382 };
1383
1384 for (atlas_index, verts, inds) in verts_inds {
1385 if next_batch.image_atlas_index.unwrap_or(atlas_index) == atlas_index {
1386 next_batch.image_atlas_index = Some(atlas_index);
1388 next_batch.indices_range = next_batch.indices_range.start
1389 ..(next_batch.indices_range.end + inds.len() as u32);
1390 } else {
1391 let start_ind = next_batch.indices_range.end;
1393 let mut full_batch = MultiMarkBatch {
1395 indices_range: start_ind..(start_ind + inds.len() as u32),
1396 clip: clip.maybe_clip(mark.clip),
1397 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1398 image_atlas_index: Some(atlas_index),
1399 gradient_atlas_index: None,
1400 text_atlas_index: None,
1401 };
1402 std::mem::swap(&mut full_batch, &mut next_batch);
1403 self.batches.push(full_batch);
1404 }
1405
1406 self.verts_inds.push((verts, inds))
1408 }
1409
1410 self.batches.push(next_batch);
1411 Ok(())
1412 }
1413
1414 #[tracing::instrument(skip_all)]
1415 pub fn add_text_mark(
1416 &mut self,
1417 mark: &TextMark,
1418 origin: [f32; 2],
1419 clip: &Clip,
1420 ) -> Result<(), AvengerWgpuError> {
1421 let registrations = izip!(
1422 mark.text_iter(),
1423 mark.x_iter(),
1424 mark.y_iter(),
1425 mark.color_iter(),
1426 mark.align_iter(),
1427 mark.angle_iter(),
1428 mark.baseline_iter(),
1429 mark.font_iter(),
1430 mark.font_size_iter(),
1431 mark.font_weight_iter(),
1432 mark.font_style_iter(),
1433 mark.limit_iter(),
1434 )
1435 .map(
1436 |(
1437 text,
1438 x,
1439 y,
1440 color,
1441 align,
1442 angle,
1443 baseline,
1444 font,
1445 font_size,
1446 font_weight,
1447 font_style,
1448 limit,
1449 )| {
1450 let instance = TextInstance {
1451 text,
1452 position: [*x + origin[0], *y + origin[1]],
1453 color,
1454 align,
1455 angle: *angle,
1456 baseline,
1457 font,
1458 font_size: *font_size,
1459 font_weight,
1460 font_style,
1461 limit: *limit,
1462 };
1463 self.text_atlas_builder
1464 .register_text(instance, self.dimensions)
1465 },
1466 )
1467 .collect::<Result<Vec<_>, AvengerWgpuError>>()?
1468 .into_iter()
1469 .flatten()
1470 .collect::<Vec<_>>();
1471
1472 let start_ind = self.num_indices() as u32;
1474 let mut next_batch = MultiMarkBatch {
1475 indices_range: start_ind..start_ind,
1476 clip: clip.maybe_clip(mark.clip),
1477 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1478 image_atlas_index: None,
1479 gradient_atlas_index: None,
1480 text_atlas_index: None,
1481 };
1482
1483 for registration in registrations {
1484 let atlas_index = registration.atlas_index;
1485 let verts = registration.verts;
1486 let inds = registration.indices;
1487
1488 if next_batch.text_atlas_index.unwrap_or(atlas_index) == atlas_index {
1490 next_batch.text_atlas_index = Some(atlas_index);
1492 next_batch.indices_range = next_batch.indices_range.start
1493 ..(next_batch.indices_range.end + inds.len() as u32);
1494 } else {
1495 let start_ind = next_batch.indices_range.end;
1497 let mut full_batch = MultiMarkBatch {
1499 indices_range: start_ind..(start_ind + inds.len() as u32),
1500 clip: clip.maybe_clip(mark.clip),
1501 clip_indices_range: self.add_clip_path(clip, mark.clip)?,
1502 image_atlas_index: None,
1503 gradient_atlas_index: None,
1504 text_atlas_index: Some(atlas_index),
1505 };
1506 std::mem::swap(&mut full_batch, &mut next_batch);
1507 self.batches.push(full_batch);
1508 }
1509
1510 self.verts_inds.push((verts, inds))
1512 }
1513
1514 self.batches.push(next_batch);
1515 Ok(())
1516 }
1517
1518 fn num_indices(&self) -> usize {
1519 self.verts_inds.iter().map(|(_, inds)| inds.len()).sum()
1520 }
1521
1522 fn num_clip_indices(&self) -> usize {
1523 self.clip_verts_inds
1524 .iter()
1525 .map(|(_, inds)| inds.len())
1526 .sum()
1527 }
1528
1529 #[tracing::instrument(skip_all)]
1530 pub fn render(
1531 &self,
1532 device: &Device,
1533 queue: &Queue,
1534 texture_format: TextureFormat,
1535 sample_count: u32,
1536 texture_view: &TextureView,
1537 resolve_target: Option<&TextureView>,
1538 ) -> CommandBuffer {
1539 let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1541 label: Some("Multi Uniform Buffer"),
1542 contents: bytemuck::cast_slice(&[self.uniform]),
1543 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1544 });
1545
1546 let uniform_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1547 entries: &[wgpu::BindGroupLayoutEntry {
1548 binding: 0,
1549 visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
1550 ty: wgpu::BindingType::Buffer {
1551 ty: wgpu::BufferBindingType::Uniform,
1552 has_dynamic_offset: false,
1553 min_binding_size: None,
1554 },
1555 count: None,
1556 }],
1557 label: Some("chart_uniform_layout"),
1558 });
1559
1560 let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1561 layout: &uniform_layout,
1562 entries: &[wgpu::BindGroupEntry {
1563 binding: 0,
1564 resource: uniform_buffer.as_entire_binding(),
1565 }],
1566 label: Some("uniform_bind_group"),
1567 });
1568
1569 let (grad_texture_size, grad_images) = self.gradient_atlas_builder.build();
1571 let (gradient_layout, gradient_texture_bind_groups) = Self::make_texture_bind_groups(
1572 device,
1573 queue,
1574 grad_texture_size,
1575 &grad_images,
1576 wgpu::FilterMode::Nearest,
1577 wgpu::FilterMode::Nearest,
1578 );
1579
1580 let (image_texture_size, image_images) = self.image_atlas_builder.build();
1582 let (image_layout, image_texture_bind_groups) = Self::make_texture_bind_groups(
1583 device,
1584 queue,
1585 image_texture_size,
1586 &image_images,
1587 wgpu::FilterMode::Linear,
1588 wgpu::FilterMode::Linear,
1589 );
1590
1591 let (text_texture_size, text_images) = self.text_atlas_builder.build();
1593 let (text_layout, text_texture_bind_groups) = Self::make_texture_bind_groups(
1594 device,
1595 queue,
1596 text_texture_size,
1597 &text_images,
1598 wgpu::FilterMode::Linear,
1599 wgpu::FilterMode::Linear,
1600 );
1601
1602 let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
1604 label: Some("Shader"),
1605 source: wgpu::ShaderSource::Wgsl(include_str!("multi.wgsl").into()),
1606 });
1607
1608 let render_pipeline_layout =
1609 device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
1610 label: Some("Render Pipeline Layout"),
1611 bind_group_layouts: &[
1612 &uniform_layout,
1613 &gradient_layout,
1614 &image_layout,
1615 &text_layout,
1616 ],
1617 push_constant_ranges: &[],
1618 });
1619
1620 let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1621 label: Some("Render Pipeline"),
1622 layout: Some(&render_pipeline_layout),
1623 vertex: wgpu::VertexState {
1624 module: &shader,
1625 entry_point: "vs_main",
1626 compilation_options: Default::default(),
1627 buffers: &[MultiVertex::desc()],
1628 },
1629 fragment: Some(wgpu::FragmentState {
1630 module: &shader,
1631 entry_point: "fs_main",
1632 compilation_options: Default::default(),
1633 targets: &[Some(wgpu::ColorTargetState {
1634 format: texture_format,
1635 blend: Some(wgpu::BlendState::ALPHA_BLENDING),
1636 write_mask: wgpu::ColorWrites::ALL,
1637 })],
1638 }),
1639 primitive: wgpu::PrimitiveState {
1640 topology: wgpu::PrimitiveTopology::TriangleList,
1641 strip_index_format: None,
1642 front_face: wgpu::FrontFace::Ccw,
1643 cull_mode: Some(wgpu::Face::Back),
1644 polygon_mode: wgpu::PolygonMode::Fill,
1645 unclipped_depth: false,
1646 conservative: false,
1647 },
1648 depth_stencil: Some(wgpu::DepthStencilState {
1649 format: wgpu::TextureFormat::Stencil8,
1650 depth_write_enabled: false,
1651 depth_compare: wgpu::CompareFunction::Always,
1652 stencil: wgpu::StencilState {
1653 front: wgpu::StencilFaceState {
1654 compare: wgpu::CompareFunction::LessEqual,
1656 ..Default::default()
1657 },
1658 back: wgpu::StencilFaceState::IGNORE,
1659 read_mask: !0,
1660 write_mask: !0,
1661 },
1662 bias: Default::default(),
1663 }),
1664 multisample: wgpu::MultisampleState {
1665 count: sample_count,
1666 mask: !0,
1667 alpha_to_coverage_enabled: false,
1668 },
1669 multiview: None,
1670 });
1671
1672 let stencil_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
1673 label: None,
1674 layout: Some(&render_pipeline_layout),
1675 vertex: wgpu::VertexState {
1676 module: &shader,
1677 entry_point: "vs_main",
1678 compilation_options: Default::default(),
1679 buffers: &[MultiVertex::desc()],
1680 },
1681 fragment: Some(wgpu::FragmentState {
1682 module: &shader,
1683 entry_point: "fs_main",
1684 compilation_options: Default::default(),
1685 targets: &[Some(wgpu::ColorTargetState {
1686 format: texture_format,
1687 blend: None,
1688 write_mask: wgpu::ColorWrites::empty(),
1689 })],
1690 }),
1691 primitive: Default::default(),
1692 depth_stencil: Some(wgpu::DepthStencilState {
1693 format: wgpu::TextureFormat::Stencil8,
1694 depth_write_enabled: false,
1695 depth_compare: wgpu::CompareFunction::Always,
1696 stencil: wgpu::StencilState {
1697 front: wgpu::StencilFaceState {
1698 compare: wgpu::CompareFunction::Always,
1699 pass_op: wgpu::StencilOperation::Replace,
1700 ..Default::default()
1701 },
1702 back: wgpu::StencilFaceState::IGNORE,
1703 read_mask: !0,
1704 write_mask: !0,
1705 },
1706 bias: Default::default(),
1707 }),
1708 multisample: wgpu::MultisampleState {
1709 count: sample_count,
1710 mask: !0,
1711 alpha_to_coverage_enabled: false,
1712 },
1713 multiview: None,
1714 });
1715
1716 let stencil_buffer = device.create_texture(&wgpu::TextureDescriptor {
1717 label: Some("Stencil buffer"),
1718 size: Extent3d {
1719 width: self.dimensions.to_physical_width(),
1720 height: self.dimensions.to_physical_height(),
1721 depth_or_array_layers: 1,
1722 },
1723 mip_level_count: 1,
1724 sample_count,
1725 dimension: wgpu::TextureDimension::D2,
1726 format: wgpu::TextureFormat::Stencil8,
1727 view_formats: &[],
1728 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
1729 });
1730
1731 let num_verts: usize = self.verts_inds.iter().map(|(v, _)| v.len()).sum();
1733 let num_inds: usize = self.verts_inds.iter().map(|(_, inds)| inds.len()).sum();
1734 let mut verticies: Vec<MultiVertex> = Vec::with_capacity(num_verts);
1735 let mut indices: Vec<u32> = Vec::with_capacity(num_inds);
1736
1737 for (vs, inds) in &self.verts_inds {
1738 let offset = verticies.len() as u32;
1739 indices.extend(inds.iter().map(|i| *i + offset));
1740 verticies.extend(vs);
1741 }
1742
1743 let num_clip_verts = self.clip_verts_inds.iter().map(|(v, _)| v.len()).sum();
1744 let num_clip_inds = self
1745 .clip_verts_inds
1746 .iter()
1747 .map(|(_, inds)| inds.len())
1748 .sum();
1749 let mut clip_verticies: Vec<MultiVertex> = Vec::with_capacity(num_clip_verts);
1750 let mut clip_indices: Vec<u32> = Vec::with_capacity(num_clip_inds);
1751
1752 for (vs, inds) in &self.clip_verts_inds {
1753 let offset = clip_verticies.len() as u32;
1754 clip_indices.extend(inds.iter().map(|i| *i + offset));
1755 clip_verticies.extend(vs);
1756 }
1757
1758 let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1760 label: Some("Vertex Buffer"),
1761 contents: bytemuck::cast_slice(verticies.as_slice()),
1762 usage: wgpu::BufferUsages::VERTEX,
1763 });
1764
1765 let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1766 label: Some("Index Buffer"),
1767 contents: bytemuck::cast_slice(indices.as_slice()),
1768 usage: wgpu::BufferUsages::INDEX,
1769 });
1770
1771 let clip_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1772 label: Some("Clip Vertex Buffer"),
1773 contents: bytemuck::cast_slice(clip_verticies.as_slice()),
1774 usage: wgpu::BufferUsages::VERTEX,
1775 });
1776
1777 let clip_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
1778 label: Some("Clip Index Buffer"),
1779 contents: bytemuck::cast_slice(clip_indices.as_slice()),
1780 usage: wgpu::BufferUsages::INDEX,
1781 });
1782
1783 let mut mark_encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
1785 label: Some("Multi Mark Render Encoder"),
1786 });
1787
1788 {
1790 let depth_view = stencil_buffer.create_view(&Default::default());
1791 let mut render_pass = mark_encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
1792 label: Some("Multi Mark Render Pass"),
1793 color_attachments: &[Some(wgpu::RenderPassColorAttachment {
1794 view: texture_view,
1795 resolve_target,
1796 ops: wgpu::Operations {
1797 load: wgpu::LoadOp::Load,
1798 store: wgpu::StoreOp::Store,
1799 },
1800 })],
1801 depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
1802 view: &depth_view,
1803 depth_ops: if cfg!(feature = "deno") {
1804 Some(wgpu::Operations {
1808 load: wgpu::LoadOp::Clear(0.0),
1809 store: wgpu::StoreOp::Discard,
1810 })
1811 } else {
1812 None
1813 },
1814 stencil_ops: Some(wgpu::Operations {
1815 load: wgpu::LoadOp::Clear(0),
1816 store: wgpu::StoreOp::Store,
1817 }),
1818 }),
1819 occlusion_query_set: None,
1820 timestamp_writes: None,
1821 });
1822
1823 render_pass.set_pipeline(&render_pipeline);
1824 render_pass.set_bind_group(0, &uniform_bind_group, &[]);
1825 let mut last_grad_ind = 0;
1826 let mut last_img_ind = 0;
1827 let mut last_text_ind = 0;
1828 let mut stencil_index: u32 = 1;
1829 render_pass.set_bind_group(1, &gradient_texture_bind_groups[last_grad_ind], &[]);
1830 render_pass.set_bind_group(2, &image_texture_bind_groups[last_img_ind], &[]);
1831 render_pass.set_bind_group(3, &text_texture_bind_groups[last_img_ind], &[]);
1832 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1833 render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1834
1835 for batch in &self.batches {
1837 if let Some(clip_inds_range) = &batch.clip_indices_range {
1838 render_pass.set_stencil_reference(stencil_index);
1839 render_pass.set_pipeline(&stencil_pipeline);
1840 render_pass.set_vertex_buffer(0, clip_vertex_buffer.slice(..));
1841 render_pass
1842 .set_index_buffer(clip_index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1843 render_pass.draw_indexed(clip_inds_range.clone(), 0, 0..1);
1844
1845 render_pass.set_pipeline(&render_pipeline);
1847 render_pass.set_vertex_buffer(0, vertex_buffer.slice(..));
1848 render_pass.set_index_buffer(index_buffer.slice(..), wgpu::IndexFormat::Uint32);
1849
1850 stencil_index += 1;
1852 } else {
1853 render_pass.set_stencil_reference(0);
1855 }
1856
1857 if let Clip::Rect {
1859 x,
1860 y,
1861 width,
1862 height,
1863 } = batch.clip
1864 {
1865 render_pass.set_scissor_rect(
1867 (x * self.uniform.scale) as u32,
1868 (y * self.uniform.scale) as u32,
1869 (width * self.uniform.scale) as u32,
1870 (height * self.uniform.scale) as u32,
1871 );
1872 } else {
1873 render_pass.set_scissor_rect(
1875 0,
1876 0,
1877 self.dimensions.to_physical_width(),
1878 self.dimensions.to_physical_height(),
1879 );
1880 }
1881
1882 if let Some(grad_ind) = batch.gradient_atlas_index {
1884 if grad_ind != last_grad_ind {
1885 render_pass.set_bind_group(1, &gradient_texture_bind_groups[grad_ind], &[]);
1886 last_grad_ind = grad_ind;
1887 }
1888 }
1889
1890 if let Some(img_ind) = batch.image_atlas_index {
1891 if img_ind != last_img_ind {
1892 render_pass.set_bind_group(2, &image_texture_bind_groups[img_ind], &[]);
1893 }
1894 last_img_ind = img_ind;
1895 }
1896
1897 if let Some(text_ind) = batch.text_atlas_index {
1898 if text_ind != last_text_ind {
1899 render_pass.set_bind_group(3, &text_texture_bind_groups[text_ind], &[]);
1900 }
1901 last_text_ind = text_ind;
1902 }
1903
1904 render_pass.draw_indexed(batch.indices_range.clone(), 0, 0..1);
1906 }
1907 }
1908
1909 mark_encoder.finish()
1910 }
1911
1912 fn make_texture_bind_groups(
1913 device: &Device,
1914 queue: &Queue,
1915 size: Extent3d,
1916 images: &[DynamicImage],
1917 mag_filter: wgpu::FilterMode,
1918 min_filter: wgpu::FilterMode,
1919 ) -> (BindGroupLayout, Vec<BindGroup>) {
1920 let mut texture_bind_groups: Vec<BindGroup> = Vec::new();
1922
1923 let texture_bind_group_layout =
1925 device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
1926 entries: &[
1927 wgpu::BindGroupLayoutEntry {
1928 binding: 0,
1929 visibility: wgpu::ShaderStages::FRAGMENT,
1930 ty: wgpu::BindingType::Texture {
1931 multisampled: false,
1932 view_dimension: wgpu::TextureViewDimension::D2,
1933 sample_type: wgpu::TextureSampleType::Float { filterable: true },
1934 },
1935 count: None,
1936 },
1937 wgpu::BindGroupLayoutEntry {
1938 binding: 1,
1939 visibility: wgpu::ShaderStages::FRAGMENT,
1940 ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
1943 count: None,
1944 },
1945 ],
1946 label: Some("texture_bind_group_layout"),
1947 });
1948
1949 for image in images {
1950 let texture = device.create_texture(&wgpu::TextureDescriptor {
1952 size,
1953 mip_level_count: 1,
1954 sample_count: 1,
1955 dimension: wgpu::TextureDimension::D2,
1956 format: wgpu::TextureFormat::Rgba8Unorm,
1957 usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
1958 label: Some("diffuse_texture"),
1959 view_formats: &[],
1960 });
1961 let texture_view = texture.create_view(&wgpu::TextureViewDescriptor::default());
1962
1963 let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
1965 address_mode_u: wgpu::AddressMode::ClampToEdge,
1966 address_mode_v: wgpu::AddressMode::ClampToEdge,
1967 address_mode_w: wgpu::AddressMode::ClampToEdge,
1968 mag_filter,
1969 min_filter,
1970 mipmap_filter: wgpu::FilterMode::Nearest,
1971 ..Default::default()
1972 });
1973
1974 let texture_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
1975 layout: &texture_bind_group_layout,
1976 entries: &[
1977 wgpu::BindGroupEntry {
1978 binding: 0,
1979 resource: wgpu::BindingResource::TextureView(&texture_view),
1980 },
1981 wgpu::BindGroupEntry {
1982 binding: 1,
1983 resource: wgpu::BindingResource::Sampler(&sampler),
1984 },
1985 ],
1986 label: Some("texture_bind_group"),
1987 });
1988
1989 queue.write_texture(
1990 wgpu::ImageCopyTexture {
1992 texture: &texture,
1993 mip_level: 0,
1994 origin: wgpu::Origin3d::ZERO,
1995 aspect: wgpu::TextureAspect::All,
1996 },
1997 image.to_rgba8().as_raw(),
1999 wgpu::ImageDataLayout {
2001 offset: 0,
2002 bytes_per_row: Some(4 * image.width()),
2003 rows_per_image: Some(image.height()),
2004 },
2005 size,
2006 );
2007
2008 texture_bind_groups.push(texture_bind_group);
2009 }
2010
2011 (texture_bind_group_layout, texture_bind_groups)
2012 }
2013}
2014
2015pub struct VertexPositions {
2016 fill: [f32; 4],
2017 stroke: [f32; 4],
2018 top_left: [f32; 2],
2019 bottom_right: [f32; 2],
2020}
2021
2022impl FillVertexConstructor<MultiVertex> for VertexPositions {
2023 fn new_vertex(&mut self, vertex: FillVertex) -> MultiVertex {
2024 MultiVertex {
2025 position: [vertex.position().x, vertex.position().y],
2026 color: self.fill,
2027 top_left: self.top_left,
2028 bottom_right: self.bottom_right,
2029 }
2030 }
2031}
2032
2033impl StrokeVertexConstructor<MultiVertex> for VertexPositions {
2034 fn new_vertex(&mut self, vertex: StrokeVertex) -> MultiVertex {
2035 MultiVertex {
2036 position: [vertex.position().x, vertex.position().y],
2037 color: self.stroke,
2038 top_left: self.top_left,
2039 bottom_right: self.bottom_right,
2040 }
2041 }
2042}
2043
2044pub struct SymbolVertexPositions {
2046 scale: f32,
2047}
2048
2049impl FillVertexConstructor<SymbolVertex> for SymbolVertexPositions {
2050 fn new_vertex(&mut self, vertex: FillVertex) -> SymbolVertex {
2051 SymbolVertex {
2052 position: [vertex.position().x, vertex.position().y].into(),
2053 normal: None,
2054 scale: self.scale,
2055 }
2056 }
2057}
2058
2059impl StrokeVertexConstructor<SymbolVertex> for SymbolVertexPositions {
2060 fn new_vertex(&mut self, vertex: StrokeVertex) -> SymbolVertex {
2061 SymbolVertex {
2062 position: [vertex.position().x, vertex.position().y].into(),
2063 normal: Some(vertex.normal()),
2064 scale: self.scale,
2065 }
2066 }
2067}
2068
2069pub struct SymbolVertex {
2070 position: Point2D<f32, UnknownUnit>,
2071 normal: Option<Vector2D<f32, UnknownUnit>>,
2072 scale: f32,
2073}
2074
2075impl SymbolVertex {
2076 #[allow(clippy::too_many_arguments)]
2077 pub fn as_multi_vertex(
2078 &self,
2079 size: f32,
2080 x: f32,
2081 y: f32,
2082 angle: f32,
2083 fill: [f32; 4],
2084 stroke: [f32; 4],
2085 line_width: f32,
2086 ) -> MultiVertex {
2087 let angle = Angle::degrees(angle);
2088 let absolue_scale = size.sqrt();
2089 let relative_scale = absolue_scale / self.scale;
2090
2091 let mut transform = PathTransform::scale(relative_scale, relative_scale);
2093
2094 let color = if let Some(normal) = self.normal {
2097 let scaled_line_width = relative_scale * NORMALIZED_SYMBOL_STROKE_WIDTH;
2098 let line_width_adjustment = normal.mul((line_width - scaled_line_width) / 2.0);
2099 transform = transform.then_translate(line_width_adjustment);
2100 stroke
2101 } else {
2102 fill
2103 };
2104
2105 transform = transform
2107 .then_rotate(angle)
2108 .then_translate(Vector2D::new(x, y));
2109 let position = transform.transform_point(self.position);
2110
2111 MultiVertex {
2112 position: position.to_array(),
2113 color,
2114 top_left: [x - absolue_scale / 2.0, y - absolue_scale / 2.0],
2115 bottom_right: [x + absolue_scale / 2.0, y + absolue_scale / 2.0],
2116 }
2117 }
2118}