use alloc::{vec, vec::Vec};
use core::fmt::Debug;
use crate::geom::{Tri, Vertex};
use crate::math::{
mat::{Mat4x4, RealToProj, RealToReal},
vary::Vary,
vec::{vec3, ProjVec4, Vec3},
};
use clip::{view_frustum, Clip, ClipVert};
use ctx::{Context, DepthSort, FaceCull};
use raster::tri_fill;
use shader::{FragmentShader, VertexShader};
use stats::Stats;
use target::Target;
pub mod batch;
pub mod cam;
pub mod clip;
pub mod ctx;
pub mod raster;
pub mod shader;
pub mod stats;
pub mod target;
pub mod tex;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Model;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct World;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct View;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Ndc;
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct Screen;
pub type ModelToWorld = RealToReal<3, Model, World>;
pub type WorldToView = RealToReal<3, World, View>;
pub type ModelToView = RealToReal<3, Model, View>;
pub type ModelToProj = RealToProj<Model>;
pub type ViewToProj = RealToProj<View>;
pub type NdcToScreen = RealToReal<3, Ndc, Screen>;
pub trait Shader<Vtx, Var, Uni>:
VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>> + FragmentShader<Var>
{
}
impl<S, Vtx, Var, Uni> Shader<Vtx, Var, Uni> for S where
S: VertexShader<Vtx, Uni, Output = Vertex<ProjVec4, Var>>
+ FragmentShader<Var>
{
}
pub fn render<Vtx: Clone, Var: Vary, Uni: Copy, Shd>(
tris: impl AsRef<[Tri<usize>]>,
verts: impl AsRef<[Vtx]>,
shader: &Shd,
uniform: Uni,
to_screen: Mat4x4<NdcToScreen>,
target: &mut impl Target,
ctx: &Context,
) where
Shd: Shader<Vtx, Var, Uni>,
{
let mut stats = Stats::start();
stats.calls = 1.0;
stats.prims.i += tris.as_ref().len();
stats.verts.i += verts.as_ref().len();
let verts: Vec<_> = verts
.as_ref()
.iter()
.cloned()
.map(|v| ClipVert::new(shader.shade_vertex(v, uniform)))
.collect();
let tris: Vec<_> = tris
.as_ref()
.iter()
.map(|Tri(vs)| Tri(vs.map(|i| verts[i].clone())))
.collect();
let mut clipped = vec![];
tris.clip(&view_frustum::PLANES, &mut clipped);
if let Some(d) = ctx.depth_sort {
depth_sort(&mut clipped, d);
}
for Tri(vs) in clipped {
let vs = vs.map(|v| {
let [x, y, _, w] = v.pos.0;
let pos = vec3(x, y, 1.0).z_div(w);
Vertex {
pos: to_screen.apply(&pos),
attrib: v.attrib.z_div(w),
}
});
match ctx.face_cull {
Some(FaceCull::Back) if is_backface(&vs) => continue,
Some(FaceCull::Front) if !is_backface(&vs) => continue,
_ => {}
}
stats.prims.o += 1;
stats.verts.o += 3;
tri_fill(vs, |scanline| {
stats.frags += target.rasterize(scanline, shader, ctx);
});
}
*ctx.stats.borrow_mut() += stats.finish();
}
fn depth_sort<A>(tris: &mut [Tri<ClipVert<A>>], d: DepthSort) {
tris.sort_unstable_by(|t, u| {
let z = t.0[0].pos.z() + t.0[1].pos.z() + t.0[2].pos.z();
let w = u.0[0].pos.z() + u.0[1].pos.z() + u.0[2].pos.z();
if d == DepthSort::FrontToBack {
z.total_cmp(&w)
} else {
w.total_cmp(&z)
}
});
}
fn is_backface<V>(vs: &[Vertex<Vec3<Screen>, V>]) -> bool {
let v = vs[1].pos - vs[0].pos;
let u = vs[2].pos - vs[0].pos;
v[0] * u[1] - v[1] * u[0] > 0.0
}