smpl_gloss_integration/
systems.rs

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