use std::sync::Arc;
use concinnity_core::animation::skeleton;
use concinnity_core::components::{CharacterCapsule, CharacterRig, GlobalTransform, Transform};
use concinnity_core::ecs::asset_id::AssetId;
use concinnity_core::ecs::{PickIndex, PipelineContext, SkinnedMeshHandle};
use concinnity_core::gfx::mesh_payload::{PayloadMorphs, SkinnedVertex};
use concinnity_core::gfx::render_types::SkinnedDrawObject;
use concinnity_core::render::backend::RenderBackend;
use concinnity_core::render::error::RenderResult;
use super::{GraphicsSystem, PickCandidate, character_shape};
pub(super) struct SkinnedSkeletonEntry {
pub(super) handle: SkinnedMeshHandle,
pub(super) name_id: AssetId,
pub(super) template_index: usize,
pub(super) skeleton: skeleton::Skeleton,
pub(super) morph_names: Vec<String>,
pub(super) model: [[f32; 4]; 4],
pub(super) capsule: Option<CharacterCapsule>,
pub(super) transform: Transform,
pub(super) local_bounds: ([f32; 3], [f32; 3]),
}
pub(super) struct SkinnedUpload {
pub(super) vertices: Vec<SkinnedVertex>,
pub(super) indices: Vec<u32>,
pub(super) draw_objects: Vec<SkinnedDrawObject>,
pub(super) morphs: Vec<Option<Arc<PayloadMorphs>>>,
}
fn upload_skinned_geometry(
backend: &mut dyn RenderBackend,
upload: SkinnedUpload,
) -> RenderResult<()> {
backend.upload_skinned(&upload.vertices, &upload.indices, upload.draw_objects)?;
if upload.morphs.iter().any(|m| m.is_some()) {
backend.upload_skinned_morphs(upload.morphs)?;
}
Ok(())
}
impl GraphicsSystem {
pub(super) fn install_skinned_templates(
&mut self,
ctx: &mut PipelineContext,
upload: SkinnedUpload,
skeletons: Vec<SkinnedSkeletonEntry>,
) -> RenderResult<()> {
if skeletons.is_empty() {
return Ok(());
}
if let Some(backend) = self.backend.as_deref_mut() {
upload_skinned_geometry(backend, upload)?;
}
self.spawn_skinned_templates(ctx, skeletons);
Ok(())
}
fn spawn_skinned_templates(
&mut self,
ctx: &mut PipelineContext,
skeletons: Vec<SkinnedSkeletonEntry>,
) {
let skinned_count = skeletons.len();
let shapes = character_shape::collect(ctx);
let want_pick = ctx.resource::<PickIndex>().is_some();
for SkinnedSkeletonEntry {
handle,
name_id,
template_index,
skeleton,
morph_names,
model,
capsule,
transform,
local_bounds,
} in skeletons
{
let layers = shapes
.get(&handle)
.map(|shape| character_shape::resolve(shape, &skeleton, &morph_names));
let capsule = capsule.map(|c| match &layers {
Some(l) => character_shape::proportioned_capsule(&c, &skeleton, &l.proportions),
None => (c.half_height, c.radius),
});
let entity = ctx.components.spawn();
ctx.insert(
entity,
character_shape::seed_pose(handle, template_index, skeleton, layers),
);
if want_pick {
ctx.insert(entity, transform);
ctx.insert(entity, GlobalTransform(model));
self.pick_candidates.push(PickCandidate {
asset_id: name_id,
entity,
local_min: local_bounds.0,
local_max: local_bounds.1,
});
}
ctx.identify(entity, name_id);
if let Some((half_height, radius)) = capsule {
ctx.push(CharacterRig::new(
handle,
template_index,
model,
half_height.max(0.05),
radius.max(0.05),
));
}
}
tracing::info!("GraphicsSystem: {} skinned mesh(es) ready", skinned_count);
}
}