use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct SkinData {
pub bones: Vec<Bone>,
pub bone_matrices: Vec<[[f32; 4]; 4]>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Bone {
pub parent_index: Option<usize>,
pub inverse_bind_matrix: [[f32; 4]; 4],
}
#[derive(Debug, Clone, Copy)]
pub struct Camera {
pub eye: [f32; 3],
pub target: [f32; 3],
pub up: [f32; 3],
pub aspect: f32,
pub fovy: f32,
pub znear: f32,
pub zfar: f32,
}
impl Default for Camera {
fn default() -> Self {
Self {
eye: [0.0, 0.0, 5.0],
target: [0.0, 0.0, 0.0],
up: [0.0, 1.0, 0.0],
aspect: 1.0,
fovy: 45.0,
znear: 0.1,
zfar: 1000.0,
}
}
}
impl Camera {
pub fn view_matrix(&self) -> [[f32; 4]; 4] {
crate::math::mat4::look_at(self.eye, self.target, self.up)
}
pub fn projection_matrix(&self) -> [[f32; 4]; 4] {
crate::math::projection::perspective_degrees(self.fovy, self.aspect, self.znear, self.zfar)
}
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Default, Debug)]
pub struct Light {
pub position: [f32; 4],
pub color: [f32; 4],
}
#[repr(C)]
#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, Default, Debug)]
pub struct SceneGlobals {
pub camera_pos: [f32; 4],
pub camera_right: [f32; 4],
pub camera_up: [f32; 4],
pub camera_forward: [f32; 4],
pub projection_params: [f32; 4],
pub ambient_color: [f32; 4],
pub fog_color: [f32; 4],
pub fog_distance: [f32; 4],
pub fog_height: [f32; 4],
pub fog_params: [f32; 4],
pub fog_background_zenith: [f32; 4],
pub fog_background_horizon: [f32; 4],
pub fog_background_nadir: [f32; 4],
pub fog_background_params: [f32; 4],
pub fog_sampling: [f32; 4],
pub lights: [Light; 4],
pub light_view_proj: [[f32; 4]; 4],
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Material {
pub albedo: Option<u32>,
pub pbr: Option<u32>, pub normal: Option<u32>,
pub occlusion: Option<u32>,
pub emissive: Option<u32>,
}
impl Material {
pub fn with_albedo(mut self, image: crate::Image) -> Self {
self.albedo = Some(image.id());
self
}
pub fn with_pbr(mut self, image: crate::Image) -> Self {
self.pbr = Some(image.id());
self
}
pub fn with_normal(mut self, image: crate::Image) -> Self {
self.normal = Some(image.id());
self
}
pub fn with_occlusion(mut self, image: crate::Image) -> Self {
self.occlusion = Some(image.id());
self
}
pub fn with_emissive(mut self, image: crate::Image) -> Self {
self.emissive = Some(image.id());
self
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ModelPart {
pub(crate) id: u32,
pub(crate) material: Material,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct Model {
pub(crate) parts: Arc<Vec<ModelPart>>,
}
impl Model {
pub fn empty(_ctx: &mut crate::Context) -> Self {
Self {
parts: Arc::new(Vec::new()),
}
}
pub fn first_id(&self) -> u32 {
self.parts.first().map(|p| p.id).unwrap_or(0)
}
pub(crate) fn new(
ctx: &mut crate::Context,
vertices: &[Vertex],
indices: &[u32],
) -> anyhow::Result<Self> {
let mesh_id = ctx.register_mesh(vertices, indices);
Ok(Self {
parts: Arc::new(vec![ModelPart {
id: mesh_id,
material: Material::default(),
}]),
})
}
pub fn with_material(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.albedo = Some(image.id());
}
self
}
pub fn with_albedo(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.albedo = Some(image.id());
}
self
}
pub fn with_normal_map(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.normal = Some(image.id());
}
self
}
pub fn with_pbr_map(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.pbr = Some(image.id());
}
self
}
pub fn with_ao_map(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.occlusion = Some(image.id());
}
self
}
pub fn with_emissive_map(mut self, image: crate::Image) -> Self {
for part in Arc::make_mut(&mut self.parts).iter_mut() {
part.material.emissive = Some(image.id());
}
self
}
pub fn with_part_material(mut self, index: usize, material: Material) -> Self {
if let Some(part) = Arc::make_mut(&mut self.parts).get_mut(index) {
part.material = material;
}
self
}
pub fn add_part(
&mut self,
ctx: &mut crate::Context,
vertices: &[Vertex],
indices: &[u32],
material: Material,
) -> anyhow::Result<&mut Self> {
let mesh_id = ctx.register_mesh(vertices, indices);
Arc::make_mut(&mut self.parts).push(ModelPart {
id: mesh_id,
material,
});
Ok(self)
}
pub fn with_part(
mut self,
ctx: &mut crate::Context,
vertices: &[Vertex],
indices: &[u32],
material: Material,
) -> anyhow::Result<Self> {
self.add_part(ctx, vertices, indices, material)?;
Ok(self)
}
pub(crate) fn cube(ctx: &mut crate::Context, size: f32) -> anyhow::Result<Self> {
let s = size / 2.0;
let vertices = vec![
Vertex {
pos: [-s, -s, s],
uv: [0.0, 1.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, s],
uv: [1.0, 1.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, s],
uv: [1.0, 0.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, s],
uv: [0.0, 0.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, -s, -s],
uv: [1.0, 1.0],
normal: [0.0, 0.0, -1.0],
tangent: [-1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, -s],
uv: [1.0, 0.0],
normal: [0.0, 0.0, -1.0],
tangent: [-1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, -s],
uv: [0.0, 0.0],
normal: [0.0, 0.0, -1.0],
tangent: [-1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, -s],
uv: [0.0, 1.0],
normal: [0.0, 0.0, -1.0],
tangent: [-1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, -s],
uv: [0.0, 0.0],
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, s],
uv: [0.0, 1.0],
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, s],
uv: [1.0, 1.0],
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, -s],
uv: [1.0, 0.0],
normal: [0.0, 1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, -s, -s],
uv: [1.0, 1.0],
normal: [0.0, -1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, -s],
uv: [0.0, 1.0],
normal: [0.0, -1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, s],
uv: [0.0, 0.0],
normal: [0.0, -1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, -s, s],
uv: [1.0, 0.0],
normal: [0.0, -1.0, 0.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, -s],
uv: [1.0, 1.0],
normal: [1.0, 0.0, 0.0],
tangent: [0.0, 0.0, -1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, -s],
uv: [1.0, 0.0],
normal: [1.0, 0.0, 0.0],
tangent: [0.0, 0.0, -1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, s, s],
uv: [0.0, 0.0],
normal: [1.0, 0.0, 0.0],
tangent: [0.0, 0.0, -1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [s, -s, s],
uv: [0.0, 1.0],
normal: [1.0, 0.0, 0.0],
tangent: [0.0, 0.0, -1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, -s, -s],
uv: [0.0, 1.0],
normal: [-1.0, 0.0, 0.0],
tangent: [0.0, 0.0, 1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, -s, s],
uv: [1.0, 1.0],
normal: [-1.0, 0.0, 0.0],
tangent: [0.0, 0.0, 1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, s],
uv: [1.0, 0.0],
normal: [-1.0, 0.0, 0.0],
tangent: [0.0, 0.0, 1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-s, s, -s],
uv: [0.0, 0.0],
normal: [-1.0, 0.0, 0.0],
tangent: [0.0, 0.0, 1.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
];
let indices = vec![
0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23, ];
Self::new(ctx, &vertices, &indices)
}
pub(crate) fn plane(ctx: &mut crate::Context, width: f32, height: f32) -> anyhow::Result<Self> {
let hw = width / 2.0;
let hh = height / 2.0;
let vertices = vec![
Vertex {
pos: [-hw, -hh, 0.0],
uv: [0.0, 1.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [hw, -hh, 0.0],
uv: [1.0, 1.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [hw, hh, 0.0],
uv: [1.0, 0.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
Vertex {
pos: [-hw, hh, 0.0],
uv: [0.0, 0.0],
normal: [0.0, 0.0, 1.0],
tangent: [1.0, 0.0, 0.0],
joint_indices: [0; 4],
joint_weights: [0.0; 4],
},
];
let indices = vec![0, 1, 2, 0, 2, 3];
Self::new(ctx, &vertices, &indices)
}
pub(crate) fn sphere(ctx: &mut crate::Context, radius: f32) -> anyhow::Result<Self> {
let segments = 32;
let rings = 16;
let mut vertices = Vec::new();
let mut indices = Vec::new();
for y in 0..=rings {
let phi = y as f32 * std::f32::consts::PI / rings as f32;
for x in 0..=segments {
let theta = x as f32 * 2.0 * std::f32::consts::PI / segments as f32;
let nx = phi.sin() * theta.cos();
let ny = phi.cos();
let nz = phi.sin() * theta.sin();
let tx = -phi.sin() * theta.sin();
let tz = phi.sin() * theta.cos();
let tangent = if phi.sin().abs() < 1e-6 {
[1.0, 0.0, 0.0]
} else {
let len = (tx * tx + tz * tz).sqrt();
[tx / len, 0.0, tz / len]
};
vertices.push(Vertex {
pos: [radius * nx, radius * ny, radius * nz],
uv: [x as f32 / segments as f32, y as f32 / rings as f32],
normal: [nx, ny, nz],
tangent,
joint_indices: [0; 4],
joint_weights: [0.0; 4],
});
}
}
for y in 0..rings {
for x in 0..segments {
let first = y * (segments + 1) + x;
let second = first + segments + 1;
indices.push(first);
indices.push(first + 1);
indices.push(second);
indices.push(second);
indices.push(first + 1);
indices.push(second + 1);
}
}
Self::new(ctx, &vertices, &indices)
}
pub fn draw_skinned(
&self,
ctx: &mut crate::Context,
target: crate::Image,
options: crate::DrawOption3D,
skin_id: u32,
) {
let target_texture_id = ctx.resolve_target_texture_id(target);
ctx.push_3d(crate::drawable::DrawCommand3D::Model(
target_texture_id,
self.clone(),
options,
0,
crate::ShaderOpts::default(),
Some(skin_id),
));
}
pub(crate) fn draw_with_shader(
&self,
ctx: &mut crate::Context,
target: crate::Image,
shader_id: u32,
options: crate::DrawOption3D,
shader_opts: crate::ShaderOpts,
skin_id: Option<u32>,
) {
let target_texture_id = ctx.resolve_target_texture_id(target);
ctx.push_3d(crate::drawable::DrawCommand3D::Model(
target_texture_id,
self.clone(),
options,
shader_id,
shader_opts,
skin_id,
));
}
pub(crate) fn draw_instanced(
&self,
ctx: &mut crate::Context,
target: crate::Image,
options: crate::DrawOption3D,
transforms: &[[[f32; 4]; 4]],
) {
if transforms.is_empty() {
return;
}
self.draw_instanced_shared(ctx, target, options, std::sync::Arc::from(transforms));
}
pub fn draw_instanced_owned(
&self,
ctx: &mut crate::Context,
target: crate::Image,
options: crate::DrawOption3D,
transforms: Vec<[[f32; 4]; 4]>,
) {
if transforms.is_empty() {
return;
}
self.draw_instanced_shared(ctx, target, options, std::sync::Arc::from(transforms));
}
pub(crate) fn draw_instanced_shared(
&self,
ctx: &mut crate::Context,
target: crate::Image,
options: crate::DrawOption3D,
transforms: std::sync::Arc<[[[f32; 4]; 4]]>,
) {
if transforms.is_empty() {
return;
}
let target_texture_id = ctx.resolve_target_texture_id(target);
ctx.push_3d(crate::drawable::DrawCommand3D::ModelInstanced(
target_texture_id,
self.clone(),
options,
0,
crate::ShaderOpts::default(),
None,
transforms,
));
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Vertex {
pub pos: [f32; 3],
pub uv: [f32; 2],
pub normal: [f32; 3],
pub tangent: [f32; 3],
pub joint_indices: [u32; 4],
pub joint_weights: [f32; 4],
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable, PartialEq, Default)]
pub(crate) struct RawVertex {
pub pos: [f32; 3], _pad1: f32, pub uv: [f32; 2], _pad2: [f32; 2], pub normal: [f32; 3], _pad3: f32, pub tangent: [f32; 3], _pad4: f32, pub joint_indices: [u32; 4], pub joint_weights: [f32; 4], }
impl From<Vertex> for RawVertex {
fn from(v: Vertex) -> Self {
Self {
pos: v.pos,
uv: v.uv,
normal: v.normal,
tangent: v.tangent,
joint_indices: v.joint_indices,
joint_weights: v.joint_weights,
..Default::default()
}
}
}
impl RawVertex {
pub(crate) fn layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<RawVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 16,
shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 32,
shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 48,
shader_location: 9,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 64,
shader_location: 3,
format: wgpu::VertexFormat::Uint32x4,
},
wgpu::VertexAttribute {
offset: 80,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MeshDataPersistent {
pub(crate) vertices: Vec<RawVertex>,
pub(crate) indices: Vec<u32>,
}
pub fn create(
ctx: &mut crate::Context,
vertices: &[Vertex],
indices: &[u32],
) -> anyhow::Result<Model> {
Model::new(ctx, vertices, indices)
}
pub fn create_cube(ctx: &mut crate::Context, size: f32) -> anyhow::Result<Model> {
Model::cube(ctx, size)
}
pub fn create_plane(ctx: &mut crate::Context, width: f32, height: f32) -> anyhow::Result<Model> {
Model::plane(ctx, width, height)
}
pub fn create_sphere(ctx: &mut crate::Context, radius: f32) -> anyhow::Result<Model> {
Model::sphere(ctx, radius)
}
pub fn create_empty(ctx: &mut crate::Context) -> Model {
Model::empty(ctx)
}
pub fn draw_with_shader(
ctx: &mut crate::Context,
target: crate::Image,
model: &Model,
shader_id: u32,
options: crate::DrawOption3D,
shader_opts: crate::ShaderOpts,
skin_id: Option<u32>,
) {
model.draw_with_shader(ctx, target, shader_id, options, shader_opts, skin_id);
}
pub fn draw_instanced(
ctx: &mut crate::Context,
target: crate::Image,
model: &Model,
options: crate::DrawOption3D,
transforms: &[[[f32; 4]; 4]],
) {
model.draw_instanced(ctx, target, options, transforms);
}
pub fn draw_instanced_shared(
ctx: &mut crate::Context,
target: crate::Image,
model: &Model,
options: crate::DrawOption3D,
transforms: std::sync::Arc<[[[f32; 4]; 4]]>,
) {
model.draw_instanced_shared(ctx, target, options, transforms);
}
impl crate::Drawable for &Model {
type Options = crate::DrawOption3D;
fn draw_to(self, ctx: &mut crate::Context, target: crate::Image, options: Self::Options) {
let target_texture_id = ctx.resolve_target_texture_id(target);
ctx.push_3d(crate::drawable::DrawCommand3D::Model(
target_texture_id,
self.clone(),
options,
0,
crate::ShaderOpts::default(),
None,
));
}
}