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 reflectance: f32,
pub light_layers: u32,
pub attenuation_color: [f32; 3],
pub attenuation_distance: f32,
pub clearcoat: f32,
pub clearcoat_roughness: f32,
pub casts_shadows: u32,
pub _pad0: 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,
reflectance: 0.5,
light_layers: u32::MAX,
attenuation_color: [1.0, 1.0, 1.0],
attenuation_distance: -1.0,
clearcoat: 0.0,
clearcoat_roughness: 0.0,
casts_shadows: 1,
_pad0: 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 layers: u32,
pub casts_shadows: u32,
pub _pad0: f32,
pub _pad1: f32,
pub _pad2: 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 ambient_color: [f32; 3],
pub fog_color: [f32; 4],
pub fog_params: [f32; 4],
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, render_layers: u32) -> RtScene {
let mut out = RtScene {
ambient: lights.ambient,
ambient_color: [
lights.ambient_color.r,
lights.ambient_color.g,
lights.ambient_color.b,
],
fog_color: [
lights.fog.color.r,
lights.fog.color.g,
lights.fog.color.b,
lights.fog.color.a,
],
fog_params: lights.fog.params(),
..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;
}
if obj.data().render_layers() & render_layers == 0 {
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 atten = odata.attenuation_color();
let atten_dist = odata.attenuation_distance();
let atten_dist = if atten_dist.is_finite() && atten_dist > 0.0 {
atten_dist
} else {
-1.0
};
let use_skin = odata.has_skin()
&& mesh.has_skin_vertices()
&& odata.skin().is_some_and(|s| !s.palette().is_empty());
let morph_weights = odata.morph_weights();
let morphed: Option<(Vec<Vec3>, Option<Vec<Vec3>>)> =
if mesh.has_morph() && morph_weights.iter().any(|&w| w != 0.0) {
cpu_morph(&mesh, coords, normals, morph_weights)
} else {
None
};
let local_pos = |i: usize| -> Vec3 {
match &morphed {
Some((p, _)) => p[i],
None => coords[i],
}
};
let local_nrm = |i: usize| -> Vec3 {
match &morphed {
Some((_, Some(n))) => n[i],
_ => normals.and_then(|n| n.get(i)).copied().unwrap_or(Vec3::Y),
}
};
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(),
reflectance: odata.reflectance(),
light_layers: odata.light_layers(),
attenuation_color: [atten.r, atten.g, atten.b],
attenuation_distance: atten_dist,
clearcoat: odata.clearcoat(),
clearcoat_roughness: odata.clearcoat_roughness(),
casts_shadows: odata.casts_shadows() as u32,
_pad0: 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 skinned_verts: Option<Vec<(Vec3, Vec3)>> = if use_skin {
let skin = odata.skin().unwrap();
let palette = skin.palette();
let joints_lock = mesh.skin_joints().unwrap().read().unwrap();
let weights_lock = mesh.skin_weights().unwrap().read().unwrap();
match (joints_lock.data().as_ref(), weights_lock.data().as_ref()) {
(Some(joints), Some(weights)) => Some(
(0..coords.len())
.map(|i| {
let local_n = local_nrm(i);
let m = skin_matrix(palette, joints[i], weights[i]);
let wp = m.transform_point3(local_pos(i));
let wn = Mat3::from_mat4(m) * local_n;
let wn = if wn.length_squared() > 1.0e-12 {
wn.normalize()
} else {
local_n
};
(wp, wn)
})
.collect(),
),
_ => None,
}
} else {
None
};
let skinned = skinned_verts.is_some();
let mesh_id = out.mesh_ranges.len() as u32;
let vert_start = out.mesh_vertices.len() as u32;
for i in 0..coords.len() {
let (pos, normal) = match &skinned_verts {
Some(sv) => sv[i],
None => (local_pos(i), local_nrm(i)),
};
let uv = uvs
.and_then(|u| u.get(i))
.copied()
.unwrap_or(glamx::Vec2::ZERO);
out.mesh_vertices.push(RtVertex {
position: [pos.x, pos.y, pos.z],
u: uv.x,
normal: [normal.x, normal.y, normal.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,
});
let instance_count = if skinned { 1 } else { num_instances };
for inst in 0..instance_count {
let m = if skinned {
Mat4::IDENTITY
} else {
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 world = |idx: u32| -> Vec3 {
match &skinned_verts {
Some(sv) => sv[idx as usize].0,
None => m.transform_point3(local_pos(idx as usize)),
}
};
let p0 = world(f[0]);
let p1 = world(f[1]);
let p2 = world(f[2]);
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);
hash_skin(&mut hasher, odata);
hash_morph(&mut hasher, odata);
});
for cl in &lights.lights {
out.lights.push(collected_to_rt(cl));
hash_light(&mut hasher, cl);
}
hash_scene_globals(&mut hasher, lights);
out.hash = hasher.0;
out
}
fn hash_scene_globals(h: &mut Fnv, lights: &LightCollection) {
h.write_f32(lights.ambient);
for c in [
lights.ambient_color.r,
lights.ambient_color.g,
lights.ambient_color.b,
lights.fog.color.r,
lights.fog.color.g,
lights.fog.color.b,
lights.fog.color.a,
] {
h.write_f32(c);
}
for p in lights.fog.params() {
h.write_f32(p);
}
}
pub fn scene_hash(scene: &SceneNode3d, lights: &LightCollection, render_layers: u32) -> 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;
}
if obj.data().render_layers() & render_layers == 0 {
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());
hash_skin(&mut hasher, odata);
hash_morph(&mut hasher, odata);
});
for cl in &lights.lights {
hash_light(&mut hasher, cl);
}
hash_scene_globals(&mut hasher, lights);
hasher.0
}
fn skin_matrix(palette: &[Mat4], joints: [u32; 4], weights: [f32; 4]) -> Mat4 {
let wsum = weights[0] + weights[1] + weights[2] + weights[3];
let inv = if wsum > 0.0 { 1.0 / wsum } else { 1.0 };
let get = |j: u32| palette.get(j as usize).copied().unwrap_or(Mat4::IDENTITY);
get(joints[0]) * (weights[0] * inv)
+ get(joints[1]) * (weights[1] * inv)
+ get(joints[2]) * (weights[2] * inv)
+ get(joints[3]) * (weights[3] * inv)
}
fn hash_skin(h: &mut Fnv, odata: &crate::scene::ObjectData3d) {
if let Some(skin) = odata.skin() {
for m in skin.palette() {
for c in m.to_cols_array() {
h.write_f32(c);
}
}
}
}
fn cpu_morph(
mesh: &crate::resource::GpuMesh3d,
coords: &[Vec3],
normals: Option<&Vec<Vec3>>,
weights: &[f32],
) -> Option<(Vec<Vec3>, Option<Vec<Vec3>>)> {
let nv = mesh.morph_vertex_count();
let nt = mesh.morph_target_count().min(weights.len());
let pos_lock = mesh.morph_positions()?.read().unwrap();
let mpos = pos_lock.data().as_ref()?;
let nrm_lock = mesh.morph_normals().map(|n| n.read().unwrap());
let mnrm = nrm_lock.as_ref().and_then(|g| g.data().as_ref());
let count = coords.len().min(nv);
let mut positions = coords.to_vec();
let mut out_normals = mnrm.is_some().then(|| match normals {
Some(n) => n.clone(),
None => vec![Vec3::Y; coords.len()],
});
for (t, &w) in weights.iter().take(nt).enumerate() {
if w == 0.0 {
continue;
}
let base = t * nv;
for v in 0..count {
let d = mpos[base + v];
positions[v] += w * Vec3::new(d[0], d[1], d[2]);
}
if let (Some(mn), Some(on)) = (mnrm, out_normals.as_mut()) {
for v in 0..count {
let d = mn[base + v];
on[v] += w * Vec3::new(d[0], d[1], d[2]);
}
}
}
Some((positions, out_normals))
}
fn hash_morph(h: &mut Fnv, odata: &crate::scene::ObjectData3d) {
for &w in odata.morph_weights() {
h.write_f32(w);
}
}
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());
h.write_f32(odata.reflectance());
h.write_u32(odata.light_layers());
h.write_u32(odata.casts_shadows() as u32);
h.write_f32(odata.clearcoat());
h.write_f32(odata.clearcoat_roughness());
let atten = odata.attenuation_color();
for c in [atten.r, atten.g, atten.b] {
h.write_f32(c);
}
h.write_f32(odata.attenuation_distance());
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);
h.write_u32(cl.layers);
h.write_u32(cl.casts_shadows as u32);
}
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,
layers: cl.layers,
casts_shadows: cl.casts_shadows as u32,
_pad0: 0.0,
_pad1: 0.0,
_pad2: 0.0,
}
}