use crate::with_graphics;
#[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>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Model {
pub(crate) id: u32,
pub(crate) material: Material,
}
impl Model {
pub fn id(&self) -> u32 {
self.id
}
pub fn new(vertices: &[Vertex], indices: &[u32]) -> anyhow::Result<Self> {
with_graphics(|g| g.create_model(vertices, indices))
.ok_or_else(|| anyhow::anyhow!("Graphics not initialized"))?
}
pub fn with_material(mut self, image: crate::Image) -> Self {
self.material.albedo = Some(image.id());
self
}
pub fn with_albedo(mut self, image: crate::Image) -> Self {
self.material.albedo = Some(image.id());
self
}
pub fn with_normal_map(mut self, image: crate::Image) -> Self {
self.material.normal = Some(image.id());
self
}
pub fn with_pbr_map(mut self, image: crate::Image) -> Self {
self.material.pbr = Some(image.id());
self
}
pub fn with_ao_map(mut self, image: crate::Image) -> Self {
self.material.occlusion = Some(image.id());
self
}
pub fn with_emissive_map(mut self, image: crate::Image) -> Self {
self.material.emissive = Some(image.id());
self
}
pub fn cube(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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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], 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, ];
let mut model = Self::new(&vertices, &indices)?;
model.material = Material::default();
Ok(model)
}
pub fn plane(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], 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], 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], 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], joint_indices: [0; 4], joint_weights: [0.0; 4] },
];
let indices = vec![0, 1, 2, 0, 2, 3];
let mut model = Self::new(&vertices, &indices)?;
model.material = Material::default();
Ok(model)
}
pub fn sphere(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();
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],
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);
}
}
let mut model = Self::new(&vertices, &indices)?;
model.material = Material::default();
Ok(model)
}
pub fn draw(&self, context: &mut crate::Context, options: crate::DrawOption3D) {
context.push_3d(crate::drawable::DrawCommand3D::Model(
*self,
options,
0,
crate::ShaderOpts::default(),
None,
));
}
pub fn draw_skinned(
&self,
context: &mut crate::Context,
options: crate::DrawOption3D,
skin_id: u32,
) {
context.push_3d(crate::drawable::DrawCommand3D::Model(
*self,
options,
0,
crate::ShaderOpts::default(),
Some(skin_id),
));
}
pub fn draw_with_shader(
&self,
context: &mut crate::Context,
shader_id: u32,
options: crate::DrawOption3D,
shader_opts: crate::ShaderOpts,
skin_id: Option<u32>,
) {
context.push_3d(crate::drawable::DrawCommand3D::Model(
*self,
options,
shader_id,
shader_opts,
skin_id,
));
}
pub fn draw_instanced(
&self,
context: &mut crate::Context,
options: crate::DrawOption3D,
transforms: &[[[f32; 4]; 4]],
) {
if transforms.is_empty() { return; }
context.push_3d(crate::drawable::DrawCommand3D::ModelInstanced(
*self,
options,
0,
crate::ShaderOpts::default(),
None,
transforms.to_vec(),
));
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex {
pub pos: [f32; 3],
pub uv: [f32; 2],
pub normal: [f32; 3],
pub joint_indices: [u32; 4],
pub joint_weights: [f32; 4],
}
impl Vertex {
pub(crate) fn layout() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 12, shader_location: 1,
format: wgpu::VertexFormat::Float32x2,
},
wgpu::VertexAttribute {
offset: 20, shader_location: 2,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: 32, shader_location: 3,
format: wgpu::VertexFormat::Uint32x4,
},
wgpu::VertexAttribute {
offset: 48, shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
}