use super::uniforms::{clip_meta, clip_planes, material_uniforms};
use molgfx_core::{
MAX_CLIP_PLANES, MAX_VOLUME_TRANSFER_POINTS, OccupancyStream, PlacedStructure, Representation,
ScalarVolume,
};
use molgfx_math::{Mat4, Vec3};
#[cfg(test)]
#[path = "volume_uniforms_tests.rs"]
mod tests;
#[repr(C)]
#[derive(Clone, Copy, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub(super) struct VolumeUniforms {
pub(super) voxel_to_world: Mat4,
pub(super) world_to_voxel: Mat4,
pub(super) dimensions: [u32; 4],
pub(super) empty_space_dimensions: [u32; 4],
pub(super) scalar: [f32; 4],
pub(super) sampling: [f32; 4],
pub(super) transfer_values: [[f32; 4]; MAX_VOLUME_TRANSFER_POINTS],
pub(super) transfer_colors: [[f32; 4]; MAX_VOLUME_TRANSFER_POINTS],
pub(super) transfer_meta: [u32; 4],
pub(super) clip_planes: [[f32; 4]; MAX_CLIP_PLANES],
pub(super) clip_meta: [u32; 4],
pub(super) crop_minimum: [u32; 4],
pub(super) crop_maximum: [u32; 4],
pub(super) slice_plane: [f32; 4],
pub(super) material: [f32; 4],
}
impl VolumeUniforms {
pub(super) fn new(volume: &ScalarVolume, representation: &Representation) -> Self {
Self::from_parts(
volume.voxel_to_world(),
volume.dimensions(),
volume.empty_space_dimensions(),
volume.range(),
representation,
)
}
pub(super) fn new_occupancy(
stream: &OccupancyStream,
placed: &PlacedStructure,
representation: &Representation,
) -> Self {
let dimensions = stream.dimensions();
let empty_space_dimensions = dimensions.map(|dimension| {
dimension.saturating_add(ScalarVolume::EMPTY_SPACE_BRICK_SIZE - 1)
/ ScalarVolume::EMPTY_SPACE_BRICK_SIZE
});
Self::from_parts(
placed.model_to_world * stream.voxel_to_model(),
dimensions,
empty_space_dimensions,
[0.0, stream.maximum()],
representation,
)
}
fn from_parts(
transform: Mat4,
dimensions: [u32; 3],
empty_space_dimensions: [u32; 3],
range: [f32; 2],
representation: &Representation,
) -> Self {
let axes = [
transform.transform_vector3(Vec3::X).length(),
transform.transform_vector3(Vec3::Y).length(),
transform.transform_vector3(Vec3::Z).length(),
];
let points = representation.volume.transfer.points();
let mut transfer_values = [[0.0; 4]; MAX_VOLUME_TRANSFER_POINTS];
let mut transfer_colors = [[0.0; 4]; MAX_VOLUME_TRANSFER_POINTS];
for (index, point) in points.iter().enumerate() {
transfer_values[index] = [point.value, point.opacity, 0.0, 0.0];
transfer_colors[index] = point.color.to_f32();
}
let opacity = finite_or(representation.material.opacity, 1.0).clamp(0.0, 1.0);
let density = finite_or(representation.volume.opacity_scale, 2.0).max(0.0);
let step = finite_or(representation.volume.step_scale, 0.65).clamp(0.2, 2.0);
let crop = representation.volume.region;
let minimum = crop.map_or([0; 3], molgfx_core::VolumeRegion::minimum);
let maximum = crop.map_or(dimensions, molgfx_core::VolumeRegion::maximum);
Self {
voxel_to_world: transform,
world_to_voxel: transform.inverse(),
dimensions: [
dimensions[0],
dimensions[1],
dimensions[2],
ScalarVolume::EMPTY_SPACE_BRICK_SIZE,
],
empty_space_dimensions: [
empty_space_dimensions[0],
empty_space_dimensions[1],
empty_space_dimensions[2],
0,
],
scalar: [
range[0],
range[1],
finite_or(representation.params.isolevel, range[0].midpoint(range[1]))
.clamp(range[0], range[1]),
opacity,
],
sampling: [
density * opacity,
step,
axes.into_iter().fold(f32::INFINITY, f32::min),
0.0,
],
transfer_values,
transfer_colors,
transfer_meta: [
crate::fallback(u32::try_from(points.len()), 2),
representation.volume.rendering as u32,
0,
0,
],
clip_planes: clip_planes(&representation.clipping),
clip_meta: clip_meta(&representation.clipping),
crop_minimum: [minimum[0], minimum[1], minimum[2], 0],
crop_maximum: [maximum[0], maximum[1], maximum[2], 0],
slice_plane: representation
.volume
.slice
.map_or([0.0, 0.0, 1.0, 0.0], |slice| {
[
slice.plane.normal.x,
slice.plane.normal.y,
slice.plane.normal.z,
slice.plane.offset,
]
}),
material: material_uniforms(representation.material),
}
}
}
fn finite_or(value: f32, fallback: f32) -> f32 {
if value.is_finite() { value } else { fallback }
}