use crate::canvas::CanvasDimensions;
use crate::marks::gradient::{build_gradients_image, to_color_or_gradient_coord};
use crate::marks::instanced_mark::{InstancedMarkBatch, InstancedMarkShader};
use avenger::marks::group::GroupBounds;
use avenger::marks::rule::RuleMark;
use avenger::marks::value::StrokeCap;
use image::DynamicImage;
use itertools::izip;
use wgpu::{Extent3d, VertexBufferLayout};
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RuleUniform {
pub size: [f32; 2],
pub origin: [f32; 2],
pub group_size: [f32; 2],
pub scale: f32,
pub clip: f32,
}
impl RuleUniform {
pub fn new(dimensions: CanvasDimensions, group_bounds: GroupBounds, clip: bool) -> Self {
Self {
size: dimensions.size,
scale: dimensions.scale,
origin: [group_bounds.x, group_bounds.y],
group_size: [
group_bounds.width.unwrap_or(0.0),
group_bounds.height.unwrap_or(0.0),
],
clip: if clip && group_bounds.width.is_some() && group_bounds.height.is_some() {
1.0
} else {
0.0
},
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RuleVertex {
pub position: [f32; 2],
}
const VERTEX_ATTRIBUTES: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![
0 => Float32x2, ];
impl RuleVertex {
pub fn desc() -> VertexBufferLayout<'static> {
VertexBufferLayout {
array_stride: std::mem::size_of::<RuleVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &VERTEX_ATTRIBUTES,
}
}
}
const STROKE_CAP_BUTT: u32 = 0;
const STROKE_CAP_SQUARE: u32 = 1;
const STROKE_CAP_ROUND: u32 = 2;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct RuleInstance {
pub x0: f32,
pub y0: f32,
pub x1: f32,
pub y1: f32,
pub stroke: [f32; 4],
pub stroke_width: f32,
pub stroke_cap: u32,
}
const INSTANCE_ATTRIBUTES: [wgpu::VertexAttribute; 7] = wgpu::vertex_attr_array![
1 => Float32, 2 => Float32, 3 => Float32, 4 => Float32, 5 => Float32x4, 6 => Float32, 7 => Uint32, ];
impl RuleInstance {
pub fn from_spec(mark: &RuleMark) -> (Vec<RuleInstance>, Option<DynamicImage>, Extent3d) {
let mut instances: Vec<RuleInstance> = Vec::new();
let (img, texture_size) = build_gradients_image(&mark.gradients);
if let Some(stroke_dash_iter) = mark.stroke_dash_iter() {
for (stroke_dash, x0, y0, x1, y1, stroke, stroke_width, cap) in izip!(
stroke_dash_iter,
mark.x0_iter(),
mark.y0_iter(),
mark.x1_iter(),
mark.y1_iter(),
mark.stroke_iter(),
mark.stroke_width_iter(),
mark.stroke_cap_iter(),
) {
let mut dash_idx = 0;
let mut start_dash_dist: f32 = 0.0;
let rule_len = ((x1 - x0).powi(2) + (y1 - y0).powi(2)).sqrt();
let xhat = (x1 - x0) / rule_len;
let yhat = (y1 - y0) / rule_len;
let mut draw = true;
while start_dash_dist < rule_len {
let end_dash_dist = if start_dash_dist + stroke_dash[dash_idx] >= rule_len {
rule_len
} else {
start_dash_dist + stroke_dash[dash_idx]
};
if draw {
let dash_x0 = x0 + xhat * start_dash_dist;
let dash_y0 = y0 + yhat * start_dash_dist;
let dash_x1 = x0 + xhat * end_dash_dist;
let dash_y1 = y0 + yhat * end_dash_dist;
instances.push(RuleInstance {
x0: dash_x0,
y0: dash_y0,
x1: dash_x1,
y1: dash_y1,
stroke: to_color_or_gradient_coord(stroke, texture_size),
stroke_width: *stroke_width,
stroke_cap: match cap {
StrokeCap::Butt => STROKE_CAP_BUTT,
StrokeCap::Square => STROKE_CAP_SQUARE,
StrokeCap::Round => STROKE_CAP_ROUND,
},
})
}
start_dash_dist = end_dash_dist;
dash_idx = (dash_idx + 1) % stroke_dash.len();
draw = !draw;
}
}
} else {
for (x0, y0, x1, y1, stroke, stroke_width, cap) in izip!(
mark.x0_iter(),
mark.y0_iter(),
mark.x1_iter(),
mark.y1_iter(),
mark.stroke_iter(),
mark.stroke_width_iter(),
mark.stroke_cap_iter(),
) {
instances.push(RuleInstance {
x0: *x0,
y0: *y0,
x1: *x1,
y1: *y1,
stroke: to_color_or_gradient_coord(stroke, texture_size),
stroke_width: *stroke_width,
stroke_cap: match cap {
StrokeCap::Butt => STROKE_CAP_BUTT,
StrokeCap::Square => STROKE_CAP_SQUARE,
StrokeCap::Round => STROKE_CAP_ROUND,
},
})
}
}
(instances, img, texture_size)
}
}
pub struct RuleShader {
verts: Vec<RuleVertex>,
indices: Vec<u16>,
instances: Vec<RuleInstance>,
uniform: RuleUniform,
batches: Vec<InstancedMarkBatch>,
texture_size: Extent3d,
shader: String,
vertex_entry_point: String,
fragment_entry_point: String,
}
impl RuleShader {
pub fn from_rule_mark(
mark: &RuleMark,
dimensions: CanvasDimensions,
group_bounds: GroupBounds,
) -> Self {
let (instances, gradient_image, texture_size) = RuleInstance::from_spec(mark);
let batches = vec![InstancedMarkBatch {
instances_range: 0..instances.len() as u32,
image: gradient_image,
}];
Self {
verts: vec![
RuleVertex {
position: [-0.5, 0.5],
},
RuleVertex {
position: [-0.5, -0.5],
},
RuleVertex {
position: [0.5, -0.5],
},
RuleVertex {
position: [0.5, 0.5],
},
],
indices: vec![0, 1, 2, 0, 2, 3],
instances,
uniform: RuleUniform::new(dimensions, group_bounds, mark.clip),
batches,
texture_size,
shader: format!(
"{}\n{}",
include_str!("rule.wgsl"),
include_str!("gradient.wgsl")
),
vertex_entry_point: "vs_main".to_string(),
fragment_entry_point: "fs_main".to_string(),
}
}
}
impl InstancedMarkShader for RuleShader {
type Instance = RuleInstance;
type Vertex = RuleVertex;
type Uniform = RuleUniform;
fn verts(&self) -> &[Self::Vertex] {
self.verts.as_slice()
}
fn indices(&self) -> &[u16] {
self.indices.as_slice()
}
fn instances(&self) -> &[Self::Instance] {
self.instances.as_slice()
}
fn uniform(&self) -> Self::Uniform {
self.uniform
}
fn batches(&self) -> &[InstancedMarkBatch] {
self.batches.as_slice()
}
fn texture_size(&self) -> Extent3d {
self.texture_size
}
fn shader(&self) -> &str {
self.shader.as_str()
}
fn vertex_entry_point(&self) -> &str {
self.vertex_entry_point.as_str()
}
fn fragment_entry_point(&self) -> &str {
self.fragment_entry_point.as_str()
}
fn instance_desc(&self) -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<RuleInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &INSTANCE_ATTRIBUTES,
}
}
fn vertex_desc(&self) -> VertexBufferLayout<'static> {
RuleVertex::desc()
}
}