use std::collections::{BTreeMap, HashMap, HashSet};
use glam::{uvec4, vec4, Mat4, UVec4, Vec3, Vec4};
use log::{error, info};
use rayon::prelude::*;
use wgpu::util::DeviceExt;
use xc3_model::{vertex::AttributeData, ImageTexture, MeshRenderFlags2, MeshRenderPass};
use crate::{
animation::animated_skinning_transforms,
culling::is_within_frustum,
material::{create_material, Material},
pipeline::{model_pipeline, ModelPipelineData, Output5Type, PipelineKey},
sampler::create_sampler,
shader,
texture::create_texture,
CameraData, DeviceBufferExt, MonolibShaderTextures, QueueBufferExt,
};
pub struct ModelGroup {
pub models: Vec<Models>,
buffers: Vec<ModelBuffers>,
skeleton: Option<xc3_model::Skeleton>,
per_group: crate::shader::model::bind_groups::BindGroup1,
per_group_buffer: wgpu::Buffer,
pub(crate) bone_animated_transforms: wgpu::Buffer,
pub(crate) bone_count: usize,
pipelines: HashMap<PipelineKey, wgpu::RenderPipeline>,
}
pub struct ModelBuffers {
vertex_buffers: Vec<VertexBuffer>,
index_buffers: Vec<IndexBuffer>,
}
impl ModelBuffers {
fn from_buffers(device: &wgpu::Device, buffers: &xc3_model::vertex::ModelBuffers) -> Self {
let vertex_buffers = model_vertex_buffers(device, buffers);
let index_buffers = model_index_buffers(device, buffers);
Self {
vertex_buffers,
index_buffers,
}
}
}
pub struct Models {
pub models: Vec<Model>,
index_to_materials: BTreeMap<usize, Material>,
bounds: Bounds,
morph_controller_names: Vec<String>,
animation_morph_names: Vec<String>,
}
impl Models {
#[allow(clippy::too_many_arguments)]
fn from_models(
device: &wgpu::Device,
queue: &wgpu::Queue,
models: &xc3_model::Models,
buffers: &[xc3_model::vertex::ModelBuffers],
skeleton: Option<&xc3_model::Skeleton>,
pipelines: &mut HashSet<PipelineKey>,
textures: &[wgpu::Texture],
image_textures: &[ImageTexture],
monolib_shader: &MonolibShaderTextures,
) -> Self {
let weights = buffers.first().and_then(|b| b.weights.as_ref());
let bone_names: Option<Vec<_>> = skeleton
.as_ref()
.map(|s| s.bones.iter().map(|b| b.name.clone()).collect());
let morph_controller_names = models.morph_controller_names.clone();
let animation_morph_names = models.animation_morph_names.clone();
let bounds = Bounds::new(device, models.max_xyz, models.min_xyz, &Mat4::IDENTITY);
let samplers: Vec<_> = models
.samplers
.iter()
.map(|s| create_sampler(device, s))
.collect();
let is_instanced_static = models.models.iter().any(|m| m.instances.len() > 1);
let mut index_to_materials = BTreeMap::new();
let models = models
.models
.iter()
.map(|model| {
create_model(
device,
queue,
model,
models,
buffers,
weights,
bone_names.as_deref(),
&models.materials,
&mut index_to_materials,
image_textures,
monolib_shader,
pipelines,
textures,
&samplers,
is_instanced_static,
)
})
.collect();
Self {
models,
index_to_materials,
morph_controller_names,
animation_morph_names,
bounds,
}
}
pub fn bounds_min_max_xyz(&self) -> (Vec3, Vec3) {
(self.bounds.min_xyz, self.bounds.max_xyz)
}
}
pub struct Model {
pub meshes: Vec<Mesh>,
model_buffers_index: usize,
instances: Instances,
}
pub struct Mesh {
vertex_buffer_index: usize,
index_buffer_index: usize,
material_index: usize,
flags2: MeshRenderFlags2,
per_mesh: crate::shader::model::bind_groups::BindGroup3,
}
struct Instances {
transforms: wgpu::Buffer,
count: u32,
}
struct Bounds {
max_xyz: Vec3,
min_xyz: Vec3,
bounds_vertex_buffer: wgpu::Buffer,
bounds_index_buffer: wgpu::Buffer,
}
struct VertexBuffer {
vertex_buffer0: wgpu::Buffer,
vertex_buffer1: wgpu::Buffer,
outline_vertex_buffer0: wgpu::Buffer,
outline_vertex_buffer1: wgpu::Buffer,
vertex_count: u32,
morph_buffers: Option<MorphBuffers>,
}
struct MorphBuffers {
vertex_buffer0: wgpu::Buffer,
weights_buffer: wgpu::Buffer,
bind_group0: crate::shader::morph::bind_groups::BindGroup0,
morph_target_controller_indices: Vec<usize>,
}
struct IndexBuffer {
index_buffer: wgpu::Buffer,
vertex_index_count: u32,
}
impl Bounds {
fn new(device: &wgpu::Device, max_xyz: Vec3, min_xyz: Vec3, transform: &Mat4) -> Self {
let (bounds_vertex_buffer, bounds_index_buffer) =
wireframe_aabb_box_vertex_index(device, min_xyz, max_xyz, transform);
Self {
max_xyz,
min_xyz,
bounds_vertex_buffer,
bounds_index_buffer,
}
}
fn draw<'a>(
&'a self,
render_pass: &mut wgpu::RenderPass<'a>,
culled: bool,
bind_group1: &'a crate::shader::solid::bind_groups::BindGroup1,
culled_bind_group1: &'a crate::shader::solid::bind_groups::BindGroup1,
) {
render_pass.set_vertex_buffer(0, self.bounds_vertex_buffer.slice(..));
render_pass.set_index_buffer(
self.bounds_index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
);
if culled {
culled_bind_group1.set(render_pass);
} else {
bind_group1.set(render_pass);
}
render_pass.draw_indexed(0..24, 0, 0..1);
}
}
impl ModelGroup {
pub fn draw<'a>(
&'a self,
render_pass: &mut wgpu::RenderPass<'a>,
write_to_all_outputs: bool,
pass_id: MeshRenderPass,
camera: &CameraData,
output5_type: Option<Output5Type>,
) {
self.per_group.set(render_pass);
for models in self
.models
.iter()
.filter(|m| is_within_frustum(m.bounds.min_xyz, m.bounds.max_xyz, camera))
{
for model in models.models.iter() {
for mesh in &model.meshes {
let material = &models.index_to_materials[&mesh.material_index];
let is_outline = material.name.contains("outline");
if (write_to_all_outputs == material.pipeline_key.write_to_all_outputs())
&& mesh.flags2.render_pass() == pass_id
&& output5_type
.map(|ty| material.pipeline_key.output5_type == ty)
.unwrap_or(true)
{
mesh.per_mesh.set(render_pass);
let pipeline = &self.pipelines[&material.pipeline_key];
render_pass.set_pipeline(pipeline);
let stencil_reference = material.pipeline_key.stencil_reference();
render_pass.set_stencil_reference(stencil_reference);
material.bind_group2.set(render_pass);
let instance_count = material
.fur_shell_instance_count
.unwrap_or(model.instances.count);
let is_instanced_static = material.pipeline_key.is_instanced_static;
self.draw_mesh(
model,
mesh,
render_pass,
is_outline,
is_instanced_static,
instance_count,
);
}
}
}
}
}
pub fn draw_bounds<'a>(
&'a self,
render_pass: &mut wgpu::RenderPass<'a>,
bind_group1: &'a crate::shader::solid::bind_groups::BindGroup1,
culled_bind_group1: &'a crate::shader::solid::bind_groups::BindGroup1,
camera: &CameraData,
) {
for models in &self.models {
let cull_models =
!is_within_frustum(models.bounds.min_xyz, models.bounds.max_xyz, camera);
models
.bounds
.draw(render_pass, cull_models, bind_group1, culled_bind_group1);
}
}
fn draw_mesh<'a>(
&'a self,
model: &'a Model,
mesh: &Mesh,
render_pass: &mut wgpu::RenderPass<'a>,
is_outline: bool,
is_instanced_static: bool,
instance_count: u32,
) {
let vertex_buffers =
&self.buffers[model.model_buffers_index].vertex_buffers[mesh.vertex_buffer_index];
if let Some(morph_buffers) = &vertex_buffers.morph_buffers {
render_pass.set_vertex_buffer(0, morph_buffers.vertex_buffer0.slice(..));
} else if is_outline {
render_pass.set_vertex_buffer(0, vertex_buffers.outline_vertex_buffer0.slice(..));
} else {
render_pass.set_vertex_buffer(0, vertex_buffers.vertex_buffer0.slice(..));
}
if is_outline {
render_pass.set_vertex_buffer(1, vertex_buffers.outline_vertex_buffer1.slice(..));
} else {
render_pass.set_vertex_buffer(1, vertex_buffers.vertex_buffer1.slice(..));
}
if is_instanced_static {
render_pass.set_vertex_buffer(2, model.instances.transforms.slice(..));
}
let index_buffer =
&self.buffers[model.model_buffers_index].index_buffers[mesh.index_buffer_index];
render_pass.set_index_buffer(
index_buffer.index_buffer.slice(..),
wgpu::IndexFormat::Uint16,
);
render_pass.draw_indexed(0..index_buffer.vertex_index_count, 0, 0..instance_count);
}
pub fn reset_morphs(&self, encoder: &mut wgpu::CommandEncoder) {
for buffers in &self.buffers {
for vertex_buffer in &buffers.vertex_buffers {
if let Some(morph_buffers) = &vertex_buffer.morph_buffers {
encoder.copy_buffer_to_buffer(
&vertex_buffer.vertex_buffer0,
0,
&morph_buffers.vertex_buffer0,
0,
vertex_buffer.vertex_buffer0.size(),
);
}
}
}
}
pub fn compute_morphs<'a>(&'a self, compute_pass: &mut wgpu::ComputePass<'a>) {
for buffers in &self.buffers {
for vertex_buffer in &buffers.vertex_buffers {
if let Some(morph_buffers) = &vertex_buffer.morph_buffers {
morph_buffers.bind_group0.set(compute_pass);
let [size_x, _, _] = crate::shader::morph::compute::MAIN_WORKGROUP_SIZE;
let x = vertex_buffer.vertex_count.div_ceil(size_x);
compute_pass.dispatch_workgroups(x, 1, 1);
}
}
}
}
pub fn update_bone_transforms(
&self,
queue: &wgpu::Queue,
animation: &xc3_model::animation::Animation,
current_time_seconds: f32,
) {
if let Some(skeleton) = &self.skeleton {
let animated_transforms =
animated_skinning_transforms(skeleton, animation, current_time_seconds);
let animated_transforms_inv_transpose =
animated_transforms.map(|t| t.inverse().transpose());
queue.write_uniform_data(
&self.per_group_buffer,
&crate::shader::model::PerGroup {
enable_skinning: uvec4(1, 0, 0, 0),
animated_transforms,
animated_transforms_inv_transpose,
},
);
let bone_transforms: Vec<_> = animation
.model_space_transforms(skeleton, animation.current_frame(current_time_seconds))
.into_iter()
.map(|t| t.to_matrix())
.collect();
queue.write_buffer(
&self.bone_animated_transforms,
0,
bytemuck::cast_slice(&bone_transforms),
);
}
}
pub fn update_morph_weights(
&self,
queue: &wgpu::Queue,
animation: &xc3_model::animation::Animation,
current_time_seconds: f32,
) {
let morph_controller_names = &self.models[0].morph_controller_names;
let animation_morph_names = &self.models[0].animation_morph_names;
let frame = animation.current_frame(current_time_seconds);
for buffers in &self.buffers {
for buffer in &buffers.vertex_buffers {
if let Some(morph_buffers) = &buffer.morph_buffers {
let weights = animation.morph_weights(
morph_controller_names,
animation_morph_names,
&morph_buffers.morph_target_controller_indices,
frame,
);
queue.write_storage_data(&morph_buffers.weights_buffer, &weights);
}
}
}
}
}
fn should_render_lod(lod: Option<usize>, models: &xc3_model::Models) -> bool {
models
.lod_data
.as_ref()
.map(|d| d.is_base_lod(lod))
.unwrap_or(true)
}
#[tracing::instrument(skip_all)]
pub fn load_model(
device: &wgpu::Device,
queue: &wgpu::Queue,
roots: &[xc3_model::ModelRoot],
monolib_shader: &MonolibShaderTextures,
) -> Vec<ModelGroup> {
let start = std::time::Instant::now();
let pipeline_data = ModelPipelineData::new(device);
let mut groups = Vec::new();
for root in roots {
let textures = load_textures(device, queue, &root.image_textures);
let group = create_model_group(
device,
queue,
&xc3_model::ModelGroup {
models: vec![root.models.clone()],
buffers: vec![root.buffers.clone()],
},
&textures,
&root.image_textures,
&pipeline_data,
root.skeleton.as_ref(),
monolib_shader,
);
groups.push(group);
}
info!("Load {} model groups: {:?}", roots.len(), start.elapsed());
groups
}
#[tracing::instrument(skip_all)]
pub fn load_map(
device: &wgpu::Device,
queue: &wgpu::Queue,
roots: &[xc3_model::MapRoot],
monolib_shader: &MonolibShaderTextures,
) -> Vec<ModelGroup> {
let start = std::time::Instant::now();
let pipeline_data = ModelPipelineData::new(device);
let mut groups = Vec::new();
for root in roots {
let textures = load_textures(device, queue, &root.image_textures);
groups.par_extend(root.groups.par_iter().map(|group| {
create_model_group(
device,
queue,
group,
&textures,
&root.image_textures,
&pipeline_data,
None,
monolib_shader,
)
}));
}
info!("Load {} model groups: {:?}", roots.len(), start.elapsed());
groups
}
#[tracing::instrument(skip_all)]
fn load_textures(
device: &wgpu::Device,
queue: &wgpu::Queue,
image_textures: &[xc3_model::ImageTexture],
) -> Vec<wgpu::Texture> {
image_textures
.iter()
.map(|texture| create_texture(device, queue, texture))
.collect()
}
#[allow(clippy::too_many_arguments)]
#[tracing::instrument(skip_all)]
fn create_model_group(
device: &wgpu::Device,
queue: &wgpu::Queue,
group: &xc3_model::ModelGroup,
textures: &[wgpu::Texture],
image_textures: &[ImageTexture],
pipeline_data: &ModelPipelineData,
skeleton: Option<&xc3_model::Skeleton>,
monolib_shader: &MonolibShaderTextures,
) -> ModelGroup {
let enable_skinning = matches!(skeleton, Some(skeleton) if !skeleton.bones.is_empty())
&& group.models.iter().any(|g| g.skinning.is_some())
&& group.buffers.iter().any(|b| b.weights.is_some());
let (per_group, per_group_buffer) = per_group_bind_group(device, enable_skinning);
let bone_transforms: Vec<_> = skeleton
.as_ref()
.map(|s| {
s.model_space_transforms()
.into_iter()
.map(|t| t.to_matrix())
.collect()
})
.unwrap_or_default();
let bone_count = skeleton.as_ref().map(|s| s.bones.len()).unwrap_or_default();
let bone_animated_transforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Bone Animated Transforms"),
contents: bytemuck::cast_slice(&bone_transforms),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
});
let buffers = group
.buffers
.iter()
.map(|buffers| ModelBuffers::from_buffers(device, buffers))
.collect();
let mut pipeline_keys = HashSet::new();
let models = group
.models
.iter()
.map(|models| {
Models::from_models(
device,
queue,
models,
&group.buffers,
skeleton,
&mut pipeline_keys,
textures,
image_textures,
monolib_shader,
)
})
.collect();
let start = std::time::Instant::now();
let pipelines: HashMap<_, _> = pipeline_keys
.into_iter()
.map(|key| {
let pipeline = model_pipeline(device, pipeline_data, &key);
(key, pipeline)
})
.collect();
info!(
"Created {} pipelines in {:?}",
pipelines.len(),
start.elapsed()
);
ModelGroup {
models,
buffers,
per_group,
per_group_buffer,
skeleton: skeleton.cloned(),
bone_animated_transforms,
bone_count,
pipelines,
}
}
fn model_index_buffers(
device: &wgpu::Device,
buffer: &xc3_model::vertex::ModelBuffers,
) -> Vec<IndexBuffer> {
buffer
.index_buffers
.iter()
.map(|buffer| {
let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("index buffer"),
contents: bytemuck::cast_slice(&buffer.indices),
usage: wgpu::BufferUsages::INDEX,
});
IndexBuffer {
index_buffer,
vertex_index_count: buffer.indices.len() as u32,
}
})
.collect()
}
#[tracing::instrument(skip_all)]
fn create_model(
device: &wgpu::Device,
queue: &wgpu::Queue,
model: &xc3_model::Model,
models: &xc3_model::Models,
buffers: &[xc3_model::vertex::ModelBuffers],
weights: Option<&xc3_model::skinning::Weights>,
bone_names: Option<&[String]>,
materials: &[xc3_model::material::Material],
index_to_material: &mut BTreeMap<usize, Material>,
image_textures: &[ImageTexture],
monolib_shader: &MonolibShaderTextures,
pipelines: &mut HashSet<PipelineKey>,
textures: &[wgpu::Texture],
samplers: &[wgpu::Sampler],
is_instanced_static: bool,
) -> Model {
let model_buffers = &buffers[model.model_buffers_index];
let meshes = model
.meshes
.iter()
.filter(|m| {
should_render_lod(m.lod_item_index, models)
&& !materials[m.material_index].name.contains("_speff_")
})
.map(|mesh| {
let material = index_to_material
.entry(mesh.material_index)
.or_insert(create_material(
device,
queue,
pipelines,
&materials[mesh.material_index],
textures,
samplers,
image_textures,
monolib_shader,
is_instanced_static,
));
Mesh {
vertex_buffer_index: mesh.vertex_buffer_index,
index_buffer_index: mesh.index_buffer_index,
material_index: mesh.material_index,
flags2: mesh.flags2,
per_mesh: per_mesh_bind_group(
device,
model_buffers,
mesh,
material,
weights,
bone_names,
),
}
})
.collect();
let transforms = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("instance transforms"),
contents: bytemuck::cast_slice(&model.instances),
usage: wgpu::BufferUsages::VERTEX,
});
let instances = Instances {
transforms,
count: model.instances.len() as u32,
};
Model {
meshes,
model_buffers_index: model.model_buffers_index,
instances,
}
}
fn wireframe_aabb_box_vertex_index(
device: &wgpu::Device,
min_xyz: Vec3,
max_xyz: Vec3,
transform: &Mat4,
) -> (wgpu::Buffer, wgpu::Buffer) {
let corners = [
vec4(min_xyz.x, min_xyz.y, min_xyz.z, 1.0),
vec4(max_xyz.x, min_xyz.y, min_xyz.z, 1.0),
vec4(max_xyz.x, max_xyz.y, min_xyz.z, 1.0),
vec4(min_xyz.x, max_xyz.y, min_xyz.z, 1.0),
vec4(min_xyz.x, min_xyz.y, max_xyz.z, 1.0),
vec4(max_xyz.x, min_xyz.y, max_xyz.z, 1.0),
vec4(max_xyz.x, max_xyz.y, max_xyz.z, 1.0),
vec4(min_xyz.x, max_xyz.y, max_xyz.z, 1.0),
]
.map(|c| *transform * c);
let bounds_vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("bounds vertex buffer"),
contents: bytemuck::cast_slice(&corners),
usage: wgpu::BufferUsages::VERTEX,
});
let bounds_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("bounds index buffer"),
contents: bytemuck::cast_slice(&[
[0u16, 1u16],
[1u16, 2u16],
[2u16, 3u16],
[3u16, 0u16],
[0u16, 4u16],
[1u16, 5u16],
[2u16, 6u16],
[3u16, 7u16],
[4u16, 5u16],
[5u16, 6u16],
[6u16, 7u16],
[7u16, 4u16],
]),
usage: wgpu::BufferUsages::INDEX,
});
(bounds_vertex_buffer, bounds_index_buffer)
}
fn model_vertex_buffers(
device: &wgpu::Device,
buffers: &xc3_model::vertex::ModelBuffers,
) -> Vec<VertexBuffer> {
buffers
.vertex_buffers
.iter()
.map(|buffer| {
let vertex_count = buffer.vertex_count();
let mut buffer0_vertices = vec![
shader::model::VertexInput0 {
position: Vec4::ZERO,
normal: Vec4::ZERO,
tangent: Vec4::ZERO,
};
vertex_count
];
let mut buffer1_vertices = vec![
shader::model::VertexInput1 {
vertex_color: Vec4::ONE,
weight_index: UVec4::ZERO,
tex01: Vec4::ZERO,
tex23: Vec4::ZERO,
tex45: Vec4::ZERO,
tex67: Vec4::ZERO,
tex8: Vec4::ZERO,
};
vertex_count
];
set_attributes(&mut buffer0_vertices, &mut buffer1_vertices, buffer);
let mut outline_buffer0_vertices = buffer0_vertices.clone();
let mut outline_buffer1_vertices = buffer1_vertices.clone();
if let Some(outline_buffer) = buffer
.outline_buffer_index
.and_then(|i| buffers.outline_buffers.get(i))
{
set_buffer0_attributes(&mut outline_buffer0_vertices, &outline_buffer.attributes);
set_buffer1_attributes(&mut outline_buffer1_vertices, &outline_buffer.attributes);
}
let vertex_buffer0 = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vertex buffer 0"),
contents: bytemuck::cast_slice(&buffer0_vertices),
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_SRC,
});
let vertex_buffer1 = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vertex buffer 1"),
contents: bytemuck::cast_slice(&buffer1_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let outline_vertex_buffer0 =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("outline vertex buffer 0"),
contents: bytemuck::cast_slice(&outline_buffer0_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let outline_vertex_buffer1 =
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("outline vertex buffer 1"),
contents: bytemuck::cast_slice(&outline_buffer1_vertices),
usage: wgpu::BufferUsages::VERTEX,
});
let morph_buffers = if !buffer.morph_targets.is_empty() {
Some(morph_buffers(device, buffer0_vertices, buffer))
} else {
None
};
VertexBuffer {
vertex_buffer0,
vertex_buffer1,
outline_vertex_buffer0,
outline_vertex_buffer1,
morph_buffers,
vertex_count: vertex_count as u32,
}
})
.collect()
}
fn morph_buffers(
device: &wgpu::Device,
buffer0_vertices: Vec<shader::model::VertexInput0>,
buffer: &xc3_model::vertex::VertexBuffer,
) -> MorphBuffers {
let morph_vertex_buffer0 = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("vertex buffer 0 morph"),
contents: bytemuck::cast_slice(&buffer0_vertices),
usage: wgpu::BufferUsages::VERTEX
| wgpu::BufferUsages::STORAGE
| wgpu::BufferUsages::COPY_DST,
});
let deltas: Vec<_> = buffer
.morph_targets
.iter()
.flat_map(|target| {
let vertex_count = buffer.vertex_count();
let mut position_deltas = vec![Vec4::ZERO; vertex_count];
let mut normal_deltas = vec![Vec4::ZERO; vertex_count];
let mut tangent_deltas = vec![Vec4::ZERO; vertex_count];
for (i, vertex_index) in target.vertex_indices.iter().enumerate() {
let vertex_index = *vertex_index as usize;
position_deltas[vertex_index] = target.position_deltas[i].extend(0.0);
normal_deltas[vertex_index] =
target.normals[i] - buffer0_vertices[vertex_index].normal;
tangent_deltas[vertex_index] =
target.tangents[i] - buffer0_vertices[vertex_index].tangent;
}
position_deltas
.iter()
.zip(normal_deltas.iter())
.zip(tangent_deltas.iter())
.map(move |((p, n), t)| crate::shader::morph::MorphVertexDelta {
position_delta: *p,
normal_delta: *n,
tangent_delta: *t,
})
.collect::<Vec<_>>()
})
.collect();
let morph_deltas = device.create_storage_buffer("morph deltas", &deltas);
let weights = vec![0.0f32; buffer.morph_targets.len()];
let morph_weights = device.create_storage_buffer("morph weights", &weights);
let bind_group0 = crate::shader::morph::bind_groups::BindGroup0::from_bindings(
device,
crate::shader::morph::bind_groups::BindGroupLayout0 {
vertices: morph_vertex_buffer0.as_entire_buffer_binding(),
morph_deltas: morph_deltas.as_entire_buffer_binding(),
morph_weights: morph_weights.as_entire_buffer_binding(),
},
);
let morph_target_controller_indices = buffer
.morph_targets
.iter()
.map(|t| t.morph_controller_index)
.collect();
MorphBuffers {
vertex_buffer0: morph_vertex_buffer0,
weights_buffer: morph_weights,
morph_target_controller_indices,
bind_group0,
}
}
fn set_attributes(
buffer0_vertices: &mut [shader::model::VertexInput0],
buffer1_vertices: &mut [shader::model::VertexInput1],
buffer: &xc3_model::vertex::VertexBuffer,
) {
set_buffer0_attributes(buffer0_vertices, &buffer.attributes);
set_buffer0_attributes(buffer0_vertices, &buffer.morph_blend_target);
set_buffer1_attributes(buffer1_vertices, &buffer.attributes);
}
fn set_buffer0_attributes(verts: &mut [shader::model::VertexInput0], attributes: &[AttributeData]) {
for attribute in attributes {
match attribute {
AttributeData::Position(vals) => {
set_attribute0(verts, vals, |v, t| v.position = t.extend(1.0))
}
AttributeData::Normal(vals) => set_attribute0(verts, vals, |v, t| v.normal = t),
AttributeData::Normal2(vals) => set_attribute0(verts, vals, |v, t| v.normal = t),
AttributeData::Tangent(vals) => set_attribute0(verts, vals, |v, t| v.tangent = t),
AttributeData::Position2(vals) => {
set_attribute0(verts, vals, |v, t| v.position = t.extend(1.0))
}
AttributeData::Normal4(vals) => set_attribute0(verts, vals, |v, t| v.normal = t),
AttributeData::Tangent2(vals) => set_attribute0(verts, vals, |v, t| v.tangent = t),
_ => (),
}
}
}
fn set_buffer1_attributes(verts: &mut [shader::model::VertexInput1], attributes: &[AttributeData]) {
for attribute in attributes {
match attribute {
AttributeData::TexCoord0(vals) => set_attribute1(verts, vals, |v, t| {
v.tex01.x = t.x;
v.tex01.y = t.y;
}),
AttributeData::TexCoord1(vals) => set_attribute1(verts, vals, |v, t| {
v.tex01.z = t.x;
v.tex01.w = t.y;
}),
AttributeData::TexCoord2(vals) => set_attribute1(verts, vals, |v, t| {
v.tex23.z = t.x;
v.tex23.w = t.y;
}),
AttributeData::TexCoord3(vals) => set_attribute1(verts, vals, |v, t| {
v.tex23.z = t.x;
v.tex23.w = t.y;
}),
AttributeData::TexCoord4(vals) => set_attribute1(verts, vals, |v, t| {
v.tex45.x = t.x;
v.tex45.y = t.y;
}),
AttributeData::TexCoord5(vals) => set_attribute1(verts, vals, |v, t| {
v.tex45.z = t.x;
v.tex45.w = t.y;
}),
AttributeData::TexCoord6(vals) => set_attribute1(verts, vals, |v, t| {
v.tex67.x = t.x;
v.tex67.y = t.y;
}),
AttributeData::TexCoord7(vals) => set_attribute1(verts, vals, |v, t| {
v.tex67.z = t.x;
v.tex67.w = t.y;
}),
AttributeData::TexCoord8(vals) => set_attribute1(verts, vals, |v, t| {
v.tex8.z = t.x;
v.tex8.w = t.y;
}),
AttributeData::VertexColor(vals) => {
set_attribute1(verts, vals, |v, t| v.vertex_color = t)
}
AttributeData::WeightIndex(vals) => {
set_attribute1(verts, vals, |v, t| {
v.weight_index.x = t[0] as u32;
v.weight_index.y = t[1] as u32;
})
}
_ => (),
}
}
}
fn set_attribute0<T, F>(vertices: &mut [shader::model::VertexInput0], values: &[T], assign: F)
where
T: Copy,
F: Fn(&mut shader::model::VertexInput0, T),
{
for (vertex, value) in vertices.iter_mut().zip(values) {
assign(vertex, *value);
}
}
fn set_attribute1<T, F>(vertices: &mut [shader::model::VertexInput1], values: &[T], assign: F)
where
T: Copy,
F: Fn(&mut shader::model::VertexInput1, T),
{
for (vertex, value) in vertices.iter_mut().zip(values) {
assign(vertex, *value);
}
}
fn per_group_bind_group(
device: &wgpu::Device,
enable_skinning: bool,
) -> (shader::model::bind_groups::BindGroup1, wgpu::Buffer) {
let buffer = device.create_uniform_buffer(
"per group buffer",
&crate::shader::model::PerGroup {
enable_skinning: uvec4(enable_skinning as u32, 0, 0, 0),
animated_transforms: [Mat4::IDENTITY; 256],
animated_transforms_inv_transpose: [Mat4::IDENTITY; 256],
},
);
(
crate::shader::model::bind_groups::BindGroup1::from_bindings(
device,
crate::shader::model::bind_groups::BindGroupLayout1 {
per_group: buffer.as_entire_buffer_binding(),
},
),
buffer,
)
}
fn per_mesh_bind_group(
device: &wgpu::Device,
buffers: &xc3_model::vertex::ModelBuffers,
mesh: &xc3_model::Mesh,
material: &Material,
weights: Option<&xc3_model::skinning::Weights>,
bone_names: Option<&[String]>,
) -> shader::model::bind_groups::BindGroup3 {
let start = buffers
.weights
.as_ref()
.map(|weights| {
weights.weight_groups.weights_start_index(
mesh.flags2.into(),
mesh.lod_item_index,
material.pipeline_key.pass_type,
)
})
.unwrap_or_default();
let per_mesh = device.create_uniform_buffer(
"per mesh buffer",
&crate::shader::model::PerMesh {
weight_group_indices: uvec4(start as u32, 0, 0, 0),
},
);
let skin_weights = weights
.and_then(|w| w.weight_buffer(mesh.flags2.into()))
.map(|w| {
bone_names
.map(|names| w.reindex_bones(names.to_vec()))
.unwrap_or(w)
});
let skin_weight_count = skin_weights
.as_ref()
.map(|w| w.weights.len())
.unwrap_or_default();
for attribute in &buffers.vertex_buffers[mesh.vertex_buffer_index].attributes {
if let AttributeData::WeightIndex(weight_indices) = attribute {
let max_index = weight_indices
.iter()
.map(|i| i[0])
.max()
.unwrap_or_default() as usize;
if max_index + start >= skin_weight_count {
error!(
"Weight index start {} and max weight index {} exceed weight count {} with {:?}",
start, max_index, skin_weight_count,
(mesh.flags2, mesh.lod_item_index, material.pipeline_key.pass_type)
);
}
}
}
let indices: Vec<_> = skin_weights
.as_ref()
.map(|skin_weights| {
skin_weights
.bone_indices
.iter()
.map(|indices| indices.map(|i| i as u32))
.collect()
})
.unwrap_or_else(|| vec![[0; 4]]);
let weights = skin_weights
.as_ref()
.map(|skin_weights| skin_weights.weights.as_slice())
.unwrap_or(&[Vec4::ZERO]);
let bone_indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("bone indices buffer"),
contents: bytemuck::cast_slice(&indices),
usage: wgpu::BufferUsages::STORAGE,
});
let skin_weights = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("skin weights buffer"),
contents: bytemuck::cast_slice(weights),
usage: wgpu::BufferUsages::STORAGE,
});
crate::shader::model::bind_groups::BindGroup3::from_bindings(
device,
crate::shader::model::bind_groups::BindGroupLayout3 {
per_mesh: per_mesh.as_entire_buffer_binding(),
bone_indices: bone_indices.as_entire_buffer_binding(),
skin_weights: skin_weights.as_entire_buffer_binding(),
},
)
}