use crate::image::Image;
use crate::scene::{MaterialId, Scene, TriRef};
use crate::Result;
use glam::camera::rh::proj::directx as proj;
use glam::camera::rh::view::look_at_mat4;
use glam::{Mat4, Vec2, Vec3, Vec4};
use std::collections::BTreeMap;
pub mod channels {
pub const BASE_COLOR: &str = "base_color";
pub const NORMAL: &str = "normal";
pub const ORM: &str = "orm";
pub const EMISSIVE: &str = "emissive";
pub const OPACITY: &str = "opacity";
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub enum MaterialClass {
#[default]
Opaque,
Masked { cutoff: f32 },
Blended,
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct MaterialInfo {
pub class: MaterialClass,
pub two_sided: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChannelEncoding {
Color,
Data,
NormalWorld,
NormalTangent,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChannelImage {
pub image: Image,
pub encoding: ChannelEncoding,
}
pub type ChannelImages = BTreeMap<String, ChannelImage>;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SurfaceSample {
pub instance: u32,
pub material: MaterialId,
pub position: Vec3,
pub normal: Vec3,
pub tangent: Vec4,
pub uv: [Vec2; 2],
pub color: Vec4,
}
#[derive(Clone, Debug, PartialEq)]
pub struct SurfaceBakeRequest {
pub width: u32,
pub height: u32,
pub samples: Vec<Option<SurfaceSample>>,
pub channels: Vec<String>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct View {
pub view: Mat4,
pub proj: Mat4,
pub width: u32,
pub height: u32,
}
impl View {
pub fn perspective(
eye: Vec3,
target: Vec3,
fov_y: f32,
width: u32,
height: u32,
near: f32,
far: f32,
) -> View {
let up = if (target - eye)
.normalize_or_zero()
.cross(Vec3::Y)
.length_squared()
< 1e-6
{
Vec3::Z
} else {
Vec3::Y
};
View {
view: look_at_mat4(eye, target, up),
proj: proj::perspective(fov_y, width as f32 / height as f32, near, far),
width,
height,
}
}
pub fn orthographic(
center: Vec3,
dir: Vec3,
up: Vec3,
half_width: f32,
half_height: f32,
depth: f32,
width: u32,
height: u32,
) -> View {
let dir = dir.normalize();
let up = if dir.cross(up).length_squared() < 1e-8 {
if dir.y.abs() < 0.9 {
Vec3::Y
} else {
Vec3::Z
}
} else {
up
};
let eye = center - dir * depth;
View {
view: look_at_mat4(eye, center, up),
proj: proj::orthographic(
-half_width,
half_width,
-half_height,
half_height,
0.0,
2.0 * depth,
),
width,
height,
}
}
pub fn eye(&self) -> Vec3 {
self.view.inverse().w_axis.truncate()
}
pub fn view_proj(&self) -> Mat4 {
self.proj * self.view
}
pub fn ray(&self, x: u32, y: u32) -> (Vec3, Vec3) {
let inv = self.view_proj().inverse();
let nx = (x as f32 + 0.5) / self.width as f32 * 2.0 - 1.0;
let ny = 1.0 - (y as f32 + 0.5) / self.height as f32 * 2.0;
let near = inv.project_point3(Vec3::new(nx, ny, 0.0));
let far = inv.project_point3(Vec3::new(nx, ny, 1.0));
(near, (far - near).normalize_or_zero())
}
}
#[derive(Clone, Debug)]
pub struct ViewRenderRequest<'a> {
pub scene: &'a Scene,
pub views: Vec<View>,
pub channels: Vec<String>,
pub lit: bool,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ViewImages {
pub channels: ChannelImages,
pub depth: Image,
}
#[derive(Clone, Debug)]
pub struct IdRenderRequest<'a> {
pub scene: &'a Scene,
pub views: Vec<View>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct IdImage {
pub width: u32,
pub height: u32,
pub ids: Vec<Option<TriRef>>,
}
#[derive(Clone, Debug, PartialEq)]
pub enum BakedMaterialKind {
Surface,
OctahedralImpostor {
frames_per_side: u32,
hemisphere: bool,
},
Flipbook { frames: u32 },
Cards,
}
#[derive(Clone, Debug, PartialEq)]
pub struct BakedMaterialDesc {
pub name: String,
pub kind: BakedMaterialKind,
pub class: MaterialClass,
pub textures: ChannelImages,
pub parameters: BTreeMap<String, f32>,
}
pub trait RenderHost {
fn material_info(&self, material: MaterialId) -> MaterialInfo;
fn bake_surface(&mut self, req: &SurfaceBakeRequest) -> Result<ChannelImages>;
fn render_views(&mut self, req: &ViewRenderRequest) -> Result<Vec<ViewImages>>;
fn render_views_behind(
&mut self,
req: &ViewRenderRequest,
behind: &[Image],
bias: f32,
) -> Result<Vec<ViewImages>> {
let _ = (req, behind, bias);
Err(crate::Error::Unsupported("render_views_behind"))
}
fn render_ids(&mut self, req: &IdRenderRequest) -> Result<Vec<IdImage>> {
let infos: Vec<MaterialInfo> = req
.scene
.materials
.iter()
.map(|m| self.material_info(m.id))
.collect();
Ok(crate::raster::cpu_render_ids(req.scene, &req.views, &infos))
}
fn create_material(&mut self, desc: &BakedMaterialDesc) -> Result<MaterialId>;
}
#[derive(Clone, Copy, Debug, Default)]
pub struct GeometryOnlyHost;
impl RenderHost for GeometryOnlyHost {
fn material_info(&self, _material: MaterialId) -> MaterialInfo {
MaterialInfo::default()
}
fn bake_surface(&mut self, _req: &SurfaceBakeRequest) -> Result<ChannelImages> {
Err(crate::Error::Unsupported("bake_surface"))
}
fn render_views(&mut self, _req: &ViewRenderRequest) -> Result<Vec<ViewImages>> {
Err(crate::Error::Unsupported("render_views"))
}
fn create_material(&mut self, _desc: &BakedMaterialDesc) -> Result<MaterialId> {
Err(crate::Error::Unsupported("create_material"))
}
}