use bytemuck::{Pod, Zeroable};
use glamx::{Mat3, Mat4, Vec3};
use crate::light::{CollectedLight, LightCollection, LightType};
use crate::scene::{InstancesBuffer3d, SceneNode3d};
pub const RT_LIGHT_POINT: u32 = 0;
pub const RT_LIGHT_DIRECTIONAL: u32 = 1;
pub const RT_LIGHT_SPOT: u32 = 2;
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct RtVertex {
pub position: [f32; 3],
pub u: f32,
pub normal: [f32; 3],
pub v: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct RtTriangle {
pub v0: u32,
pub v1: u32,
pub v2: u32,
pub material_id: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct RtEmitter {
pub p0: [f32; 3],
pub _pad0: f32,
pub p1: [f32; 3],
pub _pad1: f32,
pub p2: [f32; 3],
pub _pad2: f32,
pub emission: [f32; 3],
pub _pad3: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct RtMaterial {
pub base_color: [f32; 4],
pub emissive: [f32; 4],
pub metallic: f32,
pub roughness: f32,
pub ior: f32,
pub transmission: f32,
pub specular_tint: [f32; 3],
pub bsdf_type: u32,
pub subsurface: f32,
pub subsurface_radius: f32,
pub _pad0: f32,
pub _pad1: f32,
pub albedo_tex: i32,
pub normal_tex: i32,
pub mr_tex: i32,
pub emissive_tex: i32,
}
impl Default for RtMaterial {
fn default() -> Self {
RtMaterial {
base_color: [1.0, 1.0, 1.0, 1.0],
emissive: [0.0, 0.0, 0.0, 1.0],
metallic: 0.0,
roughness: 0.5,
ior: 1.5,
transmission: 0.0,
specular_tint: [1.0, 1.0, 1.0],
bsdf_type: 0,
subsurface: 0.0,
subsurface_radius: 0.0,
_pad0: 0.0,
_pad1: 0.0,
albedo_tex: -1,
normal_tex: -1,
mr_tex: -1,
emissive_tex: -1,
}
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct RtLight {
pub position: [f32; 3],
pub light_type: u32,
pub direction: [f32; 3],
pub intensity: f32,
pub color: [f32; 3],
pub attenuation_radius: f32,
pub inner_cone_cos: f32,
pub outer_cone_cos: f32,
pub radius: f32,
pub _pad: f32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable)]
pub struct RtInstance {
pub world_to_object: [[f32; 4]; 4],
pub object_to_world: [[f32; 4]; 4],
pub mesh_id: u32,
pub material_id: u32,
pub node_offset: u32,
pub tri_offset: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, Pod, Zeroable)]
pub struct RtMeshDesc {
pub node_offset: u32,
pub tri_offset: u32,
pub _pad: [u32; 2],
}
#[derive(Copy, Clone, Debug, Default)]
pub struct RtMeshRange {
pub vert_start: u32,
pub vert_count: u32,
pub tri_start: u32,
pub tri_count: u32,
}
#[derive(Default)]
pub struct RtScene {
pub mesh_vertices: Vec<RtVertex>,
pub mesh_triangles: Vec<RtTriangle>,
pub mesh_ranges: Vec<RtMeshRange>,
pub instances: Vec<RtInstance>,
pub materials: Vec<RtMaterial>,
pub lights: Vec<RtLight>,
pub emitters: Vec<RtEmitter>,
pub textures: Vec<std::sync::Arc<crate::resource::Texture>>,
pub ambient: f32,
pub hash: u64,
}
impl RtScene {
pub fn is_empty(&self) -> bool {
self.mesh_triangles.is_empty()
}
}
struct Fnv(u64);
impl Fnv {
#[inline]
fn new() -> Self {
Fnv(0xcbf29ce484222325)
}
#[inline]
fn write_u32(&mut self, v: u32) {
for b in v.to_le_bytes() {
self.0 ^= b as u64;
self.0 = self.0.wrapping_mul(0x100000001b3);
}
}
#[inline]
fn write_f32(&mut self, v: f32) {
let bits = if v == 0.0 { 0 } else { v.to_bits() };
self.write_u32(bits);
}
#[inline]
fn write_vec3(&mut self, v: Vec3) {
self.write_f32(v.x);
self.write_f32(v.y);
self.write_f32(v.z);
}
}
pub fn gather(scene: &SceneNode3d, lights: &LightCollection) -> RtScene {
let mut out = RtScene {
ambient: lights.ambient,
..Default::default()
};
let mut hasher = Fnv::new();
scene.apply_to_visible_scene_nodes_recursive(&mut |node| {
let pose = node.world_pose();
let scale = node.world_scale();
let data = node.data();
let Some(obj) = data.object() else {
return;
};
if !obj.data().surface_rendering_active() {
return;
}
let mesh = obj.mesh().borrow();
let coords_lock = mesh.coords().read().unwrap();
let faces_lock = mesh.faces().read().unwrap();
let normals_lock = mesh.normals().read().unwrap();
let uvs_lock = mesh.uvs().read().unwrap();
let (Some(coords), Some(faces)) = (coords_lock.data().as_ref(), faces_lock.data().as_ref())
else {
return;
};
if coords.is_empty() || faces.is_empty() {
return;
}
let normals = normals_lock.data().as_ref();
let uvs = uvs_lock.data().as_ref();
let odata = obj.data();
let color = odata.color();
let emissive = odata.emissive();
let tint = odata.specular_tint();
let instances = obj.instances().borrow();
let num_instances = instances.len();
if num_instances == 0 {
return;
}
let inst_positions = instances.positions.data().as_ref();
let inst_deformations = instances.deformations.data().as_ref();
let inst_colors = instances.colors.data().as_ref();
let mut push_tex = |tex: Option<&std::sync::Arc<crate::resource::Texture>>| -> i32 {
match tex {
Some(t) => {
let idx = out.textures.len() as i32;
out.textures.push(t.clone());
idx
}
None => -1,
}
};
let albedo_tex = push_tex(Some(odata.texture()));
let normal_tex = push_tex(odata.normal_map());
let mr_tex = push_tex(odata.metallic_roughness_map());
let emissive_tex = push_tex(odata.emissive_map());
let base_material = RtMaterial {
base_color: [color.r, color.g, color.b, color.a],
emissive: [emissive.r, emissive.g, emissive.b, 1.0],
metallic: odata.metallic(),
roughness: odata.roughness(),
ior: odata.ior(),
transmission: odata.transmission(),
specular_tint: [tint.r, tint.g, tint.b],
bsdf_type: odata.bsdf().tag(),
subsurface: odata.subsurface(),
subsurface_radius: odata.subsurface_radius(),
_pad0: 0.0,
_pad1: 0.0,
albedo_tex,
normal_tex,
mr_tex,
emissive_tex,
};
let emissive_obj = emissive.r + emissive.g + emissive.b > 1.0e-4;
let emission = [emissive.r, emissive.g, emissive.b];
let instance_transform = |inst: usize| -> Mat4 {
let inst_pos = inst_positions
.and_then(|p| p.get(inst))
.copied()
.unwrap_or(Vec3::ZERO);
let deform = match inst_deformations {
Some(d) if d.len() >= inst * 3 + 3 => {
Mat3::from_cols(d[inst * 3], d[inst * 3 + 1], d[inst * 3 + 2])
}
_ => Mat3::IDENTITY,
};
Mat4::from_translation(pose.translation + inst_pos)
* Mat4::from_quat(pose.rotation)
* Mat4::from_mat3(deform)
* Mat4::from_scale(scale)
};
let instance_material = |inst: usize| -> RtMaterial {
let inst_color = inst_colors
.and_then(|c| c.get(inst))
.copied()
.unwrap_or([1.0; 4]);
let mut mat = base_material;
mat.base_color = [
color.r * inst_color[0],
color.g * inst_color[1],
color.b * inst_color[2],
color.a * inst_color[3],
];
mat
};
{
let mesh_id = out.mesh_ranges.len() as u32;
let vert_start = out.mesh_vertices.len() as u32;
for (i, &local_pos) in coords.iter().enumerate() {
let local_n = normals.and_then(|n| n.get(i)).copied().unwrap_or(Vec3::Y);
let uv = uvs
.and_then(|u| u.get(i))
.copied()
.unwrap_or(glamx::Vec2::ZERO);
out.mesh_vertices.push(RtVertex {
position: [local_pos.x, local_pos.y, local_pos.z],
u: uv.x,
normal: [local_n.x, local_n.y, local_n.z],
v: uv.y,
});
}
let tri_start = out.mesh_triangles.len() as u32;
for f in faces {
out.mesh_triangles.push(RtTriangle {
v0: vert_start + f[0],
v1: vert_start + f[1],
v2: vert_start + f[2],
material_id: 0, });
}
out.mesh_ranges.push(RtMeshRange {
vert_start,
vert_count: coords.len() as u32,
tri_start,
tri_count: faces.len() as u32,
});
for inst in 0..num_instances {
let m = instance_transform(inst);
let material_id = out.materials.len() as u32;
out.materials.push(instance_material(inst));
out.instances.push(RtInstance {
world_to_object: m.inverse().to_cols_array_2d(),
object_to_world: m.to_cols_array_2d(),
mesh_id,
material_id,
node_offset: 0,
tri_offset: 0,
});
if emissive_obj {
for f in faces {
let p0 = m.transform_point3(coords[f[0] as usize]);
let p1 = m.transform_point3(coords[f[1] as usize]);
let p2 = m.transform_point3(coords[f[2] as usize]);
out.emitters.push(RtEmitter {
p0: p0.to_array(),
_pad0: 0.0,
p1: p1.to_array(),
_pad1: 0.0,
p2: p2.to_array(),
_pad2: 0.0,
emission,
_pad3: 0.0,
});
}
}
}
}
hash_object(&mut hasher, pose, scale, odata, coords.len(), faces.len());
hash_instances(&mut hasher, &instances);
});
for cl in &lights.lights {
out.lights.push(collected_to_rt(cl));
hash_light(&mut hasher, cl);
}
hasher.write_f32(lights.ambient);
out.hash = hasher.0;
out
}
pub fn scene_hash(scene: &SceneNode3d, lights: &LightCollection) -> u64 {
let mut hasher = Fnv::new();
scene.apply_to_visible_scene_nodes_recursive(&mut |node| {
let pose = node.world_pose();
let scale = node.world_scale();
let data = node.data();
let Some(obj) = data.object() else {
return;
};
if !obj.data().surface_rendering_active() {
return;
}
let mesh = obj.mesh().borrow();
let ncoords = mesh.coords().read().unwrap().len();
let nfaces = mesh.faces().read().unwrap().len();
if ncoords == 0 || nfaces == 0 {
return;
}
let odata = obj.data();
hash_object(&mut hasher, pose, scale, odata, ncoords, nfaces);
hash_instances(&mut hasher, &obj.instances().borrow());
});
for cl in &lights.lights {
hash_light(&mut hasher, cl);
}
hasher.write_f32(lights.ambient);
hasher.0
}
fn hash_instances(h: &mut Fnv, instances: &InstancesBuffer3d) {
h.write_u32(instances.len() as u32);
if let Some(p) = instances.positions.data() {
for v in p {
h.write_vec3(*v);
}
}
if let Some(d) = instances.deformations.data() {
for v in d {
h.write_vec3(*v);
}
}
if let Some(c) = instances.colors.data() {
for v in c {
for x in v {
h.write_f32(*x);
}
}
}
}
fn hash_object(
h: &mut Fnv,
pose: glamx::Pose3,
scale: Vec3,
odata: &crate::scene::ObjectData3d,
ncoords: usize,
nfaces: usize,
) {
h.write_vec3(pose.translation);
h.write_f32(pose.rotation.x);
h.write_f32(pose.rotation.y);
h.write_f32(pose.rotation.z);
h.write_f32(pose.rotation.w);
h.write_vec3(scale);
h.write_f32(odata.metallic());
h.write_f32(odata.roughness());
let color = odata.color();
let emissive = odata.emissive();
let tint = odata.specular_tint();
for c in [
color.r, color.g, color.b, color.a, emissive.r, emissive.g, emissive.b,
] {
h.write_f32(c);
}
h.write_u32(odata.bsdf().tag());
h.write_f32(odata.ior());
h.write_f32(odata.transmission());
for c in [tint.r, tint.g, tint.b] {
h.write_f32(c);
}
h.write_f32(odata.subsurface());
h.write_f32(odata.subsurface_radius());
let tex_id = |t: Option<&std::sync::Arc<crate::resource::Texture>>| -> u32 {
t.map(|a| std::sync::Arc::as_ptr(a) as usize as u32)
.unwrap_or(0)
};
h.write_u32(std::sync::Arc::as_ptr(odata.texture()) as usize as u32);
h.write_u32(tex_id(odata.normal_map()));
h.write_u32(tex_id(odata.metallic_roughness_map()));
h.write_u32(tex_id(odata.emissive_map()));
h.write_u32(ncoords as u32);
h.write_u32(nfaces as u32);
}
fn hash_light(h: &mut Fnv, cl: &CollectedLight) {
h.write_vec3(cl.world_position);
h.write_vec3(cl.world_direction);
h.write_vec3(cl.color);
h.write_f32(cl.intensity);
h.write_f32(cl.radius);
}
fn collected_to_rt(cl: &CollectedLight) -> RtLight {
let (light_type, attenuation_radius, inner_cone_cos, outer_cone_cos) = match cl.light_type {
LightType::Point { attenuation_radius } => (RT_LIGHT_POINT, attenuation_radius, 1.0, 0.0),
LightType::Directional(_) => (RT_LIGHT_DIRECTIONAL, 0.0, 1.0, 0.0),
LightType::Spot {
inner_cone_angle,
outer_cone_angle,
attenuation_radius,
} => (
RT_LIGHT_SPOT,
attenuation_radius,
inner_cone_angle.cos(),
outer_cone_angle.cos(),
),
};
RtLight {
position: [
cl.world_position.x,
cl.world_position.y,
cl.world_position.z,
],
light_type,
direction: [
cl.world_direction.x,
cl.world_direction.y,
cl.world_direction.z,
],
intensity: cl.intensity,
color: [cl.color.x, cl.color.y, cl.color.z],
attenuation_radius,
inner_cone_cos,
outer_cone_cos,
radius: cl.radius,
_pad: 0.0,
}
}