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 {
850            if !scene.world.has::<Renderable>(floor.entity).unwrap() {
851                scene.world.insert_one(floor.entity, Renderable).unwrap();
852            }
853        }
854    }
855}
856#[allow(missing_docs)]
857#[cfg(feature = "with-gui")]
858#[allow(clippy::semicolon_if_nothing_returned)]
859#[allow(clippy::too_many_lines)]
860#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
861pub extern "C" fn smpl_params_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
862    use gloss_renderer::plugin_manager::gui::Button;
863    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
864    use smpl_core::{
865        codec::{codec::SmplCodec, gltf::GltfCodec},
866        common::types::{Gender, GltfCompatibilityMode},
867    };
868    extern "C" fn enable_pose_corrective_toggle(new_val: bool, _widget_name: RString, entity: Entity, scene: &mut Scene) {
869        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(entity) {
870            smpl_params.enable_pose_corrective = new_val;
871        }
872    }
873    extern "C" fn save_smpl(_widget_name: RString, entity: Entity, scene: &mut Scene) {
874        let codec = SmplCodec::from_entity(&entity, scene, None);
875        codec.to_file("./saved.smpl");
876    }
877    extern "C" fn save_gltf_smpl(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
878        let mut codec = GltfCodec::from_scene(scene, None, true);
879        let now = wasm_timer::Instant::now();
880        codec.to_file(
881            "Meshcapade Avatar",
882            "./saved/output.gltf",
883            GltfOutputType::Standard,
884            GltfCompatibilityMode::Smpl,
885        );
886        codec.to_file(
887            "Meshcapade Avatar",
888            "./saved/output.glb",
889            GltfOutputType::Binary,
890            GltfCompatibilityMode::Smpl,
891        );
892        println!("Smpl mode `.gltf` export took {:?} seconds", now.elapsed());
893    }
894    extern "C" fn save_gltf_unreal(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
895        let mut codec = GltfCodec::from_scene(scene, None, true);
896        let now = wasm_timer::Instant::now();
897        codec.to_file(
898            "Meshcapade Avatar",
899            "./saved/output.gltf",
900            GltfOutputType::Standard,
901            GltfCompatibilityMode::Unreal,
902        );
903        codec.to_file(
904            "Meshcapade Avatar",
905            "./saved/output.glb",
906            GltfOutputType::Binary,
907            GltfCompatibilityMode::Unreal,
908        );
909        println!("Unreal mode `.gltf` export took {:?} seconds", now.elapsed());
910    }
911    extern "C" fn change_gender(_val: bool, widget_name: RString, entity: Entity, scene: &mut Scene) {
912        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(entity) {
913            match widget_name.as_str() {
914                "neutral" => smpl_params.gender = Gender::Neutral,
915                "female" => smpl_params.gender = Gender::Female,
916                "male" => smpl_params.gender = Gender::Male,
917                _ => {}
918            }
919        }
920    }
921    let mut widgets = RVec::new();
922    if let RSome(entity) = selected_entity {
923        if let Ok(smpl_params) = scene.world.get::<&SmplParams>(entity) {
924            let checkbox = Checkbox::new(
925                "enable_pose_corrective",
926                smpl_params.enable_pose_corrective,
927                enable_pose_corrective_toggle,
928            );
929            let is_neutral = smpl_params.gender == Gender::Neutral;
930            let is_female = smpl_params.gender == Gender::Female;
931            let is_male = smpl_params.gender == Gender::Male;
932            let chk_neutral = Checkbox::new("neutral", is_neutral, change_gender);
933            let chk_female = Checkbox::new("female", is_female, change_gender);
934            let chk_male = Checkbox::new("male", is_male, change_gender);
935            let button_save_smpl = Button::new("Save as .smpl", save_smpl);
936            let button_save_gltf_smpl = Button::new("Save as .gltf (SMPL)", save_gltf_smpl);
937            let button_save_gltf_unreal = Button::new("Save as .gltf (UNREAL)", save_gltf_unreal);
938            widgets.push(Widgets::Checkbox(chk_neutral));
939            widgets.push(Widgets::Checkbox(chk_female));
940            widgets.push(Widgets::Checkbox(chk_male));
941            widgets.push(Widgets::Checkbox(checkbox));
942            widgets.push(Widgets::Button(button_save_smpl));
943            widgets.push(Widgets::Button(button_save_gltf_smpl));
944            widgets.push(Widgets::Button(button_save_gltf_unreal));
945        }
946    }
947    GuiWindow {
948        window_name: RString::from("SmplParams"),
949        window_type: GuiWindowType::Sidebar,
950        widgets,
951    }
952}
953#[allow(missing_docs)]
954#[cfg(feature = "with-gui")]
955#[allow(clippy::semicolon_if_nothing_returned)]
956#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
957pub extern "C" fn smpl_betas_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
958    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
959    extern "C" fn beta_slider_change(new_val: f32, widget_name: RString, entity: Entity, scene: &mut Scene) {
960        let beta_idx: usize = widget_name.split('_').last().unwrap().parse().unwrap();
961        if let Ok(mut betas) = scene.world.get::<&mut Betas>(entity) {
962            betas.betas[beta_idx] = new_val;
963        }
964    }
965    let mut widgets = RVec::new();
966    if let RSome(entity) = selected_entity {
967        if let Ok(betas) = scene.world.get::<&Betas>(entity) {
968            for i in 0..betas.betas.len() {
969                let slider = Slider::new(
970                    ("Beta_".to_owned() + &i.to_string()).as_str(),
971                    betas.betas[i],
972                    -5.0,
973                    5.0,
974                    RSome(80.0),
975                    beta_slider_change,
976                    RNone,
977                );
978                widgets.push(Widgets::Slider(slider));
979            }
980        }
981    }
982    GuiWindow {
983        window_name: RString::from("Betas"),
984        window_type: GuiWindowType::Sidebar,
985        widgets,
986    }
987}
988#[allow(missing_docs)]
989#[cfg(feature = "with-gui")]
990#[allow(clippy::semicolon_if_nothing_returned)]
991#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
992pub extern "C" fn smpl_expression_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
993    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
994    extern "C" fn expr_slider_change(new_val: f32, widget_name: RString, entity: Entity, scene: &mut Scene) {
995        let coeff_idx: usize = widget_name.split('_').last().unwrap().parse().unwrap();
996        if let Ok(mut coeffs) = scene.world.get::<&mut Expression>(entity) {
997            coeffs.expr_coeffs[coeff_idx] = new_val;
998        }
999    }
1000    let mut widgets = RVec::new();
1001    if let RSome(entity) = selected_entity {
1002        if let Ok(expression) = scene.world.get::<&Expression>(entity) {
1003            for i in 0..expression.expr_coeffs.len() {
1004                let slider = Slider::new(
1005                    ("Coeff_".to_owned() + &i.to_string()).as_str(),
1006                    expression.expr_coeffs[i],
1007                    -5.0,
1008                    5.0,
1009                    RSome(80.0),
1010                    expr_slider_change,
1011                    RNone,
1012                );
1013                widgets.push(Widgets::Slider(slider));
1014            }
1015        }
1016    }
1017    GuiWindow {
1018        window_name: RString::from("Expression"),
1019        window_type: GuiWindowType::Sidebar,
1020        widgets,
1021    }
1022}
1023#[allow(missing_docs)]
1024#[cfg(feature = "with-gui")]
1025#[allow(clippy::semicolon_if_nothing_returned)]
1026#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1027pub extern "C" fn smpl_anim_scroll_gui(_selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
1028    use gloss_renderer::plugin_manager::gui::{Button, WindowPivot, WindowPosition, WindowPositionType};
1029    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
1030    extern "C" fn scene_anim_slider_change(new_val: f32, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1031        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1032            scene_anim.set_cur_time_as_sec(new_val);
1033            scene_anim.runner.temporary_pause = true;
1034        }
1035    }
1036    extern "C" fn scene_anim_slider_no_change(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1037        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1038            scene_anim.runner.temporary_pause = false;
1039        }
1040    }
1041    extern "C" fn scene_button_play_pause(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1042        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1043            scene_anim.runner.paused = !scene_anim.runner.paused;
1044        }
1045    }
1046    #[allow(clippy::cast_possible_truncation)]
1047    #[allow(clippy::cast_sign_loss)]
1048    extern "C" fn scene_button_next_frame(_widget_name: RString, _entity: Entity, scene: &mut Scene) {
1049        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1050            let nr_frames = scene_anim.num_frames;
1051            let duration = scene_anim.duration();
1052            let dt_between_frames = duration / nr_frames as u32;
1053            scene_anim.advance(dt_between_frames, false);
1054        }
1055    }
1056    extern "C" fn scene_fps_slider_change(new_val: f32, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1057        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1058            let cur_time = scene_anim.get_cur_time();
1059            let prev_fps = scene_anim.config.fps;
1060            let multiplier_duration = prev_fps / new_val;
1061            scene_anim.set_cur_time_as_sec(cur_time.as_secs_f32() * multiplier_duration);
1062            scene_anim.config.fps = new_val;
1063        }
1064    }
1065    extern "C" fn follow_anim(new_val: bool, _widget_name: RString, _entity: Entity, scene: &mut Scene) {
1066        if new_val {
1067            scene.add_resource(Follower::new(FollowParams::default()));
1068        } else {
1069            let _ = scene.remove_resource::<Follower>();
1070        }
1071    }
1072    let mut widgets = RVec::new();
1073    if let Ok(scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
1074        if scene_anim.num_frames != 0 {
1075            let max_duration = scene_anim.duration().as_secs_f32();
1076            let cur_time = scene_anim.get_cur_time().as_secs_f32();
1077            let slider_anim = Slider::new(
1078                "AnimTime",
1079                cur_time,
1080                0.0,
1081                max_duration,
1082                RSome(400.0),
1083                scene_anim_slider_change,
1084                RSome(scene_anim_slider_no_change as extern "C" fn(RString, Entity, &mut Scene)),
1085            );
1086            let button_play_pause = Button::new("play/pause", scene_button_play_pause);
1087            let button_next_frame = Button::new("next_frame", scene_button_next_frame);
1088            let slider_fps = Slider::new("FPS", scene_anim.config.fps, 1.0, 120.0, RSome(100.0), scene_fps_slider_change, RNone);
1089            let chk_follow_anim = Checkbox::new("follow", scene.has_resource::<Follower>(), follow_anim);
1090            widgets.push(Widgets::Slider(slider_anim));
1091            widgets.push(Widgets::Horizontal(RVec::from(vec![
1092                Widgets::Button(button_play_pause),
1093                Widgets::Button(button_next_frame),
1094                Widgets::Slider(slider_fps),
1095            ])));
1096            widgets.push(Widgets::Horizontal(RVec::from(vec![Widgets::Checkbox(chk_follow_anim)])));
1097        }
1098    }
1099    GuiWindow {
1100        window_name: RString::from("Scene"),
1101        window_type: GuiWindowType::FloatWindow(WindowPivot::CenterBottom, WindowPosition([0.5, 1.0]), WindowPositionType::Fixed),
1102        widgets,
1103    }
1104}
1105#[allow(missing_docs)]
1106#[cfg(feature = "with-gui")]
1107#[allow(clippy::semicolon_if_nothing_returned)]
1108#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1109pub extern "C" fn smpl_hand_pose_gui(selected_entity: ROption<Entity>, scene: &mut Scene) -> GuiWindow {
1110    use gloss_renderer::plugin_manager::gui::SelectableList;
1111    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
1112    use log::warn;
1113    use smpl_core::common::pose_hands::HandType;
1114    extern "C" fn set_hand_pose_type(widget_name: RString, entity: Entity, scene: &mut Scene) {
1115        let hand_type = match widget_name.to_string().as_str() {
1116            "Flat" => Some(HandType::Flat),
1117            "Relaxed" => Some(HandType::Relaxed),
1118            "Curled" => Some(HandType::Curled),
1119            "Fist" => Some(HandType::Fist),
1120            _ => {
1121                warn!("HandType not known");
1122                None
1123            }
1124        };
1125        let mut command_buffer = CommandBuffer::new();
1126        if let Some(hand_type) = hand_type {
1127            info!("setting to {hand_type:?}");
1128            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(entity) {
1129                info!("we already have a pose mask");
1130                pose_mask.set_overwrite_hands(hand_type);
1131            } else {
1132                info!("inserting a new pose mask");
1133                let pose_mask = PoseOverride::allow_all().overwrite_hands(hand_type).build();
1134                command_buffer.insert_one(entity, pose_mask);
1135            }
1136        } else {
1137            info!("removing overwrite");
1138            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(entity) {
1139                info!("removing overwrite and we have posemask");
1140                if pose_mask.get_overwrite_hands_type().is_some() {
1141                    pose_mask.remove_overwrite_hands();
1142                }
1143            }
1144        }
1145        command_buffer.run_on(&mut scene.world);
1146    }
1147    let mut widgets = RVec::new();
1148    if let RSome(entity) = selected_entity {
1149        if let Ok(pose_mask) = scene.world.get::<&PoseOverride>(entity) {
1150            let hand_type_overwrite = pose_mask.get_overwrite_hands_type();
1151            let mut selectable_vec = RVec::new();
1152            selectable_vec.push(Selectable::new("None", hand_type_overwrite.is_none(), set_hand_pose_type));
1153            selectable_vec.push(Selectable::new("Flat", hand_type_overwrite == Some(HandType::Flat), set_hand_pose_type));
1154            selectable_vec.push(Selectable::new(
1155                "Relaxed",
1156                hand_type_overwrite == Some(HandType::Relaxed),
1157                set_hand_pose_type,
1158            ));
1159            selectable_vec.push(Selectable::new(
1160                "Curled",
1161                hand_type_overwrite == Some(HandType::Curled),
1162                set_hand_pose_type,
1163            ));
1164            selectable_vec.push(Selectable::new("Fist", hand_type_overwrite == Some(HandType::Fist), set_hand_pose_type));
1165            let selectable_list = SelectableList::new(selectable_vec, true);
1166            widgets.push(Widgets::SelectableList(selectable_list));
1167        }
1168    }
1169    GuiWindow {
1170        window_name: RString::from("HandOverwrite"),
1171        window_type: GuiWindowType::Sidebar,
1172        widgets,
1173    }
1174}
1175#[allow(missing_docs)]
1176#[cfg(feature = "with-gui")]
1177#[allow(clippy::cast_precision_loss)]
1178#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
1179pub extern "C" fn smpl_event_dropfile(scene: &mut Scene, _runner: &mut RunnerState, event: &Event) -> bool {
1180    use crate::scene::McsCodecGloss;
1181    use log::warn;
1182    use smpl_core::codec::{codec::SmplCodec, scene::McsCodec};
1183    use std::path::PathBuf;
1184    let mut handled = false;
1185    match event {
1186        Event::DroppedFile(path) => {
1187            let path_buf = PathBuf::from(path.to_string());
1188            let filetype = match path_buf.extension() {
1189                Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
1190                None => FileType::Unknown,
1191            };
1192            if scene.has_resource::<SceneAnimation>() {
1193                scene.remove_resource::<SceneAnimation>().unwrap();
1194            }
1195            match filetype {
1196                FileType::Smpl => {
1197                    info!("handling dropped smpl file {}", path);
1198                    let codec = SmplCodec::from_file(path);
1199                    let mut builder = codec.to_entity_builder();
1200                    if !builder.has::<Betas>() {
1201                        warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
1202                        builder.add(Betas::default());
1203                    }
1204                    let gloss_interop = GlossInterop::default();
1205                    let name = scene.get_unused_name();
1206                    scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
1207                    handled = true;
1208                }
1209                FileType::Mcs => {
1210                    info!("handling dropped mcs file {}", path);
1211                    let mut codec = McsCodec::from_file(path);
1212                    let builders = codec.to_entity_builders();
1213                    for mut builder in builders {
1214                        if !builder.has::<Betas>() {
1215                            warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
1216                            builder.add(Betas::default());
1217                        }
1218                        let gloss_interop = GlossInterop::default();
1219                        let name = scene.get_unused_name();
1220                        scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
1221                        handled = true;
1222                    }
1223                    let smpl_scene = SceneAnimation::new_with_fps(codec.num_frames, codec.frame_rate);
1224                    scene.add_resource(smpl_scene);
1225                }
1226                _ => {
1227                    info!("No known filetype {}", path);
1228                }
1229            }
1230        }
1231    }
1232    handled
1233}