use glam::{Mat4, Quat, Vec3};
use thiserror::Error;
use std::{
collections::{HashMap, HashSet},
sync::LazyLock,
};
use awsm_renderer_core::{
buffers::{BufferDescriptor, BufferUsage},
error::AwsmCoreError,
pipeline::primitive::FrontFace,
renderer::AwsmRendererWebGpu,
};
use slotmap::{new_key_type, SecondaryMap, SlotMap};
use crate::{
bind_groups::{BindGroupCreate, BindGroups},
buffer::dynamic_uniform::DynamicUniformBuffer,
buffer::helpers::write_buffer_with_dirty_ranges,
meshes::skins::AwsmSkinError,
AwsmRenderer, AwsmRendererLogging,
};
impl AwsmRenderer {
pub fn update_transforms(&mut self) {
self.transforms.update_world();
let dirty_transforms = self.transforms.take_dirty_meshes();
let dirty_instances = self.instances.take_dirty_transforms();
self.meshes.update_world(
dirty_transforms,
&dirty_instances,
&self.transforms,
&self.instances,
);
}
}
pub struct Transforms {
locals: SlotMap<TransformKey, Transform>,
world_matrices: SecondaryMap<TransformKey, glam::Mat4>,
children: SecondaryMap<TransformKey, Vec<TransformKey>>,
parents: SecondaryMap<TransformKey, TransformKey>,
dirties: HashSet<TransformKey>,
dirty_meshes: Vec<TransformKey>,
gpu_dirty: bool,
pub root_node: TransformKey,
buffer: DynamicUniformBuffer<TransformKey>,
normals_buffer: DynamicUniformBuffer<TransformKey>,
pub(crate) gpu_buffer: web_sys::GpuBuffer,
pub(crate) normals_gpu_buffer: web_sys::GpuBuffer,
}
static BUFFER_USAGE: LazyLock<BufferUsage> =
LazyLock::new(|| BufferUsage::new().with_storage().with_copy_dst());
impl Transforms {
pub const INITIAL_CAPACITY: usize = 32; pub const BYTE_SIZE: usize = 64; pub const NORMALS_BYTE_SIZE: usize = 36;
pub fn new(gpu: &AwsmRendererWebGpu) -> Result<Self> {
let gpu_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("Transforms"),
Transforms::INITIAL_CAPACITY * Transforms::BYTE_SIZE,
*BUFFER_USAGE,
)
.into(),
)?;
let normals_gpu_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("Normal Transform Matrices"),
Transforms::INITIAL_CAPACITY * Transforms::NORMALS_BYTE_SIZE,
*BUFFER_USAGE,
)
.into(),
)?;
let buffer = DynamicUniformBuffer::new(
Self::INITIAL_CAPACITY,
Self::BYTE_SIZE,
None,
Some("Transforms".to_string()),
);
let normals_buffer = DynamicUniformBuffer::new(
Self::INITIAL_CAPACITY,
Self::NORMALS_BYTE_SIZE,
None,
Some("Normal Transform Matrices".to_string()),
);
let mut locals = SlotMap::with_capacity_and_key(Self::INITIAL_CAPACITY);
let mut world_matrices = SecondaryMap::with_capacity(Self::INITIAL_CAPACITY);
let mut children = SecondaryMap::new();
let root_node = locals.insert(Transform::default());
world_matrices.insert(root_node, glam::Mat4::IDENTITY);
children.insert(root_node, Vec::new());
Ok(Self {
locals,
world_matrices,
children,
parents: SecondaryMap::new(),
dirties: HashSet::new(),
dirty_meshes: Vec::with_capacity(Self::INITIAL_CAPACITY),
gpu_dirty: true,
root_node,
buffer,
normals_buffer,
gpu_buffer,
normals_gpu_buffer,
})
}
pub fn insert(&mut self, transform: Transform, parent: Option<TransformKey>) -> TransformKey {
let world_matrix = transform.to_matrix();
let key = self.locals.insert(transform);
self.world_matrices.insert(key, world_matrix);
self.children.insert(key, Vec::new());
self.dirties.insert(key);
self.buffer.update(key, &[0; Self::BYTE_SIZE]);
self.normals_buffer
.update(key, &[0; Self::NORMALS_BYTE_SIZE]);
self.set_parent(key, parent);
key
}
pub fn duplicate(&mut self, key: TransformKey) -> Result<TransformKey> {
let local_transform = self.get_local(key)?.clone();
let parent_transform = self.get_parent(key).ok();
Ok(self.insert(local_transform, parent_transform))
}
pub fn remove(&mut self, key: TransformKey) {
if key == self.root_node {
return;
}
self.unset_parent(key);
self.locals.remove(key);
self.world_matrices.remove(key);
self.children.remove(key);
self.dirties.remove(&key);
self.buffer.remove(key);
self.normals_buffer.remove(key);
self.gpu_dirty = true;
}
pub fn set_local(&mut self, key: TransformKey, transform: Transform) -> Result<()> {
if key == self.root_node {
return Err(AwsmTransformError::CannotModifyRootNode);
}
match self.locals.get_mut(key) {
Some(existing) => {
*existing = transform;
self.dirties.insert(key);
Ok(())
}
None => Err(AwsmTransformError::LocalNotFound(key)),
}
}
pub fn set_parent(&mut self, child: TransformKey, parent: Option<TransformKey>) {
if child == self.root_node {
return;
}
let parent = parent.unwrap_or(self.root_node);
if let Some(existing_parent) = self.parents.get(child) {
if *existing_parent == parent {
return;
} else {
self.unset_parent(child);
}
}
self.children.get_mut(parent).unwrap().push(child);
self.parents.insert(child, parent);
}
pub fn get_parent(&self, child: TransformKey) -> Result<TransformKey> {
if child == self.root_node {
return Err(AwsmTransformError::CannotGetParentOfRootNode);
}
self.parents
.get(child)
.copied()
.ok_or(AwsmTransformError::CannotGetParent(child))
}
pub fn get_local(&self, key: TransformKey) -> Result<&Transform> {
self.locals
.get(key)
.ok_or(AwsmTransformError::LocalNotFound(key))
}
pub fn get_world(&self, key: TransformKey) -> Result<&glam::Mat4> {
self.world_matrices
.get(key)
.ok_or(AwsmTransformError::WorldNotFound(key))
}
pub fn get_children(&self, key: TransformKey) -> Option<&Vec<TransformKey>> {
self.children.get(key)
}
pub(crate) fn update_world(&mut self) {
self.gpu_dirty = self.gpu_dirty || !self.dirties.is_empty();
self.update_inner_recursively(self.root_node, false);
self.dirties.clear();
}
pub fn write_gpu(
&mut self,
logging: &AwsmRendererLogging,
gpu: &AwsmRendererWebGpu,
bind_groups: &mut BindGroups,
) -> Result<()> {
if self.gpu_dirty {
let _maybe_span_guard = if logging.render_timings {
Some(tracing::span!(tracing::Level::INFO, "Transform GPU write").entered())
} else {
None
};
let mut transform_resized = false;
if let Some(new_size) = self.buffer.take_gpu_needs_resize() {
self.gpu_buffer = gpu.create_buffer(
&BufferDescriptor::new(Some("Transforms"), new_size, *BUFFER_USAGE).into(),
)?;
bind_groups.mark_create(BindGroupCreate::TransformsResize);
transform_resized = true;
}
let mut normals_resized = false;
if let Some(new_size) = self.normals_buffer.take_gpu_needs_resize() {
self.normals_gpu_buffer = gpu.create_buffer(
&BufferDescriptor::new(
Some("Normal Transform Matrices"),
new_size,
*BUFFER_USAGE,
)
.into(),
)?;
bind_groups.mark_create(BindGroupCreate::TransformNormalsResize);
normals_resized = true;
}
if transform_resized {
self.buffer.clear_dirty_ranges();
gpu.write_buffer(&self.gpu_buffer, None, self.buffer.raw_slice(), None, None)?;
} else {
let transform_ranges = self.buffer.take_dirty_ranges();
write_buffer_with_dirty_ranges(
gpu,
&self.gpu_buffer,
self.buffer.raw_slice(),
transform_ranges,
)?;
}
if normals_resized {
self.normals_buffer.clear_dirty_ranges();
gpu.write_buffer(
&self.normals_gpu_buffer,
None,
self.normals_buffer.raw_slice(),
None,
None,
)?;
} else {
let normal_ranges = self.normals_buffer.take_dirty_ranges();
write_buffer_with_dirty_ranges(
gpu,
&self.normals_gpu_buffer,
self.normals_buffer.raw_slice(),
normal_ranges,
)?;
}
self.gpu_dirty = false;
}
Ok(())
}
pub fn take_dirty_meshes(&mut self) -> HashMap<TransformKey, Mat4> {
self.dirty_meshes
.drain(..)
.map(|key| {
let world_matrix = self.world_matrices.get(key).copied().unwrap();
(key, world_matrix)
})
.collect()
}
pub fn buffer_offset(&self, key: TransformKey) -> Result<usize> {
self.buffer
.offset(key)
.ok_or(AwsmTransformError::TransformBufferSlotMissing(key))
}
pub fn normals_buffer_offset(&self, key: TransformKey) -> Result<usize> {
self.normals_buffer
.offset(key)
.ok_or(AwsmTransformError::TransformBufferNormalsSlotMissing(key))
}
pub fn world_matrices_ref(&self) -> &SecondaryMap<TransformKey, glam::Mat4> {
&self.world_matrices
}
pub fn get_tree(&self) -> TransformTreeNode {
fn build_node(transforms: &Transforms, key: TransformKey) -> TransformTreeNode {
let children = transforms.children.get(key).unwrap();
let child_nodes = children
.iter()
.map(|&child_key| build_node(transforms, child_key))
.collect();
TransformTreeNode {
key,
children: child_nodes,
}
}
build_node(self, self.root_node)
}
fn update_inner_recursively(&mut self, key: TransformKey, dirty_tracker: bool) -> bool {
let dirty = self.dirties.contains(&key) | dirty_tracker;
if dirty {
let local_matrix = self.locals[key].to_matrix();
let world_matrix = match self.parents.get(key) {
Some(parent) => {
let parent_matrix = self.world_matrices[*parent];
parent_matrix.mul_mat4(&local_matrix)
}
None => local_matrix,
};
self.world_matrices[key] = world_matrix;
let values = world_matrix.to_cols_array();
let values_u8 = unsafe {
std::slice::from_raw_parts(values.as_ptr() as *const u8, Self::BYTE_SIZE)
};
self.buffer.update(key, values_u8);
let normal_matrix = world_matrix.inverse().transpose();
let normal_matrix = glam::Mat3::from_mat4(normal_matrix);
let normal_values = normal_matrix.to_cols_array();
let normal_values_u8 = unsafe {
std::slice::from_raw_parts(
normal_values.as_ptr() as *const u8,
Self::NORMALS_BYTE_SIZE,
)
};
self.normals_buffer.update(key, normal_values_u8);
self.dirty_meshes.push(key);
}
let children = self.children[key].clone();
for child in children {
self.update_inner_recursively(child, dirty);
}
dirty
}
fn unset_parent(&mut self, child: TransformKey) {
if let Some(parent) = self.parents.remove(child) {
if let Some(children) = self.children.get_mut(parent) {
children.retain(|&c| c != child);
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TransformTreeNode {
pub key: TransformKey,
pub children: Vec<TransformTreeNode>,
}
#[derive(Clone, Debug)]
pub struct Transform {
pub translation: Vec3,
pub rotation: Quat,
pub scale: Vec3,
}
impl Default for Transform {
fn default() -> Self {
Self::IDENTITY
}
}
impl Transform {
pub const IDENTITY: Self = Self {
translation: Vec3::ZERO,
rotation: Quat::IDENTITY,
scale: Vec3::ONE,
};
pub fn with_translation(mut self, translation: Vec3) -> Self {
self.translation = translation;
self
}
pub fn with_rotation(mut self, rotation: Quat) -> Self {
self.rotation = rotation;
self
}
pub fn with_scale(mut self, scale: Vec3) -> Self {
self.scale = scale;
self
}
pub fn to_matrix(&self) -> Mat4 {
Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
}
pub fn winding_order(&self) -> FrontFace {
if self.to_matrix().determinant() > 0.0 {
FrontFace::Ccw
} else {
FrontFace::Cw
}
}
}
impl From<&Mat4> for Transform {
fn from(matrix: &Mat4) -> Self {
let (scale, rotation, translation) = matrix.to_scale_rotation_translation();
Self {
translation,
rotation,
scale,
}
}
}
impl From<Mat4> for Transform {
fn from(matrix: Mat4) -> Self {
Self::from(&matrix)
}
}
impl From<&Transform> for Mat4 {
fn from(transform: &Transform) -> Self {
Mat4::from_scale_rotation_translation(
transform.scale,
transform.rotation,
transform.translation,
)
}
}
impl From<Transform> for Mat4 {
fn from(transform: Transform) -> Self {
Mat4::from(&transform)
}
}
new_key_type! {
pub struct TransformKey;
}
pub type Result<T> = std::result::Result<T, AwsmTransformError>;
#[derive(Error, Debug)]
pub enum AwsmTransformError {
#[error("[transform] local transform does not exist {0:?}")]
LocalNotFound(TransformKey),
#[error("[transform] world transform does not exist {0:?}")]
WorldNotFound(TransformKey),
#[error("[transform] cannot modify root node")]
CannotModifyRootNode,
#[error("[transform] buffer slot missing {0:?}")]
TransformBufferSlotMissing(TransformKey),
#[error("[transform] normals buffer slot missing {0:?}")]
TransformBufferNormalsSlotMissing(TransformKey),
#[error("[transform] cannot get parent of root node")]
CannotGetParentOfRootNode,
#[error("[transform] cannot get parent for {0:?}")]
CannotGetParent(TransformKey),
#[error("[transform] {0:?}")]
Core(#[from] AwsmCoreError),
#[error("[transform] {0:?}")]
Skin(#[from] AwsmSkinError),
}