use glam::{Mat4, Vec3, Vec4};
use polyscope_core::slice_plane::SlicePlane;
use std::num::NonZeroU64;
use wgpu::util::DeviceExt;
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
#[allow(clippy::pub_underscore_fields)]
pub struct PlaneRenderUniforms {
pub transform: [[f32; 4]; 4],
pub color: [f32; 4],
pub grid_color: [f32; 4],
pub length_scale: f32,
pub plane_size: f32,
pub _padding: [f32; 2],
}
impl Default for PlaneRenderUniforms {
fn default() -> Self {
Self {
transform: Mat4::IDENTITY.to_cols_array_2d(),
color: [0.5, 0.5, 0.5, 1.0],
grid_color: [0.3, 0.3, 0.3, 1.0],
length_scale: 1.0,
plane_size: 0.05,
_padding: [0.0; 2],
}
}
}
fn compute_plane_transform(origin: Vec3, normal: Vec3) -> Mat4 {
let x_axis = normal.normalize();
let up = if x_axis.dot(Vec3::Y).abs() < 0.99 {
Vec3::Y
} else {
Vec3::Z
};
let y_axis = up.cross(x_axis).normalize();
let z_axis = x_axis.cross(y_axis).normalize();
Mat4::from_cols(
Vec4::new(x_axis.x, x_axis.y, x_axis.z, 0.0),
Vec4::new(y_axis.x, y_axis.y, y_axis.z, 0.0),
Vec4::new(z_axis.x, z_axis.y, z_axis.z, 0.0),
Vec4::new(origin.x, origin.y, origin.z, 1.0),
)
}
pub struct SlicePlaneRenderData {
uniform_buffer: wgpu::Buffer,
bind_group: wgpu::BindGroup,
}
impl SlicePlaneRenderData {
#[must_use]
pub fn new(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
camera_buffer: &wgpu::Buffer,
) -> Self {
let uniforms = PlaneRenderUniforms::default();
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Slice Plane Uniform Buffer"),
contents: bytemuck::cast_slice(&[uniforms]),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
});
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Slice Plane Bind Group"),
layout: bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: camera_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: uniform_buffer.as_entire_binding(),
},
],
});
Self {
uniform_buffer,
bind_group,
}
}
pub fn update(&self, queue: &wgpu::Queue, plane: &SlicePlane, length_scale: f32) {
let transform = compute_plane_transform(plane.origin(), plane.normal());
let color = plane.color();
let uniforms = PlaneRenderUniforms {
transform: transform.to_cols_array_2d(),
color: [color.x, color.y, color.z, 1.0],
grid_color: [color.x * 0.6, color.y * 0.6, color.z * 0.6, 1.0],
length_scale,
plane_size: plane.plane_size(),
_padding: [0.0; 2],
};
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[uniforms]));
}
#[must_use]
pub fn bind_group(&self) -> &wgpu::BindGroup {
&self.bind_group
}
pub fn draw<'a>(&'a self, render_pass: &mut wgpu::RenderPass<'a>) {
render_pass.set_bind_group(0, &self.bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
}
#[must_use]
pub fn create_slice_plane_bind_group_layout(device: &wgpu::Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Slice Plane Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(272),
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false,
min_binding_size: NonZeroU64::new(112),
},
count: None,
},
],
})
}
#[must_use]
pub fn create_slice_plane_pipeline(
device: &wgpu::Device,
bind_group_layout: &wgpu::BindGroupLayout,
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
) -> wgpu::RenderPipeline {
let shader_source = include_str!("shaders/slice_plane.wgsl");
let shader_module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Slice Plane Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Slice Plane Pipeline Layout"),
bind_group_layouts: &[bind_group_layout],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Slice Plane Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader_module,
entry_point: Some("vs_main"),
buffers: &[],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &shader_module,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend: Some(wgpu::BlendState {
color: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::SrcAlpha,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
alpha: wgpu::BlendComponent {
src_factor: wgpu::BlendFactor::One,
dst_factor: wgpu::BlendFactor::OneMinusSrcAlpha,
operation: wgpu::BlendOperation::Add,
},
}),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: Default::default(),
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None, polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: Some(wgpu::DepthStencilState {
format: depth_format,
depth_write_enabled: true, depth_compare: wgpu::CompareFunction::LessEqual, stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
})
}