pub mod animation;
pub mod camera;
pub mod dynamic;
pub mod light;
pub mod material;
pub mod texture;
use std::collections::HashMap;
use animation::AnimResolveMaps;
use anyhow::{anyhow, Result};
use awsm_renderer::animation::AnimationClipKey;
use awsm_renderer::lights::LightKey;
use awsm_renderer::materials::unlit::UnlitMaterial;
use awsm_renderer::materials::{Material, MaterialAlphaMode, MaterialKey};
use awsm_renderer::meshes::MeshKey;
use awsm_renderer::raw_mesh::RawMeshData;
use awsm_renderer::transforms::{Transform, TransformKey};
use awsm_renderer::{AwsmRenderer, LoadPhase};
use awsm_renderer_core::texture::mipmap::MipmapTextureKind;
use awsm_renderer_gltf::loader::GltfLoader;
use awsm_renderer_gltf::{AwsmRendererGltfExt, GltfMaterialSource, PopulateGltfOpts};
use awsm_scene::{
mesh_glb_filename, AssetId, AssetSource, EditorNode, MaterialInstance, MaterialShading, NodeId,
NodeKind, RuntimeMesh, Scene, Trs, ASSETS_DIR,
};
use glam::{Quat, Vec3};
#[derive(Default, Debug)]
pub struct LoadedScene {
pub meshes: Vec<MeshKey>,
pub lights: Vec<LightKey>,
pub clips: Vec<AnimationClipKey>,
}
pub async fn populate_awsm_scene(
renderer: &mut AwsmRenderer,
scene: &Scene,
assets: &HashMap<String, Vec<u8>>,
mut on_phase: impl FnMut(LoadPhase),
) -> Result<LoadedScene> {
let custom = dynamic::register_custom_materials(renderer, scene, assets);
let placeholder = insert_placeholder_material(renderer);
let mut maps = AnimResolveMaps::default();
let renderables = collect_renderables(&scene.nodes);
let total = renderables.len();
for (i, (id, material)) in renderables.iter().enumerate() {
on_phase(LoadPhase::BuildingMaterials { done: i, total });
let key = resolve_material(renderer, material.as_ref(), placeholder, assets, &custom).await;
maps.node_materials.insert(*id, key);
if let Some(inst) = material.as_ref() {
if custom.contains_key(&inst.asset) {
maps.custom_materials.entry(inst.asset).or_insert(key);
}
}
}
on_phase(LoadPhase::BuildingMaterials { done: total, total });
maps.custom_shaders = custom;
on_phase(LoadPhase::UploadingTextures);
renderer.finalize_gpu_textures().await?;
let mut loaded = LoadedScene::default();
let mut uploaded = 0usize;
for node in &scene.nodes {
materialize(
renderer,
scene,
node,
None,
assets,
&mut maps,
placeholder,
&mut on_phase,
&mut uploaded,
total,
&mut loaded,
)
.await?;
}
loaded.clips = animation::load_animations(renderer, scene, &maps);
renderer
.wait_for_pipelines_ready_with_progress(|cp| on_phase(LoadPhase::CompilingPipelines(cp)))
.await?;
Ok(loaded)
}
fn collect_renderables(nodes: &[EditorNode]) -> Vec<(NodeId, &Option<MaterialInstance>)> {
let mut out = Vec::new();
fn walk<'a>(nodes: &'a [EditorNode], out: &mut Vec<(NodeId, &'a Option<MaterialInstance>)>) {
for n in nodes {
match &n.kind {
NodeKind::Mesh { material, .. } | NodeKind::SkinnedMesh { material, .. } => {
out.push((n.id, material));
}
_ => {}
}
walk(&n.children, out);
}
}
walk(nodes, &mut out);
out
}
#[allow(clippy::too_many_arguments)]
async fn materialize(
renderer: &mut AwsmRenderer,
scene: &Scene,
node: &EditorNode,
parent: Option<TransformKey>,
assets: &HashMap<String, Vec<u8>>,
maps: &mut AnimResolveMaps,
placeholder: MaterialKey,
on_phase: &mut dyn FnMut(LoadPhase),
uploaded: &mut usize,
total: usize,
loaded: &mut LoadedScene,
) -> Result<()> {
let tk = renderer
.transforms
.insert(trs_to_transform(&node.transform), parent);
maps.transforms.insert(node.id, tk);
let mat = maps
.node_materials
.get(&node.id)
.copied()
.unwrap_or(placeholder);
match &node.kind {
NodeKind::Mesh { mesh, .. } => {
if let Some(entry) = scene.assets.get(mesh.0) {
match &entry.source {
AssetSource::Mesh(RuntimeMesh::Primitive(shape)) => {
let md = awsm_meshgen::primitive_mesh(shape);
let key = renderer.add_raw_mesh(mesh_data_to_raw(md), tk, mat)?;
maps.meshes.entry(node.id).or_insert(key);
loaded.meshes.push(key);
}
AssetSource::Mesh(RuntimeMesh::Glb) => {
let (keys, _) = load_glb_under(
renderer,
assets,
&mesh_glb_filename(mesh.0),
Some(tk),
mat,
)
.await?;
if let Some(&first) = keys.first() {
maps.meshes.entry(node.id).or_insert(first);
}
loaded.meshes.extend(keys);
}
_ => {}
}
}
*uploaded += 1;
on_phase(LoadPhase::UploadingMeshes {
done: *uploaded,
total,
});
}
NodeKind::SkinnedMesh { skin, .. } => {
let (keys, node_index_transforms) =
load_glb_under(renderer, assets, &mesh_glb_filename(skin.source), None, mat)
.await?;
if let Some(&first) = keys.first() {
maps.meshes.entry(node.id).or_insert(first);
}
for j in &skin.joints {
if let Some(&tk) = node_index_transforms.get(&(j.index as usize)) {
maps.skin_joints.insert(j.node, tk);
}
}
loaded.meshes.extend(keys);
*uploaded += 1;
on_phase(LoadPhase::UploadingMeshes {
done: *uploaded,
total,
});
}
NodeKind::Light(cfg) => {
let pos = Vec3::from_array(node.transform.translation);
let dir = (Quat::from_array(node.transform.rotation) * Vec3::NEG_Z).normalize_or_zero();
let lt = light::light_from_config(cfg, pos, dir);
let shadow = light::light_shadow_params_from_config(cfg.shadow());
let casts = shadow.cast;
if let Ok(k) = renderer.insert_light(lt, Some(shadow)) {
renderer.lights.bind_transform(k, tk);
maps.lights.insert(node.id, k);
loaded.lights.push(k);
}
if casts {
renderer.ensure_shadow_pipelines_compiled().await?;
}
}
NodeKind::Camera(cfg) => {
let ck = renderer
.cameras
.insert(camera::camera_params_from_config(cfg));
maps.cameras.insert(node.id, ck);
}
_ => {}
}
for child in &node.children {
Box::pin(materialize(
renderer,
scene,
child,
Some(tk),
assets,
maps,
placeholder,
on_phase,
uploaded,
total,
loaded,
))
.await?;
}
Ok(())
}
async fn load_glb_under(
renderer: &mut AwsmRenderer,
assets: &HashMap<String, Vec<u8>>,
leaf: &str,
parent: Option<TransformKey>,
material: MaterialKey,
) -> Result<(Vec<MeshKey>, HashMap<usize, TransformKey>)> {
let key = format!("{ASSETS_DIR}/{leaf}");
let bytes = assets
.get(&key)
.ok_or_else(|| anyhow!("bundle is missing mesh glb `{key}`"))?;
use awsm_renderer_gltf::data::GltfGeometryOverride;
let transparent = renderer.materials.is_transparency_pass(material);
let geometry_override = if transparent {
GltfGeometryOverride::Transparent
} else {
GltfGeometryOverride::FromMaterial
};
let hints = awsm_renderer_gltf::data::GltfDataHints::default()
.with_geometry_override(geometry_override);
let data = GltfLoader::from_glb_bytes(bytes)
.await?
.into_data(Some(hints))?;
let ctx = renderer
.populate_gltf_with(
data,
PopulateGltfOpts {
scene: None,
parent_transform: parent,
material_source: GltfMaterialSource::Single(material),
finalize_textures: false,
},
)
.await?;
let (keys, node_index_transforms): (Vec<MeshKey>, HashMap<usize, TransformKey>) = {
let lookups = ctx.key_lookups.lock().unwrap();
let keys = lookups.all_mesh_keys.values().flatten().copied().collect();
(keys, lookups.node_index_to_transform.clone())
};
if transparent {
for &k in &keys {
let _ = renderer.set_mesh_shadow_flags(
k,
awsm_renderer::shadows::MeshShadowFlags {
cast: false,
receive: false,
},
);
}
}
Ok((keys, node_index_transforms))
}
fn trs_to_transform(trs: &Trs) -> Transform {
Transform {
translation: Vec3::from_array(trs.translation),
rotation: Quat::from_array(trs.rotation),
scale: Vec3::from_array(trs.scale),
}
}
fn mesh_data_to_raw(md: awsm_meshgen::MeshData) -> RawMeshData {
RawMeshData {
positions: md.positions,
normals: md.normals,
uvs: md.uvs,
uvs1: None,
colors: md.colors,
indices: md.indices,
}
}
async fn resolve_material(
renderer: &mut AwsmRenderer,
instance: Option<&MaterialInstance>,
placeholder: MaterialKey,
assets: &HashMap<String, Vec<u8>>,
custom: &HashMap<AssetId, awsm_materials::MaterialShaderId>,
) -> MaterialKey {
let Some(inst) = instance else {
return placeholder;
};
if let Some(&shader_id) = custom.get(&inst.asset) {
if let Some(mat) = dynamic::build_custom_material(renderer, shader_id, inst, assets).await {
renderer.upload_dynamic_material_buffers(&mat);
return renderer.materials.insert(
mat,
&renderer.textures,
&renderer.dynamic_materials,
&renderer.extras_pool,
);
}
return placeholder;
}
let def = &inst.inline;
let material = match def.shading {
MaterialShading::Pbr => {
let alpha = material::alpha_mode_of(def);
let mut pbr = material::material_to_pbr(def, alpha, None);
use MipmapTextureKind as K;
if let Some(t) = &def.base_color_texture {
pbr.base_color_tex =
texture::load_texture(renderer, assets, t, true, K::Albedo).await;
}
if let Some(t) = &def.metallic_roughness_texture {
pbr.metallic_roughness_tex =
texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &def.normal_texture {
pbr.normal_tex = texture::load_texture(renderer, assets, t, false, K::Normal).await;
}
if let Some(t) = &def.occlusion_texture {
pbr.occlusion_tex =
texture::load_texture(renderer, assets, t, false, K::Occlusion).await;
}
if let Some(t) = &def.emissive_texture {
pbr.emissive_tex =
texture::load_texture(renderer, assets, t, true, K::Emissive).await;
}
bind_extension_textures(renderer, assets, def, &mut pbr).await;
Material::Pbr(Box::new(pbr))
}
_ => material::material_to_renderer(def),
};
renderer.materials.insert(
material,
&renderer.textures,
&renderer.dynamic_materials,
&renderer.extras_pool,
)
}
async fn bind_extension_textures(
renderer: &mut AwsmRenderer,
assets: &HashMap<String, Vec<u8>>,
def: &awsm_scene::MaterialDef,
pbr: &mut awsm_renderer::materials::pbr::PbrMaterial,
) {
use MipmapTextureKind as K;
let ext = &def.extensions;
if let (Some(e), Some(p)) = (ext.specular.as_ref(), pbr.specular.as_mut()) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &e.color_tex {
p.color_tex = texture::load_texture(renderer, assets, t, true, K::Albedo).await;
}
}
if let (Some(e), Some(p)) = (ext.transmission.as_ref(), pbr.transmission.as_mut()) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
}
if let (Some(e), Some(p)) = (
ext.diffuse_transmission.as_ref(),
pbr.diffuse_transmission.as_mut(),
) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &e.color_tex {
p.color_tex = texture::load_texture(renderer, assets, t, true, K::Albedo).await;
}
}
if let (Some(e), Some(p)) = (ext.volume.as_ref(), pbr.volume.as_mut()) {
if let Some(t) = &e.thickness_tex {
p.thickness_tex =
texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
}
if let (Some(e), Some(p)) = (ext.clearcoat.as_ref(), pbr.clearcoat.as_mut()) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &e.roughness_tex {
p.roughness_tex =
texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &e.normal_tex {
p.normal_tex = texture::load_texture(renderer, assets, t, false, K::Normal).await;
}
}
if let (Some(e), Some(p)) = (ext.sheen.as_ref(), pbr.sheen.as_mut()) {
if let Some(t) = &e.color_tex {
p.color_tex = texture::load_texture(renderer, assets, t, true, K::Albedo).await;
}
if let Some(t) = &e.roughness_tex {
p.roughness_tex =
texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
}
if let (Some(e), Some(p)) = (ext.anisotropy.as_ref(), pbr.anisotropy.as_mut()) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::Normal).await;
}
}
if let (Some(e), Some(p)) = (ext.iridescence.as_ref(), pbr.iridescence.as_mut()) {
if let Some(t) = &e.tex {
p.tex = texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
if let Some(t) = &e.thickness_tex {
p.thickness_tex =
texture::load_texture(renderer, assets, t, false, K::MetallicRoughness).await;
}
}
}
fn insert_placeholder_material(renderer: &mut AwsmRenderer) -> MaterialKey {
let mut m = UnlitMaterial::new(MaterialAlphaMode::Opaque, false);
m.base_color_factor = [1.0, 0.0, 1.0, 1.0];
renderer.materials.insert(
Material::Unlit(m),
&renderer.textures,
&renderer.dynamic_materials,
&renderer.extras_pool,
)
}