smpl_gloss_integration/
systems.rs

1use crate::components::{Follow, FollowParams, Follower, FollowerType, GlossInterop};
2use crate::conversions::update_entity_on_backend;
3use crate::scene::SceneAnimation;
4use crate::{codec::SmplCodecGloss, gltf::GltfCodecGloss};
5use burn::backend::{Candle, NdArray, Wgpu};
6use burn::{
7    prelude::*,
8    tensor::{Float, Int, Tensor},
9};
10use core::f32;
11use gloss_hecs::Entity;
12use gloss_hecs::{Changed, CommandBuffer};
13use gloss_renderer::plugin_manager::gui::{GuiWindow, GuiWindowType};
14use gloss_renderer::{
15    components::{ConfigChanges, ModelMatrix, PosLookat, Renderable, Verts},
16    geom::{Geom, PerVertexNormalsWeightingType},
17    plugin_manager::{
18        gui::{Checkbox, Selectable, Slider, Widgets},
19        Event, RunnerState,
20    },
21    scene::Scene,
22};
23use gloss_utils::abi_stable_aliases::std_types::{RNone, ROption, RString, RVec};
24use gloss_utils::tensor::BurnBackend;
25use gloss_utils::{
26    bshare::{ToBurn, ToNalgebraFloat, ToNalgebraInt, ToNdArray},
27    nshare::ToNalgebra,
28    tensor::{DynamicMatrixOps, DynamicTensorFloat2D, DynamicTensorInt2D},
29};
30use log::{info, warn};
31use nalgebra::{self as na};
32use smpl_core::common::animation::AnimationConfig;
33use smpl_core::common::smpl_model::{SmplCache, SmplModel};
34use smpl_core::common::types::{GltfOutputType, UpAxis};
35use smpl_core::common::{
36    animation::Animation,
37    betas::Betas,
38    expression::{Expression, ExpressionOffsets},
39    smpl_model::SmplCacheDynamic,
40};
41use smpl_core::common::{
42    outputs::{SmplOutputPoseTDynamic, SmplOutputPosedDynamic},
43    pose::Pose,
44    pose_corrective::PoseCorrectiveDynamic,
45    pose_override::PoseOverride,
46    smpl_params::SmplParams,
47};
48use smpl_core::conversions::pose_remap::PoseRemap;
49use smpl_utils::io::FileType;
50/// Check all entities with ``SmplParams`` and lazy load the smpl model if
51/// needed do it in two stages, first checking if we need to acually load
52/// anything and in the second stage we actually load stuff, this is in order to
53/// avoid accing the ``SmplModel`` mutable and then triggering all the other
54/// systems to run
55#[cfg(not(target_arch = "wasm32"))]
56pub extern "C" fn smpl_lazy_load_model(scene: &mut Scene, _runner: &mut RunnerState) {
57    let mut command_buffer = CommandBuffer::new();
58    {
59        let mut needs_loading = false;
60        let mut query_state = scene.world.query::<&SmplParams>();
61        for (_entity, smpl_params) in query_state.iter() {
62            let smpl_models = scene.get_resource::<&SmplCacheDynamic>().unwrap();
63            if !smpl_models.has_model(smpl_params.smpl_type, smpl_params.gender)
64                && smpl_models.has_lazy_loading(smpl_params.smpl_type, smpl_params.gender)
65            {
66                needs_loading = true;
67            }
68        }
69        if needs_loading {
70            let mut query_state = scene.world.query::<&SmplParams>();
71            for (_entity, smpl_params) in query_state.iter() {
72                let mut smpl_models = scene.get_resource::<&mut SmplCacheDynamic>().unwrap();
73                if !smpl_models.has_model(smpl_params.smpl_type, smpl_params.gender)
74                    && smpl_models.has_lazy_loading(smpl_params.smpl_type, smpl_params.gender)
75                {
76                    if let Some(path) = smpl_models.get_lazy_loading(smpl_params.smpl_type, smpl_params.gender).as_ref() {
77                        smpl_models.add_model_from_type(smpl_params.smpl_type, path, smpl_params.gender, 300, 100);
78                    }
79                }
80            }
81        }
82    }
83    command_buffer.run_on(&mut scene.world);
84}
85/// System to add a ``SceneAnimation`` Resource in case it doesn't already exist
86pub extern "C" fn smpl_auto_add_scene(scene: &mut Scene, _runner: &mut RunnerState) {
87    if !scene.has_resource::<SceneAnimation>() {
88        let mut selected_num_frames = 0;
89        let mut selected_fps = f32::MAX;
90        let mut anim_config = AnimationConfig::default();
91        let num_ents: usize;
92        {
93            let mut query_state = scene.world.query::<&Animation>().with::<&Renderable>();
94            num_ents = query_state.iter().len();
95            for (_, smpl_anim) in query_state.iter() {
96                let last_frame_idx = smpl_anim.num_animation_frames() + smpl_anim.start_offset;
97                selected_num_frames = selected_num_frames.max(last_frame_idx);
98                selected_fps = selected_fps.min(smpl_anim.config.fps);
99                anim_config = smpl_anim.config.clone();
100            }
101        }
102        let scene_anim = match num_ents {
103            0 => None,
104            1 => Some(SceneAnimation::new_with_config(selected_num_frames, anim_config)),
105            _ => Some(SceneAnimation::new_with_fps(selected_num_frames, selected_fps)),
106        };
107        if let Some(scene_anim) = scene_anim {
108            scene.add_resource(scene_anim);
109        }
110    }
111}
112/// System to add a ``SmplInterval`` to entities in case it doesn't already exist
113pub extern "C" fn smpl_auto_add_follow(scene: &mut Scene, _runner: &mut RunnerState) {
114    let mut command_buffer = CommandBuffer::new();
115    {
116        let Ok(follower) = scene.get_resource::<&Follower>() else {
117            return;
118        };
119        let follow_all = follower.params.follow_all;
120        if !follow_all {
121            return;
122        }
123        let mut query_state = scene.world.query::<&GlossInterop>().without::<&Follow>();
124        for (entity, _) in query_state.iter() {
125            command_buffer.insert_one(entity, Follow);
126        }
127    }
128    command_buffer.run_on(&mut scene.world);
129}
130/// System to Advance Animation timer
131#[allow(clippy::cast_precision_loss)]
132pub extern "C" fn smpl_advance_anim(scene: &mut Scene, runner: &mut RunnerState) {
133    let mut command_buffer = CommandBuffer::new();
134    {
135        if !scene.has_resource::<SceneAnimation>() {
136            return;
137        }
138        let mut entities_with_anim = Vec::new();
139        {
140            let mut query_state = scene.world.query::<(&Animation, Changed<Animation>)>().with::<&SmplParams>();
141            for (entity, (smpl_anim, changed_anim)) in query_state.iter() {
142                if (!smpl_anim.runner.paused && !smpl_anim.runner.temporary_pause) || changed_anim {
143                    entities_with_anim.push(entity);
144                }
145            }
146        }
147        let mut entities_within_interval = Vec::new();
148        let mut entities_outside_interval = Vec::new();
149        {
150            let mut scene_anim = scene.get_resource::<&mut SceneAnimation>().unwrap();
151            let current_global_time = scene_anim.runner.anim_current_time.as_secs_f32();
152            for entity in entities_with_anim.iter() {
153                let mut smpl_anim = scene.get_comp::<&mut Animation>(entity).unwrap();
154                let global_fps = scene_anim.config.fps;
155                smpl_anim.config.fps = global_fps;
156                let start_offset = smpl_anim.start_offset;
157                let is_within_interval = current_global_time >= (smpl_anim.start_offset as f32 / scene_anim.config.fps)
158                    && current_global_time <= ((smpl_anim.start_offset + smpl_anim.num_animation_frames()) as f32 / scene_anim.config.fps);
159                if is_within_interval {
160                    smpl_anim.set_cur_time_as_sec(current_global_time - (start_offset as f32 / global_fps));
161                    entities_within_interval.push(*entity);
162                } else {
163                    entities_outside_interval.push(*entity);
164                    continue;
165                }
166                if !scene_anim.runner.paused && !scene_anim.runner.temporary_pause {
167                    let is_added = smpl_anim.is_added();
168                    smpl_anim.advance(runner.dt(), runner.is_first_time() || is_added);
169                }
170                let anim_frame = smpl_anim.get_current_pose();
171                command_buffer.insert_one(*entity, anim_frame);
172                if let Some(expression) = smpl_anim.get_current_expression() {
173                    command_buffer.insert_one(*entity, expression);
174                }
175            }
176            if !scene_anim.runner.paused && !scene_anim.runner.temporary_pause && scene_anim.num_frames != 0 {
177                let is_added = scene_anim.is_added();
178                scene_anim.advance(runner.dt(), runner.is_first_time() || is_added);
179            }
180        }
181        for entity in entities_within_interval {
182            if !scene.world.has::<Renderable>(entity).unwrap() {
183                scene.world.insert_one(entity, Renderable).unwrap();
184            }
185        }
186        for entity in entities_outside_interval {
187            if scene.world.has::<Renderable>(entity).unwrap() {
188                scene.world.remove_one::<Renderable>(entity).unwrap();
189            }
190        }
191        runner.request_redraw();
192    }
193    command_buffer.run_on(&mut scene.world);
194}
195/// System to compute vertices if Betas has changed.
196/// This internally uses the generic variant of the function suffixed with
197/// ``_on_backend``
198#[allow(clippy::similar_names)]
199#[allow(clippy::too_many_lines)]
200pub extern "C" fn smpl_betas_to_verts(scene: &mut Scene, _runner: &mut RunnerState) {
201    let mut command_buffer = CommandBuffer::new();
202    {
203        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
204        let changed_models = smpl_models_dynamic.is_changed();
205        let mut query_state = scene.world.query::<(&SmplParams, &Betas, Changed<Betas>, Changed<SmplParams>)>();
206        for (entity, (smpl_params, smpl_betas, changed_betas, changed_smpl_params)) in query_state.iter() {
207            if !changed_betas && !changed_smpl_params && !changed_models {
208                continue;
209            }
210            match &*smpl_models_dynamic {
211                SmplCacheDynamic::NdArray(model) => betas_to_verts_on_backend::<NdArray>(&mut command_buffer, entity, smpl_params, smpl_betas, model),
212                SmplCacheDynamic::Wgpu(model) => betas_to_verts_on_backend::<Wgpu>(&mut command_buffer, entity, smpl_params, smpl_betas, model),
213                SmplCacheDynamic::Candle(model) => betas_to_verts_on_backend::<Candle>(&mut command_buffer, entity, smpl_params, smpl_betas, model),
214            }
215        }
216    }
217    command_buffer.run_on(&mut scene.world);
218}
219/// Function to compute vertices from betas on a generic Burn Backend. We
220/// currently support - ``Candle``, ``NdArray``, and ``Wgpu``
221#[allow(clippy::too_many_arguments)]
222fn betas_to_verts_on_backend<B: Backend>(
223    command_buffer: &mut CommandBuffer,
224    entity: Entity,
225    smpl_params: &SmplParams,
226    smpl_betas: &Betas,
227    smpl_models: &SmplCache<B>,
228) where
229    <B as Backend>::FloatTensorPrimitive<2>: Sync,
230    <B as Backend>::IntTensorPrimitive<2>: Sync,
231    B::QuantizedTensorPrimitive<1>: Sync,
232    B::QuantizedTensorPrimitive<2>: Sync,
233    B::QuantizedTensorPrimitive<3>: Sync,
234{
235    let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
236    let v_burn_merged = smpl_model.betas2verts(smpl_betas);
237    let joints_t_pose = smpl_model.verts2joints(v_burn_merged.clone());
238    let smpl_output = SmplOutputPoseTDynamic {
239        verts: v_burn_merged.clone(),
240        verts_without_expression: v_burn_merged,
241        joints: joints_t_pose,
242    };
243    command_buffer.insert_one(entity, smpl_output);
244}
245/// System to compute Expression offsets if Expression or ``SmplParams`` have
246/// changed. This internally uses the generic variant of the function suffixed
247/// with ``_on_backend``
248#[allow(clippy::similar_names)]
249pub extern "C" fn smpl_expression_offsets(scene: &mut Scene, _runner: &mut RunnerState) {
250    let mut command_buffer = CommandBuffer::new();
251    {
252        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
253        let changed_models = smpl_models_dynamic.is_changed();
254        let mut query_state = scene
255            .world
256            .query::<(&SmplParams, &Expression, Changed<Expression>, Changed<SmplParams>)>();
257        for (entity, (smpl_params, expression, changed_expression, changed_smpl_params)) in query_state.iter() {
258            if !changed_expression && !changed_smpl_params && !changed_models {
259                continue;
260            }
261            match &*smpl_models_dynamic {
262                SmplCacheDynamic::NdArray(smpl_models_ndarray) => {
263                    expression_offsets_on_backend::<NdArray>(&mut command_buffer, entity, smpl_params, expression, smpl_models_ndarray);
264                }
265                SmplCacheDynamic::Wgpu(smpl_models_wgpu) => {
266                    expression_offsets_on_backend::<Wgpu>(&mut command_buffer, entity, smpl_params, expression, smpl_models_wgpu);
267                }
268                SmplCacheDynamic::Candle(smpl_models_candle) => {
269                    expression_offsets_on_backend::<Candle>(&mut command_buffer, entity, smpl_params, expression, smpl_models_candle);
270                }
271            }
272        }
273    }
274    command_buffer.run_on(&mut scene.world);
275}
276/// Function to compute expression offsets on a generic Burn Backend. We
277/// currently support - ``Candle``, ``NdArray``, and ``Wgpu``
278#[allow(clippy::too_many_arguments)]
279fn expression_offsets_on_backend<B: Backend>(
280    command_buffer: &mut CommandBuffer,
281    entity: Entity,
282    smpl_params: &SmplParams,
283    expression: &Expression,
284    smpl_models: &SmplCache<B>,
285) where
286    <B as Backend>::FloatTensorPrimitive<2>: Sync,
287    <B as Backend>::IntTensorPrimitive<2>: Sync,
288    B::QuantizedTensorPrimitive<1>: Sync,
289    B::QuantizedTensorPrimitive<2>: Sync,
290    B::QuantizedTensorPrimitive<3>: Sync,
291{
292    let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
293    let verts_offsets_merged = smpl_model.expression2offsets(expression);
294    let expr_offsets = ExpressionOffsets {
295        offsets: verts_offsets_merged,
296    };
297    command_buffer.insert_one(entity, expr_offsets);
298}
299/// System to apply the expression offsets.
300/// This internally uses the generic variant of the function suffixed with
301/// ``_on_backend``
302#[allow(clippy::too_many_lines)]
303pub extern "C" fn smpl_expression_apply(scene: &mut Scene, _runner: &mut RunnerState) {
304    let mut command_buffer = CommandBuffer::new();
305    {
306        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
307        match &*smpl_models_dynamic {
308            SmplCacheDynamic::NdArray(smpl_models_nd) => {
309                apply_expression_on_backend::<NdArray>(scene, &mut command_buffer, smpl_models_nd);
310            }
311            SmplCacheDynamic::Wgpu(smpl_models_wgpu) => {
312                apply_expression_on_backend::<Wgpu>(scene, &mut command_buffer, smpl_models_wgpu);
313            }
314            SmplCacheDynamic::Candle(smpl_models_cndl) => {
315                apply_expression_on_backend::<Candle>(scene, &mut command_buffer, smpl_models_cndl);
316            }
317        }
318    }
319    command_buffer.run_on(&mut scene.world);
320}
321/// Function to apply expression offsets on a generic Burn Backend. We currently
322/// support - ``Candle``, ``NdArray``, and ``Wgpu``
323#[allow(clippy::too_many_arguments)]
324fn apply_expression_on_backend<B: Backend>(scene: &Scene, command_buffer: &mut CommandBuffer, smpl_models: &SmplCache<B>)
325where
326    <B as Backend>::FloatTensorPrimitive<2>: Sync,
327    <B as Backend>::IntTensorPrimitive<2>: Sync,
328    B::QuantizedTensorPrimitive<1>: Sync,
329    B::QuantizedTensorPrimitive<2>: Sync,
330    B::QuantizedTensorPrimitive<3>: Sync,
331{
332    let mut query_state = scene.world.query::<(
333        &SmplParams,
334        &mut SmplOutputPoseTDynamic<B>,
335        &ExpressionOffsets<B>,
336        Changed<SmplOutputPoseTDynamic<B>>,
337        Changed<ExpressionOffsets<B>>,
338        Changed<SmplParams>,
339    )>();
340    for (entity, (smpl_params, mut smpl_t_output, expression_offsets, changed_smpl_t, changed_expression, changed_params)) in query_state.iter() {
341        if !changed_smpl_t && !changed_expression && !changed_params {
342            continue;
343        }
344        let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
345        smpl_t_output.verts = smpl_t_output.verts_without_expression.clone() + expression_offsets.offsets.clone();
346        smpl_t_output.joints = smpl_model.verts2joints(smpl_t_output.verts.clone());
347        command_buffer.insert_one(entity, smpl_t_output.clone());
348    }
349}
350/// System to remap the ``SmplType`` of a pose to the ``SmplType`` found in
351/// ``SmplParams``
352pub extern "C" fn smpl_pose_remap(scene: &mut Scene, _runner: &mut RunnerState) {
353    let mut command_buffer = CommandBuffer::new();
354    {
355        let mut query_state = scene.world.query::<(&Pose, &SmplParams, Changed<Pose>)>();
356        for (entity, (smpl_pose, smpl_params, changed_pose)) in query_state.iter() {
357            if !changed_pose {
358                continue;
359            }
360            let pose_remap = PoseRemap::new(smpl_pose.smpl_type, smpl_params.smpl_type);
361            let new_pose = pose_remap.remap(smpl_pose);
362            command_buffer.insert_one(entity, new_pose);
363        }
364    }
365    command_buffer.run_on(&mut scene.world);
366}
367/// System for handling pose overrides
368pub extern "C" fn smpl_mask_pose(scene: &mut Scene, _runner: &mut RunnerState) {
369    let mut command_buffer = CommandBuffer::new();
370    {
371        let mut query_state = scene
372            .world
373            .query::<(&mut Pose, &mut PoseOverride, Changed<Pose>, Changed<PoseOverride>)>()
374            .with::<&SmplParams>();
375        for (_entity, (mut smpl_pose, mut pose_mask, changed_pose, changed_pose_mask)) in query_state.iter() {
376            if !changed_pose && !changed_pose_mask {
377                continue;
378            }
379            smpl_pose.apply_mask(&mut pose_mask);
380        }
381    }
382    command_buffer.run_on(&mut scene.world);
383}
384/// Smpl bodies have to be assigned some pose, so if we have no Pose and no
385/// Animation we set a dummy default pose
386pub extern "C" fn smpl_make_dummy_pose(scene: &mut Scene, _runner: &mut RunnerState) {
387    let mut command_buffer = CommandBuffer::new();
388    {
389        let mut query_state = scene.world.query::<&SmplParams>().without::<&Pose>().without::<&Animation>();
390        for (entity, smpl_params) in query_state.iter() {
391            let pose = Pose::new_empty(UpAxis::Y, smpl_params.smpl_type);
392            command_buffer.insert_one(entity, pose);
393        }
394    }
395    command_buffer.run_on(&mut scene.world);
396}
397/// System for computing and applying pose correctives given a pose
398/// This internally uses the generic variant of the function suffixed with
399/// ``_on_backend``
400#[allow(clippy::similar_names)]
401#[allow(clippy::too_many_lines)]
402pub extern "C" fn smpl_compute_pose_correctives(scene: &mut Scene, _runner: &mut RunnerState) {
403    let mut command_buffer = CommandBuffer::new();
404    {
405        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
406        let smpl_models_changed = smpl_models_dynamic.is_changed();
407        match &*smpl_models_dynamic {
408            SmplCacheDynamic::NdArray(smpl_models_ndarray) => {
409                compute_pose_correctives_on_backend::<NdArray>(scene, &mut command_buffer, smpl_models_ndarray, smpl_models_changed);
410            }
411            SmplCacheDynamic::Wgpu(smpl_models_wgpu) => {
412                compute_pose_correctives_on_backend::<Wgpu>(scene, &mut command_buffer, smpl_models_wgpu, smpl_models_changed);
413            }
414            SmplCacheDynamic::Candle(smpl_models_candle) => {
415                compute_pose_correctives_on_backend::<Candle>(scene, &mut command_buffer, smpl_models_candle, smpl_models_changed);
416            }
417        }
418    }
419    command_buffer.run_on(&mut scene.world);
420}
421/// Function to compute pose correctives given a pose on a generic Burn Backend.
422/// We currently support - ``Candle``, ``NdArray``, and ``Wgpu``
423#[allow(clippy::too_many_arguments)]
424fn compute_pose_correctives_on_backend<B: Backend>(
425    scene: &Scene,
426    command_buffer: &mut CommandBuffer,
427    smpl_models: &SmplCache<B>,
428    smpl_models_changed: bool,
429) where
430    <B as Backend>::FloatTensorPrimitive<2>: Sync,
431    <B as Backend>::IntTensorPrimitive<2>: Sync,
432    B::QuantizedTensorPrimitive<1>: Sync,
433    B::QuantizedTensorPrimitive<2>: Sync,
434    B::QuantizedTensorPrimitive<3>: Sync,
435{
436    let mut query_state = scene.world.query::<(&SmplParams, &mut Pose, Changed<Pose>, Changed<SmplParams>)>();
437    for (entity, (smpl_params, smpl_pose, changed_pose, changed_smpl_params)) in query_state.iter() {
438        if (!changed_pose && !changed_smpl_params && !smpl_models_changed) || !smpl_params.enable_pose_corrective {
439            continue;
440        }
441        let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
442        let verts_offset = smpl_model.compute_pose_correctives(&smpl_pose);
443        command_buffer.insert_one(entity, PoseCorrectiveDynamic::<B> { verts_offset });
444    }
445}
446/// System for applying a pose to the given template
447/// This internally uses the generic variant of the function suffixed with
448/// ``_on_backend``
449#[allow(clippy::too_many_lines)]
450pub extern "C" fn smpl_apply_pose(scene: &mut Scene, _runner: &mut RunnerState) {
451    let mut command_buffer = CommandBuffer::new();
452    {
453        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
454        match &*smpl_models_dynamic {
455            SmplCacheDynamic::NdArray(smpl_models_ndarray) => {
456                apply_pose_on_backend::<NdArray>(scene, &mut command_buffer, smpl_models_ndarray);
457            }
458            SmplCacheDynamic::Wgpu(smpl_models_wgpu) => {
459                apply_pose_on_backend::<Wgpu>(scene, &mut command_buffer, smpl_models_wgpu);
460            }
461            SmplCacheDynamic::Candle(smpl_models_candle) => {
462                apply_pose_on_backend::<Candle>(scene, &mut command_buffer, smpl_models_candle);
463            }
464        }
465    }
466    command_buffer.run_on(&mut scene.world);
467}
468/// Function for applying pose on a generic Burn Backend. We currently support -
469/// ``Candle``, ``NdArray``, and ``Wgpu``
470#[allow(clippy::too_many_arguments)]
471fn apply_pose_on_backend<B: Backend>(scene: &Scene, command_buffer: &mut CommandBuffer, smpl_models: &SmplCache<B>)
472where
473    <B as Backend>::FloatTensorPrimitive<2>: Sync,
474    <B as Backend>::IntTensorPrimitive<2>: Sync,
475    B::QuantizedTensorPrimitive<1>: Sync,
476    B::QuantizedTensorPrimitive<2>: Sync,
477    B::QuantizedTensorPrimitive<3>: Sync,
478{
479    let mut query_state = scene.world.query::<(
480        &SmplParams,
481        &mut SmplOutputPoseTDynamic<B>,
482        &mut Pose,
483        Option<&PoseCorrectiveDynamic<B>>,
484        Changed<Pose>,
485        Changed<SmplOutputPoseTDynamic<B>>,
486        Changed<SmplParams>,
487    )>();
488    for (entity, (smpl_params, smpl_t_output, smpl_pose, pose_corrective, changed_pose, changed_t_output, changed_smpl_params)) in query_state.iter()
489    {
490        if !changed_pose && !changed_t_output && !changed_smpl_params {
491            continue;
492        }
493        let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
494        let lbs_weights_merged = smpl_model.lbs_weights();
495        let mut verts_burn_merged = smpl_t_output.verts.clone();
496        let joints_t_pose = &smpl_t_output.joints;
497        let new_pose = smpl_pose.clone();
498        if let Some(pose_corrective) = pose_corrective {
499            if smpl_params.enable_pose_corrective {
500                let v_offset_merged = &pose_corrective.verts_offset;
501                verts_burn_merged = verts_burn_merged.add(v_offset_merged.clone());
502            }
503        }
504        let (verts_posed_nd, _, _, joints_posed) =
505            smpl_model.apply_pose(&verts_burn_merged, None, None, joints_t_pose, &lbs_weights_merged, &new_pose);
506        command_buffer.insert_one(
507            entity,
508            SmplOutputPosedDynamic {
509                verts: verts_posed_nd,
510                joints: joints_posed,
511            },
512        );
513    }
514}
515/// System to convert ``SmplOutput`` components to gloss components (``Verts``,
516/// ``Faces``, ``Normals``, etc.) for the ``upload_pass``
517#[allow(clippy::too_many_lines)]
518pub extern "C" fn smpl_to_gloss_mesh(scene: &mut Scene, _runner: &mut RunnerState) {
519    let mut command_buffer = CommandBuffer::new();
520    {
521        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
522        match &*smpl_models_dynamic {
523            SmplCacheDynamic::NdArray(smpl_models_ndarray) => {
524                let mut query_state = scene.world.query::<(
525                    &SmplParams,
526                    &SmplOutputPosedDynamic<NdArray>,
527                    Changed<SmplOutputPosedDynamic<NdArray>>,
528                    &GlossInterop,
529                )>();
530                for (entity, (smpl_params, smpl_output, changed_output, gloss_interop)) in query_state.iter() {
531                    if !changed_output && !smpl_models_dynamic.is_changed() {
532                        continue;
533                    }
534                    let smpl_model = smpl_models_ndarray.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
535                    let (verts, uv, normals, tangents, faces) = compute_common_mesh_data(smpl_model, &smpl_output.verts, gloss_interop.with_uv);
536                    update_entity_on_backend(
537                        entity,
538                        scene,
539                        &mut command_buffer,
540                        gloss_interop.with_uv,
541                        &DynamicTensorFloat2D::NdArray(verts),
542                        &DynamicTensorFloat2D::NdArray(normals),
543                        tangents.map(DynamicTensorFloat2D::NdArray),
544                        DynamicTensorFloat2D::NdArray(uv),
545                        DynamicTensorInt2D::NdArray(faces),
546                        smpl_model,
547                    );
548                }
549            }
550            SmplCacheDynamic::Wgpu(smpl_models_wgpu) => {
551                let mut query_state = scene.world.query::<(
552                    &SmplParams,
553                    &SmplOutputPosedDynamic<Wgpu>,
554                    Changed<SmplOutputPosedDynamic<Wgpu>>,
555                    &GlossInterop,
556                )>();
557                for (entity, (smpl_params, smpl_output, changed_output, gloss_interop)) in query_state.iter() {
558                    if !changed_output && !smpl_models_dynamic.is_changed() {
559                        continue;
560                    }
561                    let smpl_model = smpl_models_wgpu.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
562                    let (verts, uv, normals, tangents, faces) = compute_common_mesh_data(smpl_model, &smpl_output.verts, gloss_interop.with_uv);
563                    update_entity_on_backend(
564                        entity,
565                        scene,
566                        &mut command_buffer,
567                        gloss_interop.with_uv,
568                        &DynamicTensorFloat2D::Wgpu(verts),
569                        &DynamicTensorFloat2D::Wgpu(normals),
570                        tangents.map(DynamicTensorFloat2D::Wgpu),
571                        DynamicTensorFloat2D::Wgpu(uv),
572                        DynamicTensorInt2D::Wgpu(faces),
573                        smpl_model,
574                    );
575                }
576            }
577            SmplCacheDynamic::Candle(smpl_models_candle) => {
578                let mut query_state = scene.world.query::<(
579                    &SmplParams,
580                    &SmplOutputPosedDynamic<Candle>,
581                    Changed<SmplOutputPosedDynamic<Candle>>,
582                    &GlossInterop,
583                )>();
584                for (entity, (smpl_params, smpl_output, changed_output, gloss_interop)) in query_state.iter() {
585                    if !changed_output && !smpl_models_dynamic.is_changed() {
586                        continue;
587                    }
588                    let smpl_model = smpl_models_candle.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
589                    let (verts, uv, normals, tangents, faces) = compute_common_mesh_data(smpl_model, &smpl_output.verts, gloss_interop.with_uv);
590                    update_entity_on_backend(
591                        entity,
592                        scene,
593                        &mut command_buffer,
594                        gloss_interop.with_uv,
595                        &DynamicTensorFloat2D::Candle(verts),
596                        &DynamicTensorFloat2D::Candle(normals),
597                        tangents.map(DynamicTensorFloat2D::Candle),
598                        DynamicTensorFloat2D::Candle(uv),
599                        DynamicTensorInt2D::Candle(faces),
600                        smpl_model,
601                    );
602                }
603            }
604        }
605    }
606    command_buffer.run_on(&mut scene.world);
607}
608/// Type alias to club all mesh data together
609type MeshDataResult<B> = (
610    Tensor<B, 2, Float>,
611    Tensor<B, 2, Float>,
612    Tensor<B, 2, Float>,
613    Option<Tensor<B, 2, Float>>,
614    Tensor<B, 2, Int>,
615);
616/// Function to compute data like Normals and Tangents on a generic Burn
617/// Backend. We currently support - ``Candle``, ``NdArray``, and ``Wgpu``
618fn compute_common_mesh_data<B: Backend>(smpl_model: &dyn SmplModel<B>, verts_burn: &Tensor<B, 2, Float>, with_uv: bool) -> MeshDataResult<B> {
619    let device_str = format!("{:?}", verts_burn.device());
620    let mapping = smpl_model.idx_split_2_merged();
621    let verts_final_burn = if with_uv {
622        verts_burn.clone().select(0, mapping.clone())
623    } else {
624        verts_burn.clone()
625    };
626    let uv_burn = smpl_model.uv().clone();
627    let faces_burn = smpl_model.faces();
628    let normals_merged_burn = match device_str.as_str() {
629        "Cpu" => Geom::compute_per_vertex_normals(
630            &verts_burn.to_nalgebra(),
631            &faces_burn.to_nalgebra(),
632            &PerVertexNormalsWeightingType::Uniform,
633        )
634        .to_burn(&verts_burn.device()),
635        _ => Geom::compute_per_vertex_normals_burn(verts_burn, faces_burn, &PerVertexNormalsWeightingType::Uniform),
636    };
637    let normals_final_burn = if with_uv {
638        normals_merged_burn.clone().select(0, mapping.clone())
639    } else {
640        normals_merged_burn
641    };
642    let tangents_burn = if with_uv {
643        match device_str.as_str() {
644            "Cpu" => Some(
645                Geom::compute_tangents(
646                    &verts_final_burn.to_nalgebra(),
647                    &smpl_model.faces_uv().to_nalgebra(),
648                    &normals_final_burn.to_nalgebra(),
649                    &smpl_model.uv().to_nalgebra(),
650                )
651                .to_burn(&verts_burn.device()),
652            ),
653            _ => Some(Geom::compute_tangents_burn(
654                &verts_final_burn,
655                smpl_model.faces_uv(),
656                &normals_final_burn,
657                smpl_model.uv(),
658            )),
659        }
660    } else {
661        None
662    };
663    let faces_burn = if with_uv { smpl_model.faces_uv() } else { faces_burn };
664    (verts_final_burn, uv_burn, normals_final_burn, tangents_burn, faces_burn.clone())
665}
666/// System to align a mesh at every frame to the floor
667/// This internally uses the generic variant of the function suffixed with
668/// ``_on_backend``
669#[allow(clippy::too_many_lines)]
670pub extern "C" fn smpl_align_vertical(scene: &mut Scene, _runner: &mut RunnerState) {
671    let mut command_buffer = CommandBuffer::new();
672    let backend = {
673        let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
674        smpl_models_dynamic.get_backend()
675    };
676    match backend {
677        BurnBackend::NdArray => align_vertical_on_backend::<NdArray>(scene),
678        BurnBackend::Wgpu => align_vertical_on_backend::<Wgpu>(scene),
679        BurnBackend::Candle => align_vertical_on_backend::<Candle>(scene),
680    }
681    command_buffer.run_on(&mut scene.world);
682}
683fn align_vertical_on_backend<B: Backend>(scene: &Scene)
684where
685    <B as Backend>::FloatTensorPrimitive<2>: Sync,
686    B::QuantizedTensorPrimitive<1>: Sync,
687    B::QuantizedTensorPrimitive<2>: Sync,
688    B::QuantizedTensorPrimitive<3>: Sync,
689{
690    let mut query_state = scene
691        .world
692        .query::<(&Verts, &mut ModelMatrix, Changed<SmplOutputPoseTDynamic<B>>)>()
693        .with::<&SmplParams>();
694    for (_entity, (verts, mut model_matrix, changed_t_pose)) in query_state.iter() {
695        if changed_t_pose {
696            let verts_world = Geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
697            let min_y = verts_world.column(1).min();
698            model_matrix.0.append_translation_mut(&na::Translation3::<f32>::new(0.0, -min_y, 0.0));
699        }
700    }
701}
702/// System for follower computations
703#[allow(clippy::cast_precision_loss)]
704pub extern "C" fn smpl_follow_anim(scene: &mut Scene, runner: &mut RunnerState) {
705    let mut command_buffer = CommandBuffer::new();
706    {
707        let backend = {
708            let smpl_models_dynamic = scene.get_resource::<&SmplCacheDynamic>().unwrap();
709            smpl_models_dynamic.get_backend()
710        };
711        let Ok(mut follow) = scene.get_resource::<&mut Follower>() else {
712            return;
713        };
714        let mut query_state = scene.world.query::<&Follow>().with::<&Renderable>();
715        let mut goal = na::Point3::new(0.0, 0.0, 0.0);
716        let mut num_ents = 0;
717        for (entity, _) in query_state.iter() {
718            let model_matrix = if let Ok(mm) = scene.world.get::<&mut ModelMatrix>(entity) {
719                mm.0
720            } else {
721                ModelMatrix::default().0
722            };
723            let ent_goal = match backend {
724                BurnBackend::NdArray => {
725                    if let Some(point) = handle_goal_for_backend::<NdArray>(scene, entity, model_matrix) {
726                        point
727                    } else {
728                        continue;
729                    }
730                }
731                BurnBackend::Wgpu => {
732                    if let Some(point) = handle_goal_for_backend::<Wgpu>(scene, entity, model_matrix) {
733                        point
734                    } else {
735                        continue;
736                    }
737                }
738                BurnBackend::Candle => {
739                    if let Some(point) = handle_goal_for_backend::<Candle>(scene, entity, model_matrix) {
740                        point
741                    } else {
742                        continue;
743                    }
744                }
745            };
746            goal.coords += ent_goal.coords;
747            num_ents += 1;
748        }
749        if num_ents > 0 {
750            goal.coords /= num_ents as f32;
751        }
752        #[allow(clippy::match_wildcard_for_single_variants)]
753        match follow.params.follower_type {
754            FollowerType::Cam | FollowerType::CamAndLights => {
755                let cam = scene.get_current_cam().unwrap();
756                if !cam.is_initialized(scene) {
757                    return;
758                }
759                if let Ok(mut poslookat) = scene.world.get::<&mut PosLookat>(cam.entity) {
760                    follow.update(&goal, &poslookat.lookat, runner.dt().as_secs_f32());
761                    let point_lookat = follow.get_point_follow("cam");
762                    let diff = (poslookat.lookat - point_lookat).norm();
763                    if diff > 1e-7 {
764                        runner.request_redraw();
765                        poslookat.shift_lookat(point_lookat);
766                    }
767                }
768            }
769            _ => {}
770        }
771        #[allow(clippy::match_wildcard_for_single_variants)]
772        match follow.params.follower_type {
773            FollowerType::Lights | FollowerType::CamAndLights => {
774                let lights = scene.get_lights(false);
775                for light in lights.iter() {
776                    if let Ok(mut poslookat) = scene.world.get::<&mut PosLookat>(*light) {
777                        let point_lookat = goal;
778                        let diff = (poslookat.lookat - point_lookat).norm();
779                        if diff > 1e-7 {
780                            runner.request_redraw();
781                            poslookat.shift_lookat(point_lookat);
782                        }
783                    }
784                }
785                let point_dist_fade_center = goal;
786                command_buffer.insert_one(
787                    scene.get_entity_resource(),
788                    ConfigChanges {
789                        new_distance_fade_center: point_dist_fade_center,
790                    },
791                );
792            }
793            _ => {}
794        }
795    }
796    command_buffer.run_on(&mut scene.world);
797}
798fn handle_goal_for_backend<B: Backend>(scene: &Scene, entity: Entity, model_matrix: na::SimilarityMatrix3<f32>) -> Option<na::Point3<f32>>
799where
800    <B as Backend>::FloatTensorPrimitive<2>: Sync,
801    <B as Backend>::IntTensorPrimitive<2>: Sync,
802    B::QuantizedTensorPrimitive<1>: Sync,
803    B::QuantizedTensorPrimitive<2>: Sync,
804    B::QuantizedTensorPrimitive<3>: Sync,
805{
806    if scene.world.has::<SmplOutputPosedDynamic<B>>(entity).unwrap() {
807        let output_posed = scene.world.get::<&SmplOutputPosedDynamic<B>>(entity).unwrap();
808        let joints_ndarray = output_posed.joints.to_ndarray();
809        let pose_trans = joints_ndarray.row(0).into_nalgebra();
810        let point = pose_trans.fixed_rows::<3>(0).clone_owned();
811        let mut point = na::Point3::<f32> { coords: point };
812        point = model_matrix * point;
813        Some(point)
814    } else if scene.world.has::<Verts>(entity).unwrap() && scene.world.has::<ModelMatrix>(entity).unwrap() {
815        let verts = scene.world.get::<&Verts>(entity).unwrap();
816        let model_matrix = scene.world.get::<&ModelMatrix>(entity).unwrap();
817        Some(Geom::get_centroid(&verts.0.to_dmatrix(), Some(model_matrix.0)))
818    } else {
819        None
820    }
821}
822/// Hides the floor if the camera is below it
823pub extern "C" fn hide_floor_when_viewed_from_below(scene: &mut Scene, _runner: &mut RunnerState) {
824    let camera = scene.get_current_cam().unwrap();
825    let pos = {
826        let Ok(pos_lookat) = scene.world.get::<&PosLookat>(camera.entity) else {
827            warn!("rs: hide_floor_when_viewed_from_below: No PosLookat yet, camera is not initialized. Auto adding default");
828            return;
829        };
830        pos_lookat.position
831    };
832    if let Some(floor) = scene.get_floor() {
833        let min_y = {
834            let Ok(verts) = scene.world.get::<&Verts>(floor.entity) else {
835                warn!("rs: hide_floor_when_viewed_from_below: No Verts on floor");
836                return;
837            };
838            let Ok(model_matrix) = scene.world.get::<&ModelMatrix>(floor.entity) else {
839                warn!("rs: hide_floor_when_viewed_from_below: No ModelMatrix on floor");
840                return;
841            };
842            let verts_world = Geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
843            verts_world.column(1).min()
844        };
845        if pos.coords.y < min_y {
846            if scene.world.has::<Renderable>(floor.entity).unwrap() {
847                scene.world.remove_one::<Renderable>(floor.entity).unwrap();
848            }
849        } else if !scene.world.has::<Renderable>(floor.entity).unwrap() {
850            scene.world.insert_one(floor.entity, Renderable).unwrap();
851        }
852    }
853}
854#[allow(missing_docs)]
855#[cfg(feature = "with-gui")]
856#[allow(clippy::semicolon_if_nothing_returned)]
857#[allow(clippy::too_many_lines)]
858#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
859pub extern "C" fn smpl_params_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
860    use gloss_renderer::plugin_manager::gui::Button;
861    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
862    use smpl_core::{
863        codec::{codec::SmplCodec, gltf::GltfCodec},
864        common::types::{Gender, GltfCompatibilityMode},
865    };
866    extern "C" fn enable_pose_corrective_toggle(new_val: bool, _widget_name: RString, entity: Entity, scene: &mut Scene) {
867        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(entity) {
868            smpl_params.enable_pose_corrective = new_val;
869        }
870    }
871    extern "C" fn save_smpl(_widget_name: RString, entity: Entity, scene: &mut Scene) {
872        let codec = SmplCodec::from_entity(&entity, scene, None);
873        codec.to_file("./saved.smpl");
874    }
875    extern "C" fn save_gltf_smpl(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
876        let mut codec = GltfCodec::from_scene(scene, None, None);
877        let now = wasm_timer::Instant::now();
878        codec.to_file(
879            "Meshcapade Avatar",
880            "./saved/output.gltf",
881            GltfOutputType::Standard,
882            GltfCompatibilityMode::Smpl,
883        );
884        codec.to_file(
885            "Meshcapade Avatar",
886            "./saved/output.glb",
887            GltfOutputType::Binary,
888            GltfCompatibilityMode::Smpl,
889        );
890        println!("Smpl mode `.gltf` export took {:?} seconds", now.elapsed());
891    }
892    extern "C" fn save_gltf_unreal(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
893        let mut codec = GltfCodec::from_scene(scene, None, None);
894        let now = wasm_timer::Instant::now();
895        codec.to_file(
896            "Meshcapade Avatar",
897            "./saved/output.gltf",
898            GltfOutputType::Standard,
899            GltfCompatibilityMode::Unreal,
900        );
901        codec.to_file(
902            "Meshcapade Avatar",
903            "./saved/output.glb",
904            GltfOutputType::Binary,
905            GltfCompatibilityMode::Unreal,
906        );
907        println!("Unreal mode `.gltf` export took {:?} seconds", now.elapsed());
908    }
909    extern "C" fn change_gender(_val: bool, widget_name: RString, entity: Entity, scene: &mut Scene) {
910        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(entity) {
911            match widget_name.as_str() {
912                "neutral" => smpl_params.gender = Gender::Neutral,
913                "female" => smpl_params.gender = Gender::Female,
914                "male" => smpl_params.gender = Gender::Male,
915                _ => {}
916            }
917        }
918    }
919    let mut widgets = RVec::new();
920    if let RSome(entity) = selected_entity {
921        if let Ok(smpl_params) = scene.world.get::<&SmplParams>(entity) {
922            let checkbox = Checkbox::new(
923                "enable_pose_corrective",
924                smpl_params.enable_pose_corrective,
925                enable_pose_corrective_toggle,
926            );
927            let is_neutral = smpl_params.gender == Gender::Neutral;
928            let is_female = smpl_params.gender == Gender::Female;
929            let is_male = smpl_params.gender == Gender::Male;
930            let chk_neutral = Checkbox::new("neutral", is_neutral, change_gender);
931            let chk_female = Checkbox::new("female", is_female, change_gender);
932            let chk_male = Checkbox::new("male", is_male, change_gender);
933            let button_save_smpl = Button::new("Save as .smpl", save_smpl);
934            let button_save_gltf_smpl = Button::new("Save as .gltf (SMPL)", save_gltf_smpl);
935            let button_save_gltf_unreal = Button::new("Save as .gltf (UNREAL)", save_gltf_unreal);
936            widgets.push(Widgets::Checkbox(chk_neutral));
937            widgets.push(Widgets::Checkbox(chk_female));
938            widgets.push(Widgets::Checkbox(chk_male));
939            widgets.push(Widgets::Checkbox(checkbox));
940            widgets.push(Widgets::Button(button_save_smpl));
941            widgets.push(Widgets::Button(button_save_gltf_smpl));
942            widgets.push(Widgets::Button(button_save_gltf_unreal));
943        }
944    }
945    GuiWindow {
946        window_name: RString::from("SmplParams"),
947        window_type: GuiWindowType::Sidebar,
948        widgets,
949    }
950}
951#[allow(missing_docs)]
952#[cfg(feature = "with-gui")]
953#[allow(clippy::semicolon_if_nothing_returned)]
954#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
955pub extern "C" fn smpl_betas_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
956    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
957    extern "C" fn beta_slider_change(new_val: f32, widget_name: RString, entity: Entity, scene: &mut Scene) {
958        let beta_idx: usize = widget_name.split('_').last().unwrap().parse().unwrap();
959        if let Ok(mut betas) = scene.world.get::<&mut Betas>(entity) {
960            betas.betas[beta_idx] = new_val;
961        }
962    }
963    let mut widgets = RVec::new();
964    if let RSome(entity) = selected_entity {
965        if let Ok(betas) = scene.world.get::<&Betas>(entity) {
966            for i in 0..betas.betas.len() {
967                let slider = Slider::new(
968                    ("Beta_".to_owned() + &i.to_string()).as_str(),
969                    betas.betas[i],
970                    -5.0,
971                    5.0,
972                    RSome(80.0),
973                    beta_slider_change,
974                    RNone,
975                );
976                widgets.push(Widgets::Slider(slider));
977            }
978        }
979    }
980    GuiWindow {
981        window_name: RString::from("Betas"),
982        window_type: GuiWindowType::Sidebar,
983        widgets,
984    }
985}
986#[allow(missing_docs)]
987#[cfg(feature = "with-gui")]
988#[allow(clippy::semicolon_if_nothing_returned)]
989#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
990pub extern "C" fn smpl_expression_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
991    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
992    extern "C" fn expr_slider_change(new_val: f32, widget_name: RString, entity: Entity, scene: &mut Scene) {
993        let coeff_idx: usize = widget_name.split('_').last().unwrap().parse().unwrap();
994        if let Ok(mut coeffs) = scene.world.get::<&mut Expression>(entity) {
995            coeffs.expr_coeffs[coeff_idx] = new_val;
996        }
997    }
998    let mut widgets = RVec::new();
999    if let RSome(entity) = selected_entity {
1000        if let Ok(expression) = scene.world.get::<&Expression>(entity) {
1001            for i in 0..expression.expr_coeffs.len() {
1002                let slider = Slider::new(
1003                    ("Coeff_".to_owned() + &i.to_string()).as_str(),
1004                    expression.expr_coeffs[i],
1005                    -5.0,
1006                    5.0,
1007                    RSome(80.0),
1008                    expr_slider_change,
1009                    RNone,
1010                );
1011                widgets.push(Widgets::Slider(slider));
1012            }
1013        }
1014    }
1015    GuiWindow {
1016        window_name: RString::from("Expression"),
1017        window_type: GuiWindowType::Sidebar,
1018        widgets,
1019    }
1020}
1021#[allow(missing_docs)]
1022#[cfg(feature = "with-gui")]
1023#[allow(clippy::semicolon_if_nothing_returned)]
1024#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1025pub extern "C" fn smpl_anim_scroll_gui(_selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
1026    use gloss_renderer::plugin_manager::gui::{Button, WindowPivot, WindowPosition, WindowPositionType};
1027    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
1028    extern "C" fn scene_anim_slider_change(new_val: f32, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1029        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1030            scene_anim.set_cur_time_as_sec(new_val);
1031            scene_anim.runner.temporary_pause = true;
1032        }
1033    }
1034    extern "C" fn scene_anim_slider_no_change(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1035        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1036            scene_anim.runner.temporary_pause = false;
1037        }
1038    }
1039    extern "C" fn scene_button_play_pause(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1040        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1041            scene_anim.runner.paused = !scene_anim.runner.paused;
1042        }
1043    }
1044    #[allow(clippy::cast_possible_truncation)]
1045    #[allow(clippy::cast_sign_loss)]
1046    extern "C" fn scene_button_next_frame(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1047        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1048            let nr_frames = scene_anim.num_frames;
1049            let duration = scene_anim.duration();
1050            let dt_between_frames = duration / nr_frames as u32;
1051            scene_anim.advance(dt_between_frames, false);
1052        }
1053    }
1054    extern "C" fn scene_fps_slider_change(new_val: f32, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1055        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1056            let cur_time = scene_anim.get_cur_time();
1057            let prev_fps = scene_anim.config.fps;
1058            let multiplier_duration = prev_fps / new_val;
1059            scene_anim.set_cur_time_as_sec(cur_time.as_secs_f32() * multiplier_duration);
1060            scene_anim.config.fps = new_val;
1061        }
1062    }
1063    extern "C" fn follow_anim(new_val: bool, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1064        if new_val {
1065            scene.add_resource(Follower::new(FollowParams::default()));
1066        } else {
1067            let _ = scene.remove_resource::<Follower>();
1068        }
1069    }
1070    let mut widgets = RVec::new();
1071    if let Ok(scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1072        if scene_anim.num_frames != 0 {
1073            let max_duration = scene_anim.duration().as_secs_f32();
1074            let cur_time = scene_anim.get_cur_time().as_secs_f32();
1075            let slider_anim = Slider::new(
1076                "AnimTime",
1077                cur_time,
1078                0.0,
1079                max_duration,
1080                RSome(400.0),
1081                scene_anim_slider_change,
1082                RSome(scene_anim_slider_no_change as extern "C" fn(RString, Entity, &mut Scene)),
1083            );
1084            let button_play_pause = Button::new("play/pause", scene_button_play_pause);
1085            let button_next_frame = Button::new("next_frame", scene_button_next_frame);
1086            let slider_fps = Slider::new("FPS", scene_anim.config.fps, 1.0, 120.0, RSome(100.0), scene_fps_slider_change, RNone);
1087            let chk_follow_anim = Checkbox::new("follow", scene.has_resource::<Follower>(), follow_anim);
1088            widgets.push(Widgets::Slider(slider_anim));
1089            widgets.push(Widgets::Horizontal(RVec::from(vec![
1090                Widgets::Button(button_play_pause),
1091                Widgets::Button(button_next_frame),
1092                Widgets::Slider(slider_fps),
1093            ])));
1094            widgets.push(Widgets::Horizontal(RVec::from(vec![Widgets::Checkbox(chk_follow_anim)])));
1095        }
1096    }
1097    GuiWindow {
1098        window_name: RString::from("Scene"),
1099        window_type: GuiWindowType::FloatWindow(WindowPivot::CenterBottom, WindowPosition([0.5, 1.0]), WindowPositionType::Fixed),
1100        widgets,
1101    }
1102}
1103#[allow(missing_docs)]
1104#[cfg(feature = "with-gui")]
1105#[allow(clippy::semicolon_if_nothing_returned)]
1106#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1107pub extern "C" fn smpl_hand_pose_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
1108    use gloss_renderer::plugin_manager::gui::SelectableList;
1109    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
1110    use log::warn;
1111    use smpl_core::common::pose_hands::HandType;
1112    extern "C" fn set_hand_pose_type(widget_name: RString, entity: Entity, scene: &mut Scene) {
1113        let hand_type = match widget_name.to_string().as_str() {
1114            "Flat" => Some(HandType::Flat),
1115            "Relaxed" => Some(HandType::Relaxed),
1116            "Curled" => Some(HandType::Curled),
1117            "Fist" => Some(HandType::Fist),
1118            _ => {
1119                warn!("HandType not known");
1120                None
1121            }
1122        };
1123        let mut command_buffer = CommandBuffer::new();
1124        if let Some(hand_type) = hand_type {
1125            info!("setting to {hand_type:?}");
1126            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(entity) {
1127                info!("we already have a pose mask");
1128                pose_mask.set_overwrite_hands(hand_type);
1129            } else {
1130                info!("inserting a new pose mask");
1131                let pose_mask = PoseOverride::allow_all().overwrite_hands(hand_type).build();
1132                command_buffer.insert_one(entity, pose_mask);
1133            }
1134        } else {
1135            info!("removing overwrite");
1136            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(entity) {
1137                info!("removing overwrite and we have posemask");
1138                if pose_mask.get_overwrite_hands_type().is_some() {
1139                    pose_mask.remove_overwrite_hands();
1140                }
1141            }
1142        }
1143        command_buffer.run_on(&mut scene.world);
1144    }
1145    let mut widgets = RVec::new();
1146    if let RSome(entity) = selected_entity {
1147        if let Ok(pose_mask) = scene.world.get::<&PoseOverride>(entity) {
1148            let hand_type_overwrite = pose_mask.get_overwrite_hands_type();
1149            let mut selectable_vec = RVec::new();
1150            selectable_vec.push(Selectable::new("None", hand_type_overwrite.is_none(), set_hand_pose_type));
1151            selectable_vec.push(Selectable::new("Flat", hand_type_overwrite == Some(HandType::Flat), set_hand_pose_type));
1152            selectable_vec.push(Selectable::new(
1153                "Relaxed",
1154                hand_type_overwrite == Some(HandType::Relaxed),
1155                set_hand_pose_type,
1156            ));
1157            selectable_vec.push(Selectable::new(
1158                "Curled",
1159                hand_type_overwrite == Some(HandType::Curled),
1160                set_hand_pose_type,
1161            ));
1162            selectable_vec.push(Selectable::new("Fist", hand_type_overwrite == Some(HandType::Fist), set_hand_pose_type));
1163            let selectable_list = SelectableList::new(selectable_vec, true);
1164            widgets.push(Widgets::SelectableList(selectable_list));
1165        }
1166    }
1167    GuiWindow {
1168        window_name: RString::from("HandOverwrite"),
1169        window_type: GuiWindowType::Sidebar,
1170        widgets,
1171    }
1172}
1173#[allow(missing_docs)]
1174#[cfg(feature = "with-gui")]
1175#[allow(clippy::cast_precision_loss)]
1176#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1177pub extern "C" fn smpl_event_dropfile(scene: &mut Scene, _runner: &mut RunnerState, event: &Event) -> bool {
1178    use crate::scene::McsCodecGloss;
1179    use log::warn;
1180    use smpl_core::codec::{codec::SmplCodec, scene::McsCodec};
1181    use std::path::PathBuf;
1182    let mut handled = false;
1183    match event {
1184        Event::DroppedFile(path) => {
1185            let path_buf = PathBuf::from(path.to_string());
1186            let filetype = match path_buf.extension() {
1187                Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
1188                None => FileType::Unknown,
1189            };
1190            if scene.has_resource::<SceneAnimation>() {
1191                scene.remove_resource::<SceneAnimation>().unwrap();
1192            }
1193            match filetype {
1194                FileType::Smpl => {
1195                    info!("handling dropped smpl file {}", path);
1196                    let codec = SmplCodec::from_file(path);
1197                    let mut builder = codec.to_entity_builder();
1198                    if !builder.has::<Betas>() {
1199                        warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
1200                        builder.add(Betas::default());
1201                    }
1202                    let gloss_interop = GlossInterop::default();
1203                    let name = scene.get_unused_name();
1204                    scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
1205                    handled = true;
1206                }
1207                FileType::Mcs => {
1208                    info!("handling dropped mcs file {}", path);
1209                    let mut codec = McsCodec::from_file(path);
1210                    let builders = codec.to_entity_builders();
1211                    for mut builder in builders {
1212                        if !builder.has::<Betas>() {
1213                            warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
1214                            builder.add(Betas::default());
1215                        }
1216                        let gloss_interop = GlossInterop::default();
1217                        let name = scene.get_unused_name();
1218                        scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
1219                        handled = true;
1220                    }
1221                    let smpl_scene = SceneAnimation::new_with_fps(codec.num_frames, codec.frame_rate);
1222                    scene.add_resource(smpl_scene);
1223                }
1224                _ => {
1225                    info!("No known filetype {}", path);
1226                }
1227            }
1228        }
1229    }
1230    handled
1231}