use crate::interaction::gizmo::{GizmoAxis, GizmoMode};
use crate::interaction::snap::ConstraintOverlay;
use crate::interaction::sub_object::SubSelectionRef;
use crate::resources::{CameraUniform, ColormapId};
use crate::scene::material::Material;
pub(super) const INSTANCING_THRESHOLD: usize = 1;
#[derive(Debug, Clone)]
pub(crate) struct InstancedBatch {
pub mesh_id: crate::resources::mesh_store::MeshId,
pub texture_id: Option<u64>,
pub normal_map_id: Option<u64>,
pub ao_map_id: Option<u64>,
pub instance_offset: u32,
pub instance_count: u32,
pub is_transparent: bool,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct ClipPlane {
pub normal: [f32; 3],
pub distance: f32,
pub enabled: bool,
pub cap_color: Option<[f32; 4]>,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum ClipShape {
Plane {
normal: [f32; 3],
distance: f32,
cap_color: Option<[f32; 4]>,
},
Box {
center: [f32; 3],
half_extents: [f32; 3],
orientation: [[f32; 3]; 3],
},
Sphere { center: [f32; 3], radius: f32 },
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ClipObject {
pub shape: ClipShape,
pub color: Option<[f32; 4]>,
pub enabled: bool,
pub extent: f32,
pub hovered: bool,
pub active: bool,
}
impl Default for ClipObject {
fn default() -> Self {
Self {
shape: ClipShape::Plane {
normal: [0.0, 0.0, 1.0],
distance: 0.0,
cap_color: None,
},
color: None,
enabled: true,
extent: 4.5,
hovered: false,
active: false,
}
}
}
impl ClipObject {
pub fn plane(normal: [f32; 3], distance: f32) -> Self {
Self {
shape: ClipShape::Plane {
normal,
distance,
cap_color: None,
},
..Default::default()
}
}
pub fn box_shape(center: [f32; 3], half_extents: [f32; 3], orientation: [[f32; 3]; 3]) -> Self {
Self {
shape: ClipShape::Box {
center,
half_extents,
orientation,
},
..Default::default()
}
}
pub fn sphere(center: [f32; 3], radius: f32) -> Self {
Self {
shape: ClipShape::Sphere { center, radius },
..Default::default()
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ToneMapping {
Reinhard,
#[default]
Aces,
KhronosNeutral,
}
#[derive(Clone, Debug)]
pub struct PostProcessSettings {
pub enabled: bool,
pub tone_mapping: ToneMapping,
pub exposure: f32,
pub ssao: bool,
pub bloom: bool,
pub bloom_threshold: f32,
pub bloom_intensity: f32,
pub fxaa: bool,
pub ssaa_factor: u32,
pub contact_shadows: bool,
pub contact_shadow_max_distance: f32,
pub contact_shadow_steps: u32,
pub contact_shadow_thickness: f32,
}
impl Default for PostProcessSettings {
fn default() -> Self {
Self {
enabled: false,
tone_mapping: ToneMapping::Aces,
exposure: 1.0,
ssao: false,
bloom: false,
bloom_threshold: 1.0,
bloom_intensity: 0.1,
fxaa: false,
ssaa_factor: 1,
contact_shadows: false,
contact_shadow_max_distance: 0.5,
contact_shadow_steps: 16,
contact_shadow_thickness: 0.1,
}
}
}
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub enum LightKind {
Directional {
direction: [f32; 3],
},
Point {
position: [f32; 3],
range: f32,
},
Spot {
position: [f32; 3],
direction: [f32; 3],
range: f32,
inner_angle: f32,
outer_angle: f32,
},
}
#[derive(Clone, Debug)]
pub struct LightSource {
pub kind: LightKind,
pub color: [f32; 3],
pub intensity: f32,
}
impl Default for LightSource {
fn default() -> Self {
Self {
kind: LightKind::Directional {
direction: [0.4, 0.3, 1.5],
},
color: [1.0, 1.0, 1.0],
intensity: 1.0,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ShadowFilter {
#[default]
Pcf,
Pcss,
}
#[derive(Clone, Debug)]
pub struct LightingSettings {
pub lights: Vec<LightSource>,
pub shadow_bias: f32,
pub shadows_enabled: bool,
pub sky_color: [f32; 3],
pub ground_color: [f32; 3],
pub hemisphere_intensity: f32,
pub shadow_extent_override: Option<f32>,
pub shadow_cascade_count: u32,
pub cascade_split_lambda: f32,
pub shadow_atlas_resolution: u32,
pub shadow_filter: ShadowFilter,
pub pcss_light_radius: f32,
}
impl Default for LightingSettings {
fn default() -> Self {
Self {
lights: vec![LightSource::default()],
shadow_bias: 0.0001,
shadows_enabled: true,
sky_color: [0.8, 0.9, 1.0],
ground_color: [0.3, 0.2, 0.1],
hemisphere_intensity: 0.5,
shadow_extent_override: None,
shadow_cascade_count: 4,
cascade_split_lambda: 0.75,
shadow_atlas_resolution: 4096,
shadow_filter: ShadowFilter::Pcf,
pcss_light_radius: 0.02,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PickId(pub u64);
impl PickId {
pub const NONE: Self = Self(0);
}
#[derive(Clone)]
#[non_exhaustive]
pub struct SceneRenderItem {
pub mesh_id: crate::resources::mesh_store::MeshId,
pub model: [[f32; 4]; 4],
pub selected: bool,
pub visible: bool,
pub show_normals: bool,
pub material: Material,
pub active_attribute: Option<crate::resources::AttributeRef>,
pub scalar_range: Option<(f32, f32)>,
pub colormap_id: Option<crate::resources::ColormapId>,
pub nan_color: Option<[f32; 4]>,
pub pick_id: PickId,
pub render_as_wireframe: bool,
}
impl Default for SceneRenderItem {
fn default() -> Self {
Self {
mesh_id: crate::resources::mesh_store::MeshId(0),
model: glam::Mat4::IDENTITY.to_cols_array_2d(),
selected: false,
visible: true,
show_normals: false,
material: Material::default(),
active_attribute: None,
scalar_range: None,
colormap_id: None,
nan_color: None,
pick_id: PickId::NONE,
render_as_wireframe: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PointRenderMode {
#[default]
ScreenSpaceCircle,
}
#[derive(Clone)]
#[non_exhaustive]
pub struct PointCloudItem {
pub positions: Vec<[f32; 3]>,
pub colors: Vec<[f32; 4]>,
pub scalars: Vec<f32>,
pub scalar_range: Option<(f32, f32)>,
pub colormap_id: Option<ColormapId>,
pub point_size: f32,
pub default_color: [f32; 4],
pub model: [[f32; 4]; 4],
pub render_mode: PointRenderMode,
pub id: u64,
pub radii: Vec<f32>,
pub transparencies: Vec<f32>,
pub gaussian: bool,
}
impl Default for PointCloudItem {
fn default() -> Self {
Self {
positions: Vec::new(),
colors: Vec::new(),
scalars: Vec::new(),
scalar_range: None,
colormap_id: None,
point_size: 4.0,
default_color: [1.0, 1.0, 1.0, 1.0],
model: glam::Mat4::IDENTITY.to_cols_array_2d(),
render_mode: PointRenderMode::ScreenSpaceCircle,
id: 0,
radii: Vec::new(),
transparencies: Vec::new(),
gaussian: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GlyphType {
#[default]
Arrow,
Sphere,
Cube,
}
#[non_exhaustive]
pub struct GlyphItem {
pub positions: Vec<[f32; 3]>,
pub vectors: Vec<[f32; 3]>,
pub scale: f32,
pub scale_by_magnitude: bool,
pub magnitude_clamp: Option<(f32, f32)>,
pub scalars: Vec<f32>,
pub scalar_range: Option<(f32, f32)>,
pub colormap_id: Option<ColormapId>,
pub glyph_type: GlyphType,
pub model: [[f32; 4]; 4],
pub id: u64,
}
impl Default for GlyphItem {
fn default() -> Self {
Self {
positions: Vec::new(),
vectors: Vec::new(),
scale: 1.0,
scale_by_magnitude: true,
magnitude_clamp: None,
scalars: Vec::new(),
scalar_range: None,
colormap_id: None,
glyph_type: GlyphType::Arrow,
model: glam::Mat4::IDENTITY.to_cols_array_2d(),
id: 0,
}
}
}
#[non_exhaustive]
pub struct VolumeItem {
pub volume_id: crate::resources::VolumeId,
pub color_lut: Option<ColormapId>,
pub opacity_lut: Option<ColormapId>,
pub scalar_range: (f32, f32),
pub bbox_min: [f32; 3],
pub bbox_max: [f32; 3],
pub step_scale: f32,
pub model: [[f32; 4]; 4],
pub enable_shading: bool,
pub opacity_scale: f32,
pub threshold_min: f32,
pub threshold_max: f32,
pub nan_color: Option<[f32; 4]>,
}
impl Default for VolumeItem {
fn default() -> Self {
Self {
volume_id: crate::resources::VolumeId(0),
color_lut: None,
opacity_lut: None,
scalar_range: (0.0, 1.0),
bbox_min: [0.0, 0.0, 0.0],
bbox_max: [1.0, 1.0, 1.0],
step_scale: 1.0,
model: glam::Mat4::IDENTITY.to_cols_array_2d(),
enable_shading: false,
opacity_scale: 1.0,
threshold_min: 0.0,
threshold_max: 1.0,
nan_color: None,
}
}
}
#[derive(Clone)]
#[non_exhaustive]
pub struct PolylineItem {
pub positions: Vec<[f32; 3]>,
pub scalars: Vec<f32>,
pub strip_lengths: Vec<u32>,
pub scalar_range: Option<(f32, f32)>,
pub colormap_id: Option<ColormapId>,
pub default_color: [f32; 4],
pub line_width: f32,
pub id: u64,
pub node_colors: Vec<[f32; 4]>,
pub edge_scalars: Vec<f32>,
pub edge_colors: Vec<[f32; 4]>,
pub node_radii: Vec<f32>,
pub node_vectors: Vec<[f32; 3]>,
pub edge_vectors: Vec<[f32; 3]>,
pub vector_scale: f32,
}
impl Default for PolylineItem {
fn default() -> Self {
Self {
positions: Vec::new(),
scalars: Vec::new(),
strip_lengths: Vec::new(),
scalar_range: None,
colormap_id: None,
default_color: [0.9, 0.92, 0.96, 1.0],
line_width: 2.0,
id: 0,
node_colors: Vec::new(),
edge_scalars: Vec::new(),
edge_colors: Vec::new(),
node_radii: Vec::new(),
node_vectors: Vec::new(),
edge_vectors: Vec::new(),
vector_scale: 1.0,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct StreamtubeItem {
pub positions: Vec<[f32; 3]>,
pub strip_lengths: Vec<u32>,
pub radius: f32,
pub color: [f32; 4],
pub id: u64,
}
impl Default for StreamtubeItem {
fn default() -> Self {
Self {
positions: Vec::new(),
strip_lengths: Vec::new(),
radius: 0.05,
color: [1.0, 1.0, 1.0, 1.0],
id: 0,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct CameraFrustumItem {
pub pose: [[f32; 4]; 4],
pub fov_y: f32,
pub aspect: f32,
pub near: f32,
pub far: f32,
pub color: [f32; 4],
pub line_width: f32,
pub image_plane_depth: Option<f32>,
}
impl Default for CameraFrustumItem {
fn default() -> Self {
Self {
pose: glam::Mat4::IDENTITY.to_cols_array_2d(),
fov_y: std::f32::consts::FRAC_PI_4,
aspect: 16.0 / 9.0,
near: 0.1,
far: 10.0,
color: [0.8, 0.8, 0.9, 1.0],
line_width: 2.0,
image_plane_depth: None,
}
}
}
impl CameraFrustumItem {
fn plane_corners(&self, d: f32) -> [[f32; 3]; 4] {
let half_h = (self.fov_y * 0.5).tan() * d;
let half_w = half_h * self.aspect;
let pose = glam::Mat4::from_cols_array_2d(&self.pose);
let corners_cam = [
glam::vec3(-half_w, half_h, -d),
glam::vec3(half_w, half_h, -d),
glam::vec3(half_w, -half_h, -d),
glam::vec3(-half_w, -half_h, -d),
];
corners_cam.map(|c| {
let w = pose.transform_point3(c);
[w.x, w.y, w.z]
})
}
pub(crate) fn to_polyline(&self) -> PolylineItem {
let near = self.plane_corners(self.near);
let far = self.plane_corners(self.far);
let mut positions: Vec<[f32; 3]> = Vec::new();
let mut strip_lengths: Vec<u32> = Vec::new();
positions.extend_from_slice(&[near[0], near[1], near[2], near[3], near[0]]);
strip_lengths.push(5);
positions.extend_from_slice(&[far[0], far[1], far[2], far[3], far[0]]);
strip_lengths.push(5);
for i in 0..4 {
positions.extend_from_slice(&[near[i], far[i]]);
strip_lengths.push(2);
}
if let Some(d) = self.image_plane_depth {
let ip = self.plane_corners(d);
positions.extend_from_slice(&[ip[0], ip[1], ip[2], ip[3], ip[0]]);
strip_lengths.push(5);
}
PolylineItem {
positions,
strip_lengths,
default_color: self.color,
line_width: self.line_width,
..PolylineItem::default()
}
}
pub fn camera_target(&self, standoff_factor: f32) -> crate::camera::CameraTarget {
let near = self.plane_corners(self.near);
let far = self.plane_corners(self.far);
let mut sum = glam::Vec3::ZERO;
for c in near.iter().chain(far.iter()) {
sum += glam::Vec3::from(*c);
}
let center = sum / 8.0;
let mut max_dist_sq: f32 = 0.0;
for c in near.iter().chain(far.iter()) {
let d = (glam::Vec3::from(*c) - center).length_squared();
if d > max_dist_sq {
max_dist_sq = d;
}
}
let distance = max_dist_sq.sqrt() * standoff_factor;
let pose = glam::Mat4::from_cols_array_2d(&self.pose);
let cam_z_world = pose.transform_vector3(glam::Vec3::Z); let eye = center + cam_z_world.normalize() * distance;
let forward = (center - eye).normalize();
let up_hint = if forward.dot(glam::Vec3::Z).abs() > 0.99 {
glam::Vec3::Y
} else {
glam::Vec3::Z
};
let right = forward.cross(up_hint).normalize();
let up = right.cross(forward).normalize();
let rot = glam::Mat3::from_cols(right, up, -forward);
let orientation = glam::Quat::from_mat3(&rot);
crate::camera::CameraTarget {
center,
distance,
orientation,
}
}
pub fn camera_view_target(&self) -> crate::camera::CameraTarget {
let pose = glam::Mat4::from_cols_array_2d(&self.pose);
let eye = pose.transform_point3(glam::Vec3::ZERO);
let rot = glam::Mat3::from_cols(
pose.x_axis.truncate(),
pose.y_axis.truncate(),
pose.z_axis.truncate(),
);
let orientation = glam::Quat::from_mat3(&rot).normalize();
let distance = (self.near * 0.5_f32).max(0.01);
let center = eye + orientation * (-glam::Vec3::Z) * distance;
crate::camera::CameraTarget {
center,
distance,
orientation,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum ImageAnchor {
#[default]
TopLeft,
TopRight,
BottomLeft,
BottomRight,
Center,
}
#[non_exhaustive]
#[derive(Clone)]
pub struct ScreenImageItem {
pub pixels: Vec<[u8; 4]>,
pub width: u32,
pub height: u32,
pub anchor: ImageAnchor,
pub scale: f32,
pub alpha: f32,
pub depth: Option<Vec<f32>>,
}
impl Default for ScreenImageItem {
fn default() -> Self {
Self {
pixels: Vec::new(),
width: 0,
height: 0,
anchor: ImageAnchor::TopLeft,
scale: 1.0,
alpha: 1.0,
depth: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum FilterMode {
#[default]
Cpu,
Gpu,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ComputeFilterKind {
Clip {
plane_normal: [f32; 3],
plane_dist: f32,
},
ClipBox {
center: [f32; 3],
half_extents: [f32; 3],
orientation: [[f32; 3]; 3],
},
ClipSphere {
center: [f32; 3],
radius: f32,
},
Threshold {
min: f32,
max: f32,
},
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct ComputeFilterItem {
pub mesh_id: crate::resources::mesh_store::MeshId,
pub kind: ComputeFilterKind,
pub attribute_name: Option<String>,
}
impl Default for ComputeFilterItem {
fn default() -> Self {
Self {
mesh_id: crate::resources::mesh_store::MeshId(0),
kind: ComputeFilterKind::Clip {
plane_normal: [0.0, 0.0, 1.0],
plane_dist: 0.0,
},
attribute_name: None,
}
}
}
#[derive(Debug, Clone)]
pub struct RenderCamera {
pub view: glam::Mat4,
pub projection: glam::Mat4,
pub eye_position: [f32; 3],
pub forward: [f32; 3],
pub orientation: glam::Quat,
pub near: f32,
pub far: f32,
pub fov: f32,
pub aspect: f32,
}
impl RenderCamera {
pub fn camera_uniform(&self) -> CameraUniform {
let vp = self.view_proj();
CameraUniform {
view_proj: vp.to_cols_array_2d(),
eye_pos: self.eye_position,
_pad: 0.0,
forward: self.forward,
_pad1: 0.0,
inv_view_proj: vp.inverse().to_cols_array_2d(),
view: self.view.to_cols_array_2d(),
}
}
pub fn view_proj(&self) -> glam::Mat4 {
self.projection * self.view
}
pub fn from_camera(cam: &crate::camera::Camera) -> Self {
let eye = cam.eye_position();
let forward = (cam.center - eye).normalize_or_zero();
Self {
view: cam.view_matrix(),
projection: cam.proj_matrix(),
eye_position: eye.to_array(),
forward: forward.to_array(),
orientation: cam.orientation,
near: cam.znear,
far: cam.zfar,
fov: cam.fov_y,
aspect: cam.aspect,
}
}
}
impl Default for RenderCamera {
fn default() -> Self {
Self {
view: glam::Mat4::IDENTITY,
projection: glam::Mat4::IDENTITY,
eye_position: [0.0, 0.0, 5.0],
forward: [0.0, 0.0, -1.0],
orientation: glam::Quat::IDENTITY,
near: 0.1,
far: 1000.0,
fov: std::f32::consts::FRAC_PI_4,
aspect: 1.333,
}
}
}
#[non_exhaustive]
pub struct CameraFrame {
pub render_camera: RenderCamera,
pub viewport_size: [f32; 2],
pub viewport_index: usize,
}
impl Default for CameraFrame {
fn default() -> Self {
Self {
render_camera: RenderCamera::default(),
viewport_size: [800.0, 600.0],
viewport_index: 0,
}
}
}
impl CameraFrame {
pub fn new(render_camera: RenderCamera, viewport_size: [f32; 2]) -> Self {
Self {
render_camera,
viewport_size,
viewport_index: 0,
}
}
pub fn from_camera(cam: &crate::camera::Camera, viewport_size: [f32; 2]) -> Self {
Self::new(RenderCamera::from_camera(cam), viewport_size)
}
pub fn with_viewport_index(mut self, viewport_index: usize) -> Self {
self.viewport_index = viewport_index;
self
}
}
#[non_exhaustive]
pub enum SurfaceSubmission {
Flat(Vec<SceneRenderItem>),
}
impl Default for SurfaceSubmission {
fn default() -> Self {
SurfaceSubmission::Flat(Vec::new())
}
}
#[non_exhaustive]
pub struct SceneFrame {
pub generation: u64,
pub surfaces: SurfaceSubmission,
pub point_clouds: Vec<PointCloudItem>,
pub glyphs: Vec<GlyphItem>,
pub polylines: Vec<PolylineItem>,
pub volumes: Vec<VolumeItem>,
pub isolines: Vec<crate::geometry::isoline::IsolineItem>,
pub streamtube_items: Vec<StreamtubeItem>,
pub camera_frustums: Vec<CameraFrustumItem>,
pub screen_images: Vec<ScreenImageItem>,
pub gpu_implicit: Vec<crate::resources::GpuImplicitItem>,
pub gpu_mc_jobs: Vec<crate::resources::GpuMarchingCubesJob>,
}
impl Default for SceneFrame {
fn default() -> Self {
Self {
generation: 0,
surfaces: SurfaceSubmission::default(),
point_clouds: Vec::new(),
glyphs: Vec::new(),
polylines: Vec::new(),
volumes: Vec::new(),
isolines: Vec::new(),
streamtube_items: Vec::new(),
camera_frustums: Vec::new(),
screen_images: Vec::new(),
gpu_implicit: Vec::new(),
gpu_mc_jobs: Vec::new(),
}
}
}
impl SceneFrame {
pub fn new(surfaces: SurfaceSubmission) -> Self {
Self {
surfaces,
..Self::default()
}
}
pub fn from_surface_items(items: Vec<SceneRenderItem>) -> Self {
Self::new(SurfaceSubmission::Flat(items))
}
pub fn from_scene(
scene: &mut crate::scene::scene::Scene,
selection: &crate::interaction::selection::Selection,
) -> Self {
let items = scene.collect_render_items(selection);
Self {
generation: scene.version(),
surfaces: SurfaceSubmission::Flat(items),
..Self::default()
}
}
}
#[non_exhaustive]
pub struct ViewportFrame {
pub background_color: Option<[f32; 4]>,
pub wireframe_mode: bool,
pub show_grid: bool,
pub grid_cell_size: f32,
pub grid_half_extent: f32,
pub grid_z: f32,
pub show_axes_indicator: bool,
}
impl Default for ViewportFrame {
fn default() -> Self {
Self {
background_color: None,
wireframe_mode: false,
show_grid: false,
grid_cell_size: 0.0,
grid_half_extent: 0.0,
grid_z: 0.0,
show_axes_indicator: true,
}
}
}
#[non_exhaustive]
pub struct InteractionFrame {
pub selection_generation: u64,
pub gizmo_model: Option<glam::Mat4>,
pub gizmo_mode: GizmoMode,
pub gizmo_hovered: GizmoAxis,
pub gizmo_space_orientation: glam::Quat,
pub constraint_overlays: Vec<ConstraintOverlay>,
pub outline_selected: bool,
pub outline_color: [f32; 4],
pub outline_width_px: f32,
pub xray_selected: bool,
pub xray_color: [f32; 4],
pub sub_selection: Option<SubSelectionRef>,
pub sub_highlight_face_fill_color: [f32; 4],
pub sub_highlight_edge_color: [f32; 4],
pub sub_highlight_edge_width_px: f32,
pub sub_highlight_vertex_size_px: f32,
}
impl Default for InteractionFrame {
fn default() -> Self {
Self {
selection_generation: 0,
gizmo_model: None,
gizmo_mode: GizmoMode::Translate,
gizmo_hovered: GizmoAxis::None,
gizmo_space_orientation: glam::Quat::IDENTITY,
constraint_overlays: Vec::new(),
outline_selected: false,
outline_color: [1.0, 1.0, 1.0, 1.0],
outline_width_px: 2.0,
xray_selected: false,
xray_color: [0.3, 0.7, 1.0, 0.25],
sub_selection: None,
sub_highlight_face_fill_color: [1.0, 0.85, 0.0, 0.25],
sub_highlight_edge_color: [1.0, 0.85, 0.0, 1.0],
sub_highlight_edge_width_px: 2.0,
sub_highlight_vertex_size_px: 10.0,
}
}
}
impl InteractionFrame {
pub fn from_selection(selection: &crate::interaction::selection::Selection) -> Self {
Self {
selection_generation: selection.version(),
..Self::default()
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum GroundPlaneMode {
#[default]
None,
ShadowOnly,
Tile,
SolidColor,
}
#[derive(Clone, Debug)]
pub struct GroundPlane {
pub mode: GroundPlaneMode,
pub height: f32,
pub color: [f32; 4],
pub tile_size: f32,
pub shadow_color: [f32; 4],
pub shadow_opacity: f32,
}
impl Default for GroundPlane {
fn default() -> Self {
Self {
mode: GroundPlaneMode::None,
height: 0.0,
color: [0.3, 0.3, 0.3, 1.0],
tile_size: 1.0,
shadow_color: [0.0, 0.0, 0.0, 1.0],
shadow_opacity: 0.5,
}
}
}
#[derive(Clone, Debug)]
pub struct EnvironmentMap {
pub intensity: f32,
pub rotation: f32,
pub show_skybox: bool,
}
impl Default for EnvironmentMap {
fn default() -> Self {
Self {
intensity: 1.0,
rotation: 0.0,
show_skybox: true,
}
}
}
#[non_exhaustive]
pub struct EffectsFrame {
pub lighting: LightingSettings,
pub clip_objects: Vec<ClipObject>,
pub cap_fill_enabled: bool,
pub post_process: PostProcessSettings,
pub compute_filter_items: Vec<ComputeFilterItem>,
pub environment: Option<EnvironmentMap>,
pub ground_plane: GroundPlane,
}
impl Default for EffectsFrame {
fn default() -> Self {
Self {
lighting: LightingSettings::default(),
clip_objects: Vec::new(),
cap_fill_enabled: true,
post_process: PostProcessSettings::default(),
compute_filter_items: Vec::new(),
environment: None,
ground_plane: GroundPlane::default(),
}
}
}
pub struct SceneEffects<'a> {
pub lighting: &'a LightingSettings,
pub environment: &'a Option<EnvironmentMap>,
pub compute_filter_items: &'a [ComputeFilterItem],
}
pub struct ViewportEffects<'a> {
pub clip_objects: &'a [ClipObject],
pub cap_fill_enabled: bool,
pub post_process: &'a PostProcessSettings,
pub ground_plane: &'a GroundPlane,
}
impl EffectsFrame {
pub fn split(&self) -> (SceneEffects<'_>, ViewportEffects<'_>) {
(
SceneEffects {
lighting: &self.lighting,
environment: &self.environment,
compute_filter_items: &self.compute_filter_items,
},
ViewportEffects {
clip_objects: &self.clip_objects,
cap_fill_enabled: self.cap_fill_enabled,
post_process: &self.post_process,
ground_plane: &self.ground_plane,
},
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LabelAnchor {
#[default]
Leading,
Center,
Trailing,
}
#[derive(Debug, Clone)]
pub struct LabelItem {
pub world_anchor: Option<[f32; 3]>,
pub screen_anchor: Option<[f32; 2]>,
pub text: String,
pub color: [f32; 4],
pub font_size: f32,
pub font: Option<crate::resources::font::FontHandle>,
pub background: bool,
pub background_color: [f32; 4],
pub padding: f32,
pub leader_line: bool,
pub leader_color: [f32; 4],
pub anchor_align: LabelAnchor,
pub offset: [f32; 2],
pub opacity: f32,
pub max_width: Option<f32>,
pub border_radius: f32,
pub z_order: i32,
pub occlude: bool,
}
impl Default for LabelItem {
fn default() -> Self {
Self {
world_anchor: None,
screen_anchor: None,
text: String::new(),
color: [1.0, 1.0, 1.0, 1.0],
font_size: 14.0,
font: None,
background: false,
background_color: [0.0, 0.0, 0.0, 0.55],
padding: 3.0,
leader_line: false,
leader_color: [1.0, 1.0, 1.0, 0.6],
anchor_align: LabelAnchor::Leading,
offset: [0.0, 0.0],
opacity: 1.0,
max_width: None,
border_radius: 0.0,
z_order: 0,
occlude: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScalarBarAnchor {
TopLeft,
TopRight,
BottomLeft,
#[default]
BottomRight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ScalarBarOrientation {
#[default]
Vertical,
Horizontal,
}
#[derive(Debug, Clone)]
pub struct ScalarBarItem {
pub colormap_id: crate::resources::ColormapId,
pub scalar_min: f32,
pub scalar_max: f32,
pub title: Option<String>,
pub anchor: ScalarBarAnchor,
pub orientation: ScalarBarOrientation,
pub bar_width_px: f32,
pub bar_length_px: f32,
pub margin_px: f32,
pub font: Option<crate::resources::font::FontHandle>,
pub font_size: f32,
pub label_color: [f32; 4],
pub tick_count: u32,
pub background_color: [f32; 4],
pub ticks_reversed: bool,
pub title_font_size: Option<f32>,
}
impl Default for ScalarBarItem {
fn default() -> Self {
Self {
colormap_id: crate::resources::ColormapId(0),
scalar_min: 0.0,
scalar_max: 1.0,
title: None,
anchor: ScalarBarAnchor::BottomRight,
orientation: ScalarBarOrientation::Vertical,
bar_width_px: 20.0,
bar_length_px: 200.0,
margin_px: 16.0,
font: None,
font_size: 12.0,
label_color: [1.0, 1.0, 1.0, 1.0],
tick_count: 5,
background_color: [0.0, 0.0, 0.0, 0.63],
ticks_reversed: false,
title_font_size: None,
}
}
}
#[derive(Debug, Clone)]
pub struct RulerItem {
pub start: [f32; 3],
pub end: [f32; 3],
pub color: [f32; 4],
pub line_width_px: f32,
pub font: Option<crate::resources::FontHandle>,
pub font_size: f32,
pub label_color: [f32; 4],
pub label_format: Option<String>,
pub end_caps: bool,
}
impl Default for RulerItem {
fn default() -> Self {
Self {
start: [0.0; 3],
end: [1.0, 0.0, 0.0],
color: [1.0, 1.0, 1.0, 1.0],
line_width_px: 1.5,
font: None,
font_size: 13.0,
label_color: [1.0, 1.0, 1.0, 1.0],
label_format: None,
end_caps: true,
}
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct OverlayImageItem {
pub pixels: Vec<[u8; 4]>,
pub width: u32,
pub height: u32,
pub anchor: ImageAnchor,
pub scale: f32,
pub alpha: f32,
}
impl Default for OverlayImageItem {
fn default() -> Self {
Self {
pixels: Vec::new(),
width: 0,
height: 0,
anchor: ImageAnchor::TopLeft,
scale: 1.0,
alpha: 1.0,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct OverlayFrame {
pub labels: Vec<LabelItem>,
pub scalar_bars: Vec<ScalarBarItem>,
pub rulers: Vec<RulerItem>,
pub images: Vec<OverlayImageItem>,
}
#[non_exhaustive]
pub struct FrameData {
pub camera: CameraFrame,
pub scene: SceneFrame,
pub viewport: ViewportFrame,
pub interaction: InteractionFrame,
pub effects: EffectsFrame,
pub overlays: OverlayFrame,
}
impl Default for FrameData {
fn default() -> Self {
Self {
camera: CameraFrame::default(),
scene: SceneFrame::default(),
viewport: ViewportFrame::default(),
interaction: InteractionFrame::default(),
effects: EffectsFrame::default(),
overlays: OverlayFrame::default(),
}
}
}
impl FrameData {
pub fn new(camera: CameraFrame, scene: SceneFrame) -> Self {
Self {
camera,
scene,
..Self::default()
}
}
pub fn from_scene(
camera: CameraFrame,
scene: &mut crate::scene::scene::Scene,
selection: &crate::interaction::selection::Selection,
) -> Self {
Self {
camera,
scene: SceneFrame::from_scene(scene, selection),
interaction: InteractionFrame::from_selection(selection),
..Self::default()
}
}
pub fn with_background(mut self, color: [f32; 4]) -> Self {
self.viewport.background_color = Some(color);
self
}
pub fn with_lighting(mut self, lighting: LightingSettings) -> Self {
self.effects.lighting = lighting;
self
}
pub fn with_post_process(mut self, post: PostProcessSettings) -> Self {
self.effects.post_process = post;
self
}
pub fn with_ground_plane(mut self, ground: GroundPlane) -> Self {
self.effects.ground_plane = ground;
self
}
}
macro_rules! emit_draw_calls {
($resources:expr, $render_pass:expr, $frame:expr, $use_instancing:expr, $batches:expr, $camera_bg:expr, $grid_bg:expr, $compute_filter_results:expr, $slot:expr) => {{
let resources = $resources;
let render_pass = $render_pass;
let frame = $frame;
let use_instancing: bool = $use_instancing;
let _vp_slot: Option<&ViewportSlot> = $slot;
let compute_filter_results: &[crate::resources::ComputeFilterResult] = $compute_filter_results;
let batches: &[InstancedBatch] = $batches;
let camera_bg: &wgpu::BindGroup = $camera_bg;
let grid_bg: &wgpu::BindGroup = $grid_bg;
let scene_items: &[SceneRenderItem] = match &frame.scene.surfaces {
SurfaceSubmission::Flat(items) => items,
};
render_pass.set_bind_group(0, camera_bg, &[]);
if frame.viewport.show_grid {
render_pass.set_pipeline(&resources.grid_pipeline);
render_pass.set_bind_group(0, grid_bg, &[]);
render_pass.draw(0..3, 0..1);
render_pass.set_bind_group(0, camera_bg, &[]);
}
if !matches!(
frame.effects.ground_plane.mode,
crate::renderer::types::GroundPlaneMode::None
) {
render_pass.set_pipeline(&resources.ground_plane_pipeline);
render_pass.set_bind_group(0, &resources.ground_plane_bind_group, &[]);
render_pass.draw(0..3, 0..1);
render_pass.set_bind_group(0, camera_bg, &[]);
}
if !scene_items.is_empty() {
if use_instancing && !batches.is_empty() {
let excluded_items: Vec<&SceneRenderItem> = scene_items
.iter()
.filter(|item| {
item.visible
&& (item.active_attribute.is_some()
|| item.material.is_two_sided()
|| item.material.param_vis.is_some())
&& resources
.mesh_store
.get(item.mesh_id)
.is_some()
})
.collect();
let mut opaque_batches: Vec<&InstancedBatch> = Vec::new();
let mut transparent_batches: Vec<&InstancedBatch> = Vec::new();
for batch in batches {
if batch.is_transparent {
transparent_batches.push(batch);
} else {
opaque_batches.push(batch);
}
}
if !opaque_batches.is_empty() && !frame.viewport.wireframe_mode {
if let Some(ref pipeline) = resources.solid_instanced_pipeline {
render_pass.set_pipeline(pipeline);
for batch in &opaque_batches {
let Some(mesh) = resources.mesh_store.get(batch.mesh_id) else { continue };
let mat_key = (
batch.texture_id.unwrap_or(u64::MAX),
batch.normal_map_id.unwrap_or(u64::MAX),
batch.ao_map_id.unwrap_or(u64::MAX),
);
let Some(inst_tex_bg) = resources.instance_bind_groups.get(&mat_key) else { continue };
render_pass.set_bind_group(1, inst_tex_bg, &[]);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(
0..mesh.index_count,
0,
batch.instance_offset..batch.instance_offset + batch.instance_count,
);
}
}
}
if !transparent_batches.is_empty() && !frame.viewport.wireframe_mode {
if let Some(ref pipeline) = resources.transparent_instanced_pipeline {
render_pass.set_pipeline(pipeline);
for batch in &transparent_batches {
let Some(mesh) = resources.mesh_store.get(batch.mesh_id) else { continue };
let mat_key = (
batch.texture_id.unwrap_or(u64::MAX),
batch.normal_map_id.unwrap_or(u64::MAX),
batch.ao_map_id.unwrap_or(u64::MAX),
);
let Some(inst_tex_bg) = resources.instance_bind_groups.get(&mat_key) else { continue };
render_pass.set_bind_group(1, inst_tex_bg, &[]);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(
0..mesh.index_count,
0,
batch.instance_offset..batch.instance_offset + batch.instance_count,
);
}
}
}
if frame.viewport.wireframe_mode {
for item in scene_items {
if !item.visible { continue; }
let Some(mesh) = resources.mesh_store.get(item.mesh_id) else { continue };
render_pass.set_pipeline(&resources.wireframe_pipeline);
render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(mesh.edge_index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
}
} else {
for item in &excluded_items {
let Some(mesh) = resources
.mesh_store
.get(item.mesh_id)
else {
continue;
};
let pipeline = if item.material.opacity < 1.0 {
&resources.transparent_pipeline
} else if item.material.is_two_sided() {
&resources.solid_two_sided_pipeline
} else {
&resources.solid_pipeline
};
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
let is_face_attr = item.active_attribute.as_ref().map_or(false, |a| {
matches!(
a.kind,
crate::resources::AttributeKind::Face
| crate::resources::AttributeKind::FaceColor
| crate::resources::AttributeKind::Halfedge
| crate::resources::AttributeKind::Corner
)
});
if is_face_attr {
if let Some(ref fvb) = mesh.face_vertex_buffer {
render_pass.set_vertex_buffer(0, fvb.slice(..));
render_pass.draw(0..mesh.index_count, 0..1);
}
} else {
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(
mesh.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
}
}
}
} else {
let eye = glam::Vec3::from(frame.camera.render_camera.eye_position);
let dist_from_eye = |item: &&SceneRenderItem| -> f32 {
let pos = glam::Vec3::new(
item.model[3][0],
item.model[3][1],
item.model[3][2],
);
(pos - eye).length()
};
let mut opaque: Vec<&SceneRenderItem> = Vec::new();
let mut transparent: Vec<&SceneRenderItem> = Vec::new();
for item in scene_items {
if !item.visible || resources.mesh_store.get(item.mesh_id).is_none() {
continue;
}
if item.material.opacity < 1.0 {
transparent.push(item);
} else {
opaque.push(item);
}
}
opaque.sort_by(|a, b| dist_from_eye(a).partial_cmp(&dist_from_eye(b)).unwrap_or(std::cmp::Ordering::Equal));
transparent.sort_by(|a, b| dist_from_eye(b).partial_cmp(&dist_from_eye(a)).unwrap_or(std::cmp::Ordering::Equal));
macro_rules! draw_item {
($item:expr, $pipeline:expr) => {{
let item = $item;
let mesh = resources.mesh_store.get(item.mesh_id).unwrap();
render_pass.set_bind_group(1, &mesh.object_bind_group, &[]);
let is_face_attr = item.active_attribute.as_ref().map_or(false, |a| {
matches!(
a.kind,
crate::resources::AttributeKind::Face
| crate::resources::AttributeKind::FaceColor
| crate::resources::AttributeKind::Halfedge
| crate::resources::AttributeKind::Corner
)
});
if frame.viewport.wireframe_mode {
render_pass.set_pipeline(&resources.wireframe_pipeline);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(
mesh.edge_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..mesh.edge_index_count, 0, 0..1);
} else if is_face_attr {
if let Some(ref fvb) = mesh.face_vertex_buffer {
render_pass.set_pipeline($pipeline);
render_pass.set_vertex_buffer(0, fvb.slice(..));
render_pass.draw(0..mesh.index_count, 0..1);
}
} else {
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
let filter_result = compute_filter_results
.iter()
.find(|r| r.mesh_id == item.mesh_id);
render_pass.set_pipeline($pipeline);
if let Some(fr) = filter_result {
render_pass.set_index_buffer(
fr.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..fr.index_count, 0, 0..1);
} else {
render_pass.set_index_buffer(
mesh.index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
}
}
if item.show_normals {
if let Some(ref nl_buf) = mesh.normal_line_buffer {
if mesh.normal_line_count > 0 {
render_pass.set_pipeline(&resources.wireframe_pipeline);
render_pass.set_bind_group(1, &mesh.normal_bind_group, &[]);
render_pass.set_vertex_buffer(0, nl_buf.slice(..));
render_pass.draw(0..mesh.normal_line_count, 0..1);
}
}
}
}};
}
for item in &opaque {
let pl = if item.material.is_two_sided() {
&resources.solid_two_sided_pipeline
} else {
&resources.solid_pipeline
};
draw_item!(item, pl);
}
for item in &transparent {
draw_item!(item, &resources.transparent_pipeline);
}
}
}
if let Some(slot) = _vp_slot {
if frame.interaction.gizmo_model.is_some() && slot.gizmo_index_count > 0 {
render_pass.set_pipeline(&resources.gizmo_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
render_pass.set_bind_group(1, &slot.gizmo_bind_group, &[]);
render_pass.set_vertex_buffer(0, slot.gizmo_vertex_buffer.slice(..));
render_pass.set_index_buffer(
slot.gizmo_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..slot.gizmo_index_count, 0, 0..1);
}
}
if let Some(slot) = _vp_slot {
if !slot.constraint_line_buffers.is_empty() {
render_pass.set_pipeline(&resources.overlay_line_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for (vbuf, ibuf, index_count, _ubuf, bg) in &slot.constraint_line_buffers {
render_pass.set_bind_group(1, bg, &[]);
render_pass.set_vertex_buffer(0, vbuf.slice(..));
render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..*index_count, 0, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if !slot.cap_buffers.is_empty() {
render_pass.set_pipeline(&resources.overlay_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.cap_buffers {
render_pass.set_bind_group(1, bg, &[]);
render_pass.set_vertex_buffer(0, vbuf.slice(..));
render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..*idx_count, 0, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if !slot.clip_plane_fill_buffers.is_empty() {
render_pass.set_pipeline(&resources.overlay_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_fill_buffers {
render_pass.set_bind_group(1, bg, &[]);
render_pass.set_vertex_buffer(0, vbuf.slice(..));
render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..*idx_count, 0, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if !slot.clip_plane_line_buffers.is_empty() {
render_pass.set_pipeline(&resources.overlay_line_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for (vbuf, ibuf, idx_count, _ubuf, bg) in &slot.clip_plane_line_buffers {
render_pass.set_bind_group(1, bg, &[]);
render_pass.set_vertex_buffer(0, vbuf.slice(..));
render_pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..*idx_count, 0, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if !slot.xray_object_buffers.is_empty() {
render_pass.set_pipeline(&resources.xray_pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for (mesh_id, _buf, bg) in &slot.xray_object_buffers {
let Some(mesh) = resources.mesh_store.get(*mesh_id) else { continue };
render_pass.set_bind_group(1, bg, &[]);
render_pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
render_pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..mesh.index_count, 0, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if !slot.outline_object_buffers.is_empty() {
let composite_bg = slot.hdr.as_ref().map(|h| &h.outline_composite_bind_group);
let pipeline = resources.outline_composite_pipeline_msaa.as_ref()
.or(resources.outline_composite_pipeline_single.as_ref());
if let (Some(pipeline), Some(bg)) = (pipeline, composite_bg) {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, bg, &[]);
render_pass.draw(0..3, 0..1);
}
}
}
if let Some(slot) = _vp_slot {
if frame.viewport.show_axes_indicator && slot.axes_vertex_count > 0 {
render_pass.set_pipeline(&resources.axes_pipeline);
render_pass.set_vertex_buffer(0, slot.axes_vertex_buffer.slice(..));
render_pass.draw(0..slot.axes_vertex_count, 0..1);
}
}
}};
}
macro_rules! emit_scivis_draw_calls {
($resources:expr, $render_pass:expr, $pc_gpu_data:expr, $glyph_gpu_data:expr, $polyline_gpu_data:expr, $volume_gpu_data:expr, $streamtube_gpu_data:expr, $camera_bg:expr) => {{
let resources = $resources;
let render_pass = $render_pass;
let camera_bg: &wgpu::BindGroup = $camera_bg;
if !$pc_gpu_data.is_empty() {
if let Some(ref pipeline) = resources.point_cloud_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for pc in $pc_gpu_data.iter() {
render_pass.set_bind_group(1, &pc.bind_group, &[]);
render_pass.set_vertex_buffer(0, pc.vertex_buffer.slice(..));
render_pass.draw(0..6, 0..pc.point_count);
}
}
}
if !$glyph_gpu_data.is_empty() {
if let Some(ref pipeline) = resources.glyph_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for glyph in $glyph_gpu_data.iter() {
render_pass.set_bind_group(1, &glyph.uniform_bind_group, &[]);
render_pass.set_bind_group(2, &glyph.instance_bind_group, &[]);
render_pass.set_vertex_buffer(0, glyph.mesh_vertex_buffer.slice(..));
render_pass.set_index_buffer(
glyph.mesh_index_buffer.slice(..),
wgpu::IndexFormat::Uint32,
);
render_pass.draw_indexed(0..glyph.mesh_index_count, 0, 0..glyph.instance_count);
}
}
}
if !$polyline_gpu_data.is_empty() {
if let Some(ref pipeline) = resources.polyline_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for pl in $polyline_gpu_data.iter() {
if pl.segment_count == 0 {
continue;
}
render_pass.set_bind_group(1, &pl.bind_group, &[]);
render_pass.set_vertex_buffer(0, pl.vertex_buffer.slice(..));
render_pass.draw(0..6, 0..pl.segment_count);
}
}
}
if !$volume_gpu_data.is_empty() {
if let Some(ref pipeline) = resources.volume_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for vol in $volume_gpu_data.iter() {
render_pass.set_bind_group(1, &vol.bind_group, &[]);
render_pass.set_vertex_buffer(0, vol.vertex_buffer.slice(..));
render_pass
.set_index_buffer(vol.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..36, 0, 0..1);
}
}
}
if !$streamtube_gpu_data.is_empty() {
if let Some(ref pipeline) = resources.streamtube_pipeline {
render_pass.set_pipeline(pipeline);
render_pass.set_bind_group(0, camera_bg, &[]);
for tube in $streamtube_gpu_data.iter() {
if tube.index_count == 0 {
continue;
}
render_pass.set_bind_group(1, &tube.uniform_bind_group, &[]);
render_pass.set_vertex_buffer(0, tube.vertex_buffer.slice(..));
render_pass
.set_index_buffer(tube.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
render_pass.draw_indexed(0..tube.index_count, 0, 0..1);
}
}
}
}};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn render_camera_from_camera_roundtrip() {
let cam = crate::camera::Camera::default();
let rc = RenderCamera::from_camera(&cam);
assert_eq!(rc.eye_position, cam.eye_position().to_array());
assert_eq!(rc.orientation, cam.orientation);
assert_eq!(rc.near, cam.znear);
assert_eq!(rc.far, cam.zfar);
assert_eq!(rc.fov, cam.fov_y);
assert_eq!(rc.aspect, cam.aspect);
let expected_vp = cam.view_proj_matrix();
let actual_vp = rc.view_proj();
assert!(
(expected_vp - actual_vp).abs_diff_eq(glam::Mat4::ZERO, 1e-5),
"view_proj mismatch"
);
}
#[test]
fn render_camera_uniform_contains_eye_and_forward() {
let rc = RenderCamera {
eye_position: [1.0, 2.0, 3.0],
forward: [0.0, 0.0, -1.0],
..RenderCamera::default()
};
let u = rc.camera_uniform();
assert_eq!(u.eye_pos, [1.0, 2.0, 3.0]);
assert_eq!(u.forward, [0.0, 0.0, -1.0]);
assert_eq!(u.view_proj, rc.view_proj().to_cols_array_2d());
}
}