use crate::codec::SmplCodecGloss;
use crate::components::{Follow, FollowParams, Follower, FollowerType, GlossInterop};
use crate::conversions::{update_entity_on_backend, update_entity_on_backend_wgpu};
use crate::gltf::GltfInteropOptions;
use crate::scene::SceneAnimation;
use burn::backend::ndarray::NdArrayDevice;
use burn::backend::NdArray;
use burn::{
    prelude::*,
    tensor::{Float, Int, Tensor},
};
use core::f32;
use gloss_burn_multibackend::backend::MultiBackend;
use gloss_burn_multibackend::backend::MultiDevice;
use gloss_geometry::geom::{self, PerVertexNormalsWeightingType};
use gloss_hecs::Entity;
use gloss_hecs::{Changed, CommandBuffer};
use gloss_renderer::components::{Colors, Faces, Normals, Tangents, UVs};
use gloss_renderer::plugin_manager::gui::{GuiWindow, GuiWindowType};
use gloss_renderer::{
    components::{ConfigChanges, ModelMatrix, PosLookat, Renderable, Verts},
    plugin_manager::{
        gui::{Checkbox, Selectable, Slider, Widgets},
        Event, RunnerState,
    },
    scene::Scene,
};
use gloss_utils::abi_stable_aliases::std_types::{RNone, ROption, RString, RVec};
use gloss_utils::{
    bshare::{ToBurn, ToNalgebraFloat, ToNalgebraInt, ToNdArray},
    nshare::ToNalgebra,
    tensor::{DynamicMatrixOps, DynamicTensorFloat2D, DynamicTensorInt2D},
};
use log::{info, warn};
use nalgebra::{self as na};
use smpl_core::codec::gltf::GltfExportOptions;
use smpl_core::codec::scene::CameraTrack;
use smpl_core::common::animation::AnimationConfig;
use smpl_core::common::smpl_model::{SmplCache, SmplModel};
use smpl_core::common::transform_sequence::TransformSequence;
use smpl_core::common::types::{FaceType, GltfOutputType, UpAxis};
use smpl_core::common::vertex_offsets::VertexOffsets;
use smpl_core::common::{
    animation::Animation,
    betas::Betas,
    expression::{Expression, ExpressionOffsets},
    outputs::SmplOutputPoseT,
    outputs::SmplOutputPosed,
};
use smpl_core::common::{pose::Pose, pose_corrective::PoseCorrective, pose_override::PoseOverride, smpl_params::SmplParams};
use smpl_core::conversions::pose_remap::PoseRemap;
use smpl_core::AppBackend;
use smpl_utils::io::FileType;
/// Check all entities with ``SmplParams`` and lazy load the smpl model if
/// needed do it in two stages, first checking if we need to acually load
/// anything and in the second stage we actually load stuff, this is in order to
/// avoid accing the ``SmplModel`` mutable and then triggering all the other
/// systems to run
#[cfg(not(target_arch = "wasm32"))]
pub extern "C" fn smpl_lazy_load_model(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let mut needs_loading = false;
        let mut query_state = scene.world.query::<&SmplParams>();
        for (_entity, smpl_params) in query_state.iter() {
            let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
            if !smpl_models.has_model(smpl_params.smpl_type, smpl_params.gender)
                && smpl_models.has_lazy_loading(smpl_params.smpl_type, smpl_params.gender)
            {
                needs_loading = true;
            }
        }
        if needs_loading {
            let mut query_state = scene.world.query::<&SmplParams>();
            for (_entity, smpl_params) in query_state.iter() {
                let mut smpl_models = scene.get_resource::<&mut SmplCache>().unwrap();
                if !smpl_models.has_model(smpl_params.smpl_type, smpl_params.gender)
                    && smpl_models.has_lazy_loading(smpl_params.smpl_type, smpl_params.gender)
                {
                    if let Some(path) = smpl_models.get_lazy_loading(smpl_params.smpl_type, smpl_params.gender).as_ref() {
                        smpl_models.add_model_from_type(smpl_params.smpl_type, path, smpl_params.gender, 300, 100);
                    }
                }
            }
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to add a ``SceneAnimation`` Resource in case it doesn't already exist
pub extern "C" fn smpl_auto_add_scene(scene: &mut Scene, _runner: &mut RunnerState) {
    if !scene.has_resource::<SceneAnimation>() {
        let mut selected_num_frames = 0;
        let mut selected_fps = f32::MAX;
        let mut anim_config = AnimationConfig::default();
        let num_ents: usize;
        {
            let mut query_state = scene.world.query::<&Animation>().with::<&Renderable>();
            let num_smpl_ents = query_state.iter().len();
            for (_, smpl_anim) in query_state.iter() {
                let last_frame_idx = smpl_anim.num_animation_frames() + smpl_anim.start_offset;
                selected_num_frames = selected_num_frames.max(last_frame_idx);
                selected_fps = selected_fps.min(smpl_anim.config.fps);
                anim_config = smpl_anim.config.clone();
            }
            let mut camera_query_state = scene.world.query::<&CameraTrack>();
            let num_cam_ents = camera_query_state.iter().len();
            for (_, camera_track) in camera_query_state.iter() {
                if let Some(translations) = camera_track.per_frame_translations.as_ref() {
                    let last_frame_idx = translations.nrows();
                    selected_num_frames = selected_num_frames.max(last_frame_idx);
                    if num_smpl_ents == 0 {
                        selected_fps = 60.0;
                    }
                }
            }
            num_ents = num_smpl_ents + num_cam_ents;
        }
        let scene_anim = match num_ents {
            0 => None,
            1 => Some(SceneAnimation::new_with_config(selected_num_frames, anim_config)),
            _ => Some(SceneAnimation::new_with_fps(selected_num_frames, selected_fps)),
        };
        if let Some(scene_anim) = scene_anim {
            scene.add_resource(scene_anim);
        }
    }
}
/// System to add a ``SmplInterval`` to entities in case it doesn't already exist
pub extern "C" fn smpl_auto_add_follow(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let Ok(follower) = scene.get_resource::<&Follower>() else {
            return;
        };
        let follow_all = follower.params.follow_all;
        if !follow_all {
            return;
        }
        let mut query_state = scene.world.query::<&GlossInterop>().without::<&Follow>();
        for (entity, _) in query_state.iter() {
            command_buffer.insert_one(entity, Follow);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to Advance Animation timer
#[allow(clippy::cast_precision_loss)]
pub extern "C" fn smpl_advance_anim(scene: &mut Scene, runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        if !scene.has_resource::<SceneAnimation>() {
            return;
        }
        let mut entities_with_anim = Vec::new();
        {
            let mut query_state = scene.world.query::<(&Animation, Changed<Animation>)>().with::<&SmplParams>();
            for (entity, (smpl_anim, changed_anim)) in query_state.iter() {
                if (!smpl_anim.runner.paused && !smpl_anim.runner.temporary_pause) || changed_anim {
                    entities_with_anim.push(entity);
                }
            }
        }
        let mut entities_within_interval = Vec::new();
        let mut entities_outside_interval = Vec::new();
        {
            let mut scene_anim = scene.get_resource::<&mut SceneAnimation>().unwrap();
            let current_global_time = scene_anim.runner.anim_current_time.as_secs_f32();
            for entity in entities_with_anim.iter() {
                let mut smpl_anim = scene.get_comp::<&mut Animation>(entity).unwrap();
                let anim_paused = smpl_anim.runner.paused || smpl_anim.runner.temporary_pause;
                let global_fps = scene_anim.config.fps;
                smpl_anim.config.fps = global_fps;
                let start_offset = smpl_anim.start_offset;
                let is_within_interval = current_global_time >= (smpl_anim.start_offset as f32 / scene_anim.config.fps)
                    && current_global_time <= ((smpl_anim.start_offset + smpl_anim.num_animation_frames()) as f32 / scene_anim.config.fps);
                if is_within_interval {
                    if !anim_paused {
                        smpl_anim.set_cur_time_as_sec(current_global_time - (start_offset as f32 / global_fps));
                    }
                    entities_within_interval.push(*entity);
                } else {
                    entities_outside_interval.push(*entity);
                    continue;
                }
                if !scene_anim.runner.paused && !scene_anim.runner.temporary_pause && !anim_paused {
                    let is_added = smpl_anim.is_added();
                    smpl_anim.advance(runner.dt(), runner.is_first_time() || is_added);
                }
                let anim_frame: Pose = smpl_anim.get_current_pose();
                command_buffer.insert_one(*entity, anim_frame);
                if let Some(expression) = smpl_anim.get_current_expression() {
                    command_buffer.insert_one(*entity, expression);
                }
            }
            if !scene_anim.runner.paused && !scene_anim.runner.temporary_pause && scene_anim.num_frames != 0 {
                let is_added = scene_anim.is_added();
                scene_anim.advance(runner.dt(), runner.is_first_time() || is_added);
            }
        }
        for entity in entities_within_interval {
            if !scene.world.has::<Renderable>(entity).unwrap() {
                scene.world.insert_one(entity, Renderable).unwrap();
            }
        }
        for entity in entities_outside_interval {
            if scene.world.has::<Renderable>(entity).unwrap() {
                scene.world.remove_one::<Renderable>(entity).unwrap();
            }
        }
        runner.request_redraw();
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to compute vertices if Betas has changed.
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_betas_to_verts(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let changed_models = smpl_models.is_changed();
        let mut query_state = scene.world.query::<(&SmplParams, &Betas, Changed<Betas>, Changed<SmplParams>)>();
        for (entity, (smpl_params, smpl_betas, changed_betas, changed_smpl_params)) in query_state.iter() {
            if !changed_betas && !changed_smpl_params && !changed_models {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            let v_burn_merged = smpl_model.betas2verts(smpl_betas);
            let joints_t_pose = smpl_model.verts2joints(v_burn_merged.clone());
            let smpl_output = SmplOutputPoseT {
                verts: v_burn_merged.clone(),
                verts_with_expression: v_burn_merged.clone(),
                verts_without_expression: v_burn_merged,
                joints: joints_t_pose,
            };
            command_buffer.insert_one(entity, smpl_output);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to compute Expression offsets if Expression or ``SmplParams`` have
/// changed.
#[allow(clippy::similar_names)]
#[allow(unused_mut)]
pub extern "C" fn smpl_expression_offsets(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();

        let mut query_state = scene
            .world
            .query::<(&SmplParams, &Expression, Changed<Expression>, Changed<SmplParams>)>();
        for (entity, (smpl_params, expression, changed_expression, changed_smpl_params)) in query_state.iter() {
            if !changed_expression && !changed_smpl_params && !smpl_models.is_changed() {
                continue;
            }
            let mut face_model = smpl_models.get_face_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();

            let verts_offsets_merged = face_model.expression2offsets(expression);
            let expr_offsets = ExpressionOffsets {
                offsets: verts_offsets_merged,
            };
            command_buffer.insert_one(entity, expr_offsets);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to apply the expression offsets.
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_expression_apply(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let mut query_state = scene.world.query::<(
            &SmplParams,
            &mut SmplOutputPoseT,
            &ExpressionOffsets,
            Changed<SmplOutputPoseT>,
            Changed<ExpressionOffsets>,
            Changed<SmplParams>,
        )>();
        for (entity, (smpl_params, mut smpl_t_output, expression_offsets, changed_smpl_t, changed_expression, changed_params)) in query_state.iter() {
            if !changed_smpl_t && !changed_expression && !changed_params {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            smpl_t_output.verts = smpl_t_output.verts_without_expression.clone() + expression_offsets.offsets.clone();
            smpl_t_output.joints = smpl_model.verts2joints(smpl_t_output.verts.clone());
            command_buffer.insert_one(entity, smpl_t_output.clone());
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to apply the free vertex offsets.
/// This internally uses the generic variant of the function suffixed with
/// ``_on_backend``
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_vertex_offset_apply(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let mut query_state = scene.world.query::<(
            &SmplParams,
            &mut SmplOutputPoseT,
            &VertexOffsets,
            Changed<SmplOutputPoseT>,
            Changed<VertexOffsets>,
            Changed<SmplParams>,
        )>();
        for (entity, (smpl_params, mut smpl_t_output, vertex_offsets, changed_smpl_t, changed_vertex_offsets, changed_params)) in query_state.iter() {
            if !changed_smpl_t && !changed_vertex_offsets && !changed_params {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            smpl_t_output.verts =
                smpl_t_output.verts_with_expression.clone() + vertex_offsets.strength * vertex_offsets.offsets.clone().to_burn(&smpl_model.device());
            smpl_t_output.joints = smpl_model.verts2joints(smpl_t_output.verts.clone());
            command_buffer.insert_one(entity, smpl_t_output.clone());
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System to remap the ``SmplType`` of a pose to the ``SmplType`` found in
/// ``SmplParams``
pub extern "C" fn smpl_pose_remap(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let mut query_state = scene.world.query::<(&Pose, &SmplParams, Changed<Pose>)>();
        for (entity, (smpl_pose, smpl_params, changed_pose)) in query_state.iter() {
            if !changed_pose {
                continue;
            }
            let pose_remap = PoseRemap::new(smpl_pose.smpl_type, smpl_params.smpl_type);
            let new_pose = pose_remap.remap(smpl_pose);
            command_buffer.insert_one(entity, new_pose);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System for handling pose overrides
pub extern "C" fn smpl_mask_pose(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let mut query_state = scene
            .world
            .query::<(&mut Pose, &mut PoseOverride, Changed<Pose>, Changed<PoseOverride>)>()
            .with::<&SmplParams>();
        for (_entity, (mut smpl_pose, mut pose_mask, changed_pose, changed_pose_mask)) in query_state.iter() {
            if !changed_pose && !changed_pose_mask {
                continue;
            }
            smpl_pose.apply_mask(&mut pose_mask);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// Smpl bodies have to be assigned some pose, so if we have no Pose and no
/// Animation we set a dummy default pose
pub extern "C" fn smpl_make_dummy_pose(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let mut query_state = scene.world.query::<&SmplParams>().without::<&Pose>().without::<&Animation>();
        for (entity, smpl_params) in query_state.iter() {
            let pose = Pose::new_empty(UpAxis::Y, smpl_params.smpl_type);
            command_buffer.insert_one(entity, pose);
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System for computing and applying pose correctives given a pose
#[allow(clippy::similar_names)]
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_compute_pose_correctives(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let smpl_models_changed = smpl_models.is_changed();
        let mut query_state = scene.world.query::<(&SmplParams, &mut Pose, Changed<Pose>, Changed<SmplParams>)>();
        for (entity, (smpl_params, smpl_pose, changed_pose, changed_smpl_params)) in query_state.iter() {
            if (!changed_pose && !changed_smpl_params && !smpl_models_changed) || !smpl_params.enable_pose_corrective {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            let verts_offset = smpl_model.compute_pose_correctives(&smpl_pose);
            command_buffer.insert_one(entity, PoseCorrective { verts_offset });
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System for applying a pose to the given template
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_apply_pose(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let mut query_state = scene.world.query::<(
            &SmplParams,
            &mut SmplOutputPoseT,
            &mut Pose,
            Option<&PoseCorrective>,
            Changed<Pose>,
            Changed<SmplOutputPoseT>,
            Changed<SmplParams>,
        )>();
        for (entity, (smpl_params, smpl_t_output, smpl_pose, pose_corrective, changed_pose, changed_t_output, changed_smpl_params)) in
            query_state.iter()
        {
            if !changed_pose && !changed_t_output && !changed_smpl_params {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            let lbs_weights_merged = smpl_model.lbs_weights();
            let mut verts_burn_merged = smpl_t_output.verts.clone();
            let joints_t_pose = &smpl_t_output.joints;
            let new_pose = smpl_pose.clone();
            if let Some(pose_corrective) = pose_corrective {
                if smpl_params.enable_pose_corrective {
                    let v_offset_merged = &pose_corrective.verts_offset;
                    verts_burn_merged = verts_burn_merged.add(v_offset_merged.clone());
                }
            }
            let (verts_posed_nd, joints_posed) = smpl_model.apply_pose(&verts_burn_merged, joints_t_pose, &lbs_weights_merged, &new_pose);
            command_buffer.insert_one(
                entity,
                SmplOutputPosed {
                    verts: verts_posed_nd,
                    joints: joints_posed,
                },
            );
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// Convert a float tensor from Router to `NdArray`.
pub fn to_ndarray<const D: usize>(t: &Tensor<AppBackend, D>) -> Tensor<NdArray, D> {
    let data = t.to_data().convert::<<NdArray as Backend>::FloatElem>();
    Tensor::<NdArray, D>::from_data(data, &NdArrayDevice::default())
}
/// Same idea for int tensors, if you ever need it.
pub fn to_ndarray_int<const D: usize>(t: &Tensor<AppBackend, D, Int>) -> Tensor<NdArray, D, Int> {
    let data = t.to_data().convert::<<NdArray as Backend>::IntElem>();
    Tensor::<NdArray, D, Int>::from_data(data, &NdArrayDevice::default())
}
/// System to convert ``SmplOutput`` components to gloss components (``Verts``,
/// ``Faces``, ``Normals``, etc.) for the ``upload_pass``
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_to_gloss_mesh(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let smpl_models = scene.get_resource::<&SmplCache>().unwrap();
        let gpu = scene.get_resource::<&easy_wgpu::gpu::Gpu>();
        let mut query_state = scene.world.query::<(
            &SmplParams,
            &SmplOutputPosed,
            &GlossInterop,
            Changed<SmplOutputPosed>,
            Changed<GlossInterop>,
        )>();
        for (entity, (smpl_params, smpl_output, gloss_interop, changed_output, changed_gloss_interop)) in query_state.iter() {
            if !changed_output && !changed_gloss_interop && !smpl_models.is_changed() {
                continue;
            }
            let smpl_model = smpl_models.get_model_ref(smpl_params.smpl_type, smpl_params.gender).unwrap();
            let (verts, uv, normals, tangents, faces) = compute_common_mesh_data(smpl_model, &smpl_output.verts, gloss_interop.with_uv);
            let device = verts.device();
            if let MultiDevice::Wgpu(_) = device {
                update_entity_on_backend_wgpu(
                    entity,
                    scene,
                    gpu.as_ref().unwrap(),
                    &mut command_buffer,
                    gloss_interop.with_uv,
                    &verts,
                    &normals,
                    tangents,
                    &uv,
                    &faces,
                );
            } else {
                let verts = to_ndarray(&verts);
                let normals = to_ndarray(&normals);
                let tangents = tangents.map(|t: Tensor<AppBackend, 2>| to_ndarray(&t));
                let uv = to_ndarray(&uv);
                let faces = to_ndarray_int(&faces);
                update_entity_on_backend(
                    entity,
                    scene,
                    &mut command_buffer,
                    gloss_interop.with_uv,
                    &DynamicTensorFloat2D::NdArray(verts),
                    &DynamicTensorFloat2D::NdArray(normals),
                    tangents.map(DynamicTensorFloat2D::NdArray),
                    DynamicTensorFloat2D::NdArray(uv),
                    DynamicTensorInt2D::NdArray(faces),
                    smpl_model,
                );
            }
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// Type alias to club all mesh data together
type MeshDataResult<B> = (
    Tensor<B, 2, Float>,
    Tensor<B, 2, Float>,
    Tensor<B, 2, Float>,
    Option<Tensor<B, 2, Float>>,
    Tensor<B, 2, Int>,
);
/// Function to compute data like Normals and Tangents on a generic Burn
/// Backend. We currently support - ``Candle``, ``NdArray``, and ``Wgpu``
fn compute_common_mesh_data(
    smpl_model: &dyn SmplModel<MultiBackend>,
    verts_burn: &Tensor<MultiBackend, 2, Float>,
    with_uv: bool,
) -> MeshDataResult<MultiBackend> {
    let device: gloss_burn_multibackend::backend::MultiDevice = verts_burn.device();
    let mapping = smpl_model.idx_split_2_merged();
    let verts_final_burn = if with_uv {
        verts_burn.clone().select(0, mapping.clone())
    } else {
        verts_burn.clone()
    };
    let uv_burn = smpl_model.uv().clone();
    let faces_burn = smpl_model.faces();
    let normals_merged_burn = match device {
        MultiDevice::Candle(_) => geom::compute_per_vertex_normals(
            &verts_burn.to_nalgebra(),
            &faces_burn.to_nalgebra(),
            &PerVertexNormalsWeightingType::Uniform,
        )
        .to_burn(&verts_burn.device()),
        MultiDevice::Wgpu(_) => {
            gloss_geometry::cubecl::compute_per_vertex_normals_cubecl(verts_burn.clone(), faces_burn.clone(), &smpl_model.vertex_face_csr().unwrap())
        }
        _ => geom::compute_per_vertex_normals_burn(verts_burn, faces_burn, &PerVertexNormalsWeightingType::Uniform),
    };
    let normals_final_burn = if with_uv {
        normals_merged_burn.clone().select(0, mapping.clone())
    } else {
        normals_merged_burn
    };
    let tangents_burn = if with_uv {
        match device {
            MultiDevice::Candle(_) => Some(
                geom::compute_tangents(
                    &verts_final_burn.to_nalgebra(),
                    &smpl_model.faces_uv().to_nalgebra(),
                    &normals_final_burn.to_nalgebra(),
                    &smpl_model.uv().to_nalgebra(),
                )
                .to_burn(&verts_burn.device()),
            ),
            MultiDevice::Wgpu(_) => Some(gloss_geometry::cubecl::compute_tangents_cubecl(
                verts_final_burn.clone(),
                smpl_model.faces_uv().clone(),
                normals_final_burn.clone(),
                smpl_model.uv().clone(),
                &smpl_model.vertex_face_uv_csr().unwrap(),
            )),
            _ => Some(geom::compute_tangents_burn(
                &verts_final_burn,
                smpl_model.faces_uv(),
                &normals_final_burn,
                smpl_model.uv(),
            )),
        }
    } else {
        None
    };
    let faces_burn = if with_uv { smpl_model.faces_uv() } else { faces_burn };
    (verts_final_burn, uv_burn, normals_final_burn, tangents_burn, faces_burn.clone())
}
/// System to align a mesh at every frame to the floor
#[allow(clippy::too_many_lines)]
pub extern "C" fn smpl_align_vertical(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let mut query_state = scene
            .world
            .query::<(&Verts, &mut ModelMatrix, Changed<SmplOutputPoseT>)>()
            .with::<&SmplParams>();
        for (_entity, (verts, mut model_matrix, changed_t_pose)) in query_state.iter() {
            if changed_t_pose {
                let verts_world = geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
                let min_y = verts_world.column(1).min();
                model_matrix.0.append_translation_mut(&na::Translation3::<f32>::new(0.0, -min_y, 0.0));
            }
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// System for follower computations
#[allow(clippy::cast_precision_loss)]
pub extern "C" fn smpl_follow_anim(scene: &mut Scene, runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        let Ok(mut follow) = scene.get_resource::<&mut Follower>() else {
            return;
        };
        let mut query_state = scene.world.query::<&Follow>().with::<&Renderable>();
        let mut goal = na::Point3::new(0.0, 0.0, 0.0);
        let mut num_ents = 0;
        for (entity, _) in query_state.iter() {
            let model_matrix = if let Ok(mm) = scene.world.get::<&mut ModelMatrix>(entity) {
                mm.0
            } else {
                ModelMatrix::default().0
            };
            let Some(ent_goal) = compute_follower_goal(scene, entity, model_matrix) else {
                continue;
            };
            goal.coords += ent_goal.coords;
            num_ents += 1;
        }
        if num_ents > 0 {
            goal.coords /= num_ents as f32;
        }
        #[allow(clippy::match_wildcard_for_single_variants)]
        match follow.params.follower_type {
            FollowerType::Cam | FollowerType::CamAndLights => {
                let cam = scene.get_current_cam().unwrap();
                if !cam.is_initialized(scene) {
                    return;
                }
                if let Ok(mut poslookat) = scene.world.get::<&mut PosLookat>(cam.entity) {
                    follow.update(&goal, &poslookat.lookat, runner.dt().as_secs_f32());
                    let point_lookat = follow.get_point_follow("cam");
                    let diff = (poslookat.lookat - point_lookat).norm();
                    if diff > 1e-7 {
                        runner.request_redraw();
                        poslookat.shift_lookat(point_lookat);
                    }
                }
            }
            _ => {}
        }
        #[allow(clippy::match_wildcard_for_single_variants)]
        match follow.params.follower_type {
            FollowerType::Lights | FollowerType::CamAndLights => {
                let lights = scene.get_lights(false);
                for light in lights.iter() {
                    if let Ok(mut poslookat) = scene.world.get::<&mut PosLookat>(*light) {
                        let point_lookat = goal;
                        let diff = (poslookat.lookat - point_lookat).norm();
                        if diff > 1e-7 {
                            runner.request_redraw();
                            poslookat.shift_lookat(point_lookat);
                        }
                    }
                }
                let point_dist_fade_center = goal;
                command_buffer.insert_one(
                    scene.get_entity_resource(),
                    ConfigChanges {
                        new_distance_fade_center: point_dist_fade_center,
                    },
                );
            }
            _ => {}
        }
    }
    command_buffer.run_on(&mut scene.world);
}
fn compute_follower_goal(scene: &Scene, entity: Entity, model_matrix: na::SimilarityMatrix3<f32>) -> Option<na::Point3<f32>> {
    if scene.world.has::<SmplOutputPosed>(entity).unwrap() {
        let output_posed = scene.world.get::<&SmplOutputPosed>(entity).unwrap();
        let joints_ndarray = output_posed.joints.to_ndarray();
        let pose_trans = joints_ndarray.row(0).into_nalgebra();
        let point = pose_trans.fixed_rows::<3>(0).clone_owned();
        let mut point = na::Point3::<f32> { coords: point };
        point = model_matrix * point;
        Some(point)
    } else if scene.world.has::<Verts>(entity).unwrap() && scene.world.has::<ModelMatrix>(entity).unwrap() {
        let verts = scene.world.get::<&Verts>(entity).unwrap();
        let model_matrix = scene.world.get::<&ModelMatrix>(entity).unwrap();
        Some(geom::get_centroid(&verts.0.to_dmatrix(), Some(model_matrix.0)))
    } else {
        None
    }
}
/// System to apply global transforms to props in the scene
#[allow(clippy::too_many_lines)]
pub extern "C" fn prop_transform_sequence(scene: &mut Scene, _runner: &mut RunnerState) {
    let mut command_buffer = CommandBuffer::new();
    {
        if let Ok(scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            let current_time = scene_anim.runner.anim_current_time.as_secs_f32();
            let mut query_state = scene.world.query::<&TransformSequence>();
            for (entity, transform_sequence) in query_state.iter() {
                let mm = transform_sequence.get_transform_at_time(current_time, scene_anim.config.fps);
                command_buffer.insert_one(entity, mm);
            }
        }
    }
    command_buffer.run_on(&mut scene.world);
}
/// Hides the floor if the camera is below it
pub extern "C" fn hide_floor_when_viewed_from_below(scene: &mut Scene, _runner: &mut RunnerState) {
    let camera = scene.get_current_cam().unwrap();
    let pos = {
        let Ok(pos_lookat) = scene.world.get::<&PosLookat>(camera.entity) else {
            warn!("rs: hide_floor_when_viewed_from_below: No PosLookat yet, camera is not initialized. Auto adding default");
            return;
        };
        pos_lookat.position
    };
    if let Some(floor) = scene.get_floor() {
        let min_y = {
            let Ok(verts) = scene.world.get::<&Verts>(floor.entity) else {
                warn!("rs: hide_floor_when_viewed_from_below: No Verts on floor");
                return;
            };
            let Ok(model_matrix) = scene.world.get::<&ModelMatrix>(floor.entity) else {
                warn!("rs: hide_floor_when_viewed_from_below: No ModelMatrix on floor");
                return;
            };
            let verts_world = geom::transform_verts(&verts.0.to_dmatrix(), &model_matrix.0);
            verts_world.column(1).min()
        };
        if pos.coords.y < min_y {
            if scene.world.has::<Renderable>(floor.entity).unwrap() {
                scene.world.remove_one::<Renderable>(floor.entity).unwrap();
            }
        } else if !scene.world.has::<Renderable>(floor.entity).unwrap() {
            scene.world.insert_one(floor.entity, Renderable).unwrap();
        }
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::semicolon_if_nothing_returned)]
#[allow(clippy::too_many_lines)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_params_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use crate::gltf::GltfCodecGloss;
    use crate::scene::McsCodecGloss;
    use gloss_renderer::plugin_manager::gui::Button;
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    use smpl_core::{
        codec::scene::McsCodec,
        codec::{codec::SmplCodec, gltf::GltfCodec},
        common::types::{Gender, GltfCompatibilityMode},
    };
    extern "C" fn enable_pose_corrective_toggle(new_val: bool, _widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(*entity) {
            smpl_params.enable_pose_corrective = new_val;
        }
    }
    extern "C" fn save_smpl(_widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        let codec = SmplCodec::from_entity(entity, scene, None);
        codec.to_file("./saved/output.smpl");
    }
    extern "C" fn save_mcs(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        let codec = McsCodec::from_scene(scene);
        codec.to_file("./saved/output.mcs");
    }
    extern "C" fn save_gltf_smpl(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        let mut codec = GltfCodec::from_scene(scene, &GltfInteropOptions::default());
        let now = wasm_timer::Instant::now();
        codec.to_file(
            "Meshcapade Avatar",
            "./saved/output.gltf",
            &GltfExportOptions {
                out_type: GltfOutputType::Standard,
                ..Default::default()
            },
        );
        codec.to_file("Meshcapade Avatar", "./saved/output.glb", &GltfExportOptions::default());
        info!("Time taken for Smpl mode `.gltf` export: {:?}", now.elapsed());
    }
    extern "C" fn save_gltf_unreal(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        let mut codec = GltfCodec::from_scene(scene, &GltfInteropOptions::default());
        let now = wasm_timer::Instant::now();
        codec.to_file(
            "Meshcapade Avatar",
            "./saved/output.gltf",
            &GltfExportOptions {
                out_type: GltfOutputType::Standard,
                compatibility_mode: GltfCompatibilityMode::Unreal,
                face_type: FaceType::ARKit,
            },
        );
        codec.to_file(
            "Meshcapade Avatar",
            "./saved/output.glb",
            &GltfExportOptions {
                out_type: GltfOutputType::Binary,
                compatibility_mode: GltfCompatibilityMode::Unreal,
                face_type: FaceType::ARKit,
            },
        );
        info!("Time taken for Unreal mode `.gltf` export: {:?}", now.elapsed());
    }
    extern "C" fn change_gender(_val: bool, widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        if let Ok(mut smpl_params) = scene.world.get::<&mut SmplParams>(*entity) {
            match widget_name.as_str() {
                "neutral" => smpl_params.gender = Gender::Neutral,
                "female" => smpl_params.gender = Gender::Female,
                "male" => smpl_params.gender = Gender::Male,
                _ => {}
            }
        }
    }
    let mut widgets = RVec::new();
    if let RSome(entity) = selected_entity {
        if let Ok(smpl_params) = scene.world.get::<&SmplParams>(*entity) {
            let checkbox = Checkbox::new(
                "enable_pose_corrective",
                smpl_params.enable_pose_corrective,
                enable_pose_corrective_toggle,
            );
            let is_neutral = smpl_params.gender == Gender::Neutral;
            let is_female = smpl_params.gender == Gender::Female;
            let is_male = smpl_params.gender == Gender::Male;
            let chk_neutral = Checkbox::new("neutral", is_neutral, change_gender);
            let chk_female = Checkbox::new("female", is_female, change_gender);
            let chk_male = Checkbox::new("male", is_male, change_gender);
            let button_save_smpl = Button::new("Save as .smpl", save_smpl);
            let button_save_mcs = Button::new("Save as .mcs", save_mcs);
            let button_save_gltf_smpl = Button::new("Save as .gltf (SMPL)", save_gltf_smpl);
            let button_save_gltf_unreal = Button::new("Save as .gltf (UNREAL)", save_gltf_unreal);
            widgets.push(Widgets::Checkbox(chk_neutral));
            widgets.push(Widgets::Checkbox(chk_female));
            widgets.push(Widgets::Checkbox(chk_male));
            widgets.push(Widgets::Checkbox(checkbox));
            widgets.push(Widgets::Button(button_save_smpl));
            widgets.push(Widgets::Button(button_save_mcs));
            widgets.push(Widgets::Button(button_save_gltf_smpl));
            widgets.push(Widgets::Button(button_save_gltf_unreal));
        }
    }
    GuiWindow {
        window_name: RString::from("SmplParams"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_betas_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    #[allow(clippy::range_plus_one)]
    extern "C" fn beta_slider_change(new_val: f32, widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        let beta_idx: usize = widget_name.split(' ').next_back().unwrap().parse().unwrap();
        if let Ok(mut betas) = scene.world.get::<&mut Betas>(*entity) {
            betas.betas = betas.betas.clone().slice_fill(beta_idx..beta_idx + 1, new_val);
        }
    }
    let mut widgets = RVec::new();
    #[allow(clippy::range_plus_one)]
    if let RSome(entity) = selected_entity {
        if let Ok(betas) = scene.world.get::<&Betas>(*entity) {
            for i in 0..betas.betas.dims()[0] {
                let slider = Slider::new(
                    ("Beta ".to_owned() + &i.to_string()).as_str(),
                    betas.betas.clone().slice(i..i + 1).into_scalar(),
                    -5.0,
                    5.0,
                    RSome(80.0),
                    beta_slider_change,
                    RNone,
                );
                widgets.push(Widgets::Slider(slider));
            }
        }
    }
    GuiWindow {
        window_name: RString::from("Betas"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::single_match)]
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_expression_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    #[allow(clippy::range_plus_one)]
    extern "C" fn expr_slider_change(new_val: f32, widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        if let Ok(mut expression) = scene.world.get::<&mut Expression>(*entity) {
            #[allow(unused_mut)]
            let mut coeff_idx: usize = 0;
            if let Ok(idx) = widget_name.split('_').next_back().unwrap().parse() {
                coeff_idx = idx;
            }

            expression.expr_coeffs = expression.expr_coeffs.clone().slice_fill(coeff_idx..coeff_idx + 1, new_val);
        }
    }
    let mut widgets = RVec::new();
    if let RSome(entity) = selected_entity {
        let face_type = scene
            .world
            .get::<&Expression>(*entity)
            .as_deref()
            .unwrap_or(&Expression::default())
            .expr_type;
        #[allow(clippy::range_plus_one)]
        if let Ok(expression) = scene.world.get::<&Expression>(*entity) {
            if face_type == FaceType::SmplX {
                for i in 0..expression.expr_coeffs.dims()[0] {
                    let slider = Slider::new(
                        ("Coeff_".to_owned() + &i.to_string()).as_str(),
                        expression.expr_coeffs.clone().slice(i..i + 1).into_scalar(),
                        -5.0,
                        5.0,
                        RSome(80.0),
                        expr_slider_change,
                        RNone,
                    );
                    widgets.push(Widgets::Slider(slider));
                }
            }
        }
    }
    GuiWindow {
        window_name: RString::from("Expression"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_vertex_offset_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    extern "C" fn vertex_offset_slider_change(new_val: f32, _widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        if let Ok(mut offsets) = scene.world.get::<&mut VertexOffsets>(*entity) {
            offsets.strength = new_val;
        }
    }
    let mut widgets = RVec::new();
    if let RSome(entity) = selected_entity {
        if let Ok(offsets) = scene.world.get::<&VertexOffsets>(*entity) {
            let slider = Slider::new("strength", offsets.strength, -3.0, 3.0, RSome(80.0), vertex_offset_slider_change, RNone);
            widgets.push(Widgets::Slider(slider));
        }
    }
    GuiWindow {
        window_name: RString::from("VertexOffsets"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_anim_scroll_gui(_selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_renderer::plugin_manager::gui::{Button, WindowPivot, WindowPosition, WindowPositionType};
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    extern "C" fn scene_anim_slider_change(new_val: f32, _widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            scene_anim.set_cur_time_as_sec(new_val);
            scene_anim.runner.temporary_pause = true;
        }
    }
    extern "C" fn scene_anim_slider_no_change(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            scene_anim.runner.temporary_pause = false;
        }
    }
    extern "C" fn scene_button_play_pause(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            scene_anim.runner.paused = !scene_anim.runner.paused;
        }
    }
    #[allow(clippy::cast_possible_truncation)]
    #[allow(clippy::cast_sign_loss)]
    extern "C" fn scene_button_next_frame(_widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            let nr_frames = scene_anim.num_frames;
            let duration = scene_anim.duration();
            let dt_between_frames = duration / nr_frames as u32;
            scene_anim.advance(dt_between_frames, false);
        }
    }
    extern "C" fn scene_fps_slider_change(new_val: f32, _widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if let Ok(mut scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
            let cur_time = scene_anim.get_cur_time();
            let prev_fps = scene_anim.config.fps;
            let multiplier_duration = prev_fps / new_val;
            scene_anim.set_cur_time_as_sec(cur_time.as_secs_f32() * multiplier_duration);
            scene_anim.config.fps = new_val;
        }
    }
    extern "C" fn follow_anim(new_val: bool, _widget_name: &RString, _entity: &Entity, scene: &mut Scene) {
        if new_val {
            scene.add_resource(Follower::new(FollowParams::default()));
        } else {
            let _ = scene.remove_resource::<Follower>();
        }
    }
    let mut widgets = RVec::new();
    if let Ok(scene_anim) = scene.get_resource::<&mut SceneAnimation>() {
        if scene_anim.num_frames != 0 {
            let max_duration = scene_anim.duration().as_secs_f32();
            let cur_time = scene_anim.get_cur_time().as_secs_f32();
            let slider_anim = Slider::new(
                "Time",
                cur_time,
                0.0,
                max_duration,
                RSome(800.0),
                scene_anim_slider_change,
                RSome(scene_anim_slider_no_change as extern "C" fn(&RString, &Entity, &mut Scene)),
            );
            let button_play_pause = Button::new("Play / Pause", scene_button_play_pause);
            let button_next_frame = Button::new("Next Frame", scene_button_next_frame);
            let slider_fps = Slider::new("FPS", scene_anim.config.fps, 1.0, 120.0, RSome(100.0), scene_fps_slider_change, RNone);
            let chk_follow_anim = Checkbox::new("Follow", scene.has_resource::<Follower>(), follow_anim);
            widgets.push(Widgets::Slider(slider_anim));
            widgets.push(Widgets::Horizontal(RVec::from(vec![
                Widgets::Button(button_play_pause),
                Widgets::Button(button_next_frame),
                Widgets::Slider(slider_fps),
            ])));
            widgets.push(Widgets::Horizontal(RVec::from(vec![Widgets::Checkbox(chk_follow_anim)])));
        }
    }
    GuiWindow {
        window_name: RString::from("Animation"),
        window_type: GuiWindowType::FloatWindow(WindowPivot::CenterBottom, WindowPosition([0.6, 1.0]), WindowPositionType::Fixed),
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::semicolon_if_nothing_returned)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_hand_pose_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_renderer::plugin_manager::gui::SelectableList;
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    use log::warn;
    use smpl_core::common::pose_hands::HandType;
    extern "C" fn set_hand_pose_type(widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        let hand_type = match widget_name.to_string().as_str() {
            "Flat" => Some(HandType::Flat),
            "Relaxed" => Some(HandType::Relaxed),
            "Curled" => Some(HandType::Curled),
            "Fist" => Some(HandType::Fist),
            _ => {
                warn!("HandType not known");
                None
            }
        };
        let mut command_buffer = CommandBuffer::new();
        if let Some(hand_type) = hand_type {
            info!("setting to {hand_type:?}");
            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(*entity) {
                info!("we already have a pose mask");
                pose_mask.set_overwrite_hands(hand_type);
            } else {
                info!("inserting a new pose mask");
                let pose_mask = PoseOverride::allow_all().overwrite_hands(hand_type).build();
                command_buffer.insert_one(*entity, pose_mask);
            }
        } else {
            info!("removing overwrite");
            if let Ok(mut pose_mask) = scene.world.get::<&mut PoseOverride>(*entity) {
                info!("removing overwrite and we have posemask");
                if pose_mask.get_overwrite_hands_type().is_some() {
                    pose_mask.remove_overwrite_hands();
                }
            }
        }
        command_buffer.run_on(&mut scene.world);
    }
    let mut widgets = RVec::new();
    if let RSome(entity) = selected_entity {
        if let Ok(pose_mask) = scene.world.get::<&PoseOverride>(*entity) {
            let hand_type_overwrite = pose_mask.get_overwrite_hands_type();
            let mut selectable_vec = RVec::new();
            selectable_vec.push(Selectable::new("None", hand_type_overwrite.is_none(), set_hand_pose_type));
            selectable_vec.push(Selectable::new("Flat", hand_type_overwrite == Some(HandType::Flat), set_hand_pose_type));
            selectable_vec.push(Selectable::new(
                "Relaxed",
                hand_type_overwrite == Some(HandType::Relaxed),
                set_hand_pose_type,
            ));
            selectable_vec.push(Selectable::new(
                "Curled",
                hand_type_overwrite == Some(HandType::Curled),
                set_hand_pose_type,
            ));
            selectable_vec.push(Selectable::new("Fist", hand_type_overwrite == Some(HandType::Fist), set_hand_pose_type));
            let selectable_list = SelectableList::new(selectable_vec, true);
            widgets.push(Widgets::SelectableList(selectable_list));
        }
    }
    GuiWindow {
        window_name: RString::from("HandOverwrite"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_interop_gui(selected_entity: &ROption<Entity>, scene: &mut Scene) -> GuiWindow {
    use gloss_utils::abi_stable_aliases::std_types::ROption::RSome;
    extern "C" fn gloss_interop_with_uv_set(val: bool, _widget_name: &RString, entity: &Entity, scene: &mut Scene) {
        if let Ok(mut gloss_interop) = scene.world.get::<&mut GlossInterop>(*entity) {
            gloss_interop.with_uv = val;
        }
        scene.world.remove_one::<UVs>(*entity).ok();
        scene.world.remove_one::<Normals>(*entity).ok();
        scene.world.remove_one::<Faces>(*entity).ok();
        scene.world.remove_one::<Colors>(*entity).ok();
        scene.world.remove_one::<Tangents>(*entity).ok();
    }
    let mut widgets = RVec::new();
    if let RSome(entity) = selected_entity {
        if let Ok(gloss_interop) = scene.world.get::<&GlossInterop>(*entity) {
            let checkbox_with_uv = Checkbox::new("with_uv", gloss_interop.with_uv, gloss_interop_with_uv_set);
            widgets.push(Widgets::Checkbox(checkbox_with_uv));
        }
    }
    GuiWindow {
        window_name: RString::from("GlossInterop"),
        window_type: GuiWindowType::Sidebar,
        widgets,
    }
}
#[allow(missing_docs)]
#[cfg(feature = "with-gui")]
#[allow(clippy::cast_precision_loss)]
#[cfg_attr(target_arch = "wasm32", allow(improper_ctypes_definitions))]
pub extern "C" fn smpl_event_dropfile(scene: &mut Scene, _runner: &mut RunnerState, event: &Event) -> bool {
    use crate::scene::McsCodecGloss;
    use log::warn;
    use smpl_core::codec::{codec::SmplCodec, scene::McsCodec};
    use std::path::PathBuf;
    let mut handled = false;
    match event {
        Event::DroppedFile(path) => {
            let path_buf = PathBuf::from(path.to_string());
            let filetype = match path_buf.extension() {
                Some(extension) => FileType::find_match(extension.to_str().unwrap_or("")),
                None => FileType::Unknown,
            };
            if scene.has_resource::<SceneAnimation>() {
                scene.remove_resource::<SceneAnimation>().unwrap();
            }
            match filetype {
                FileType::Smpl => {
                    info!("handling dropped smpl file {path}");
                    let codec = SmplCodec::from_file(path);
                    let mut builder = codec.to_entity_builder();
                    if !builder.has::<Betas>() {
                        warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
                        builder.add(Betas::default());
                    }
                    let gloss_interop = GlossInterop::default();
                    let name = scene.get_unused_name();
                    scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
                    handled = true;
                }
                FileType::Mcs => {
                    info!("handling dropped mcs file {path}");
                    let mut codec = McsCodec::from_file(path);
                    let builders = codec.to_entity_builders(true);
                    for mut builder in builders {
                        if !builder.has::<Betas>() {
                            warn!("The .smpl file didn't have any shape_parameters associated, we are defaulting to the mean smpl shape");
                            builder.add(Betas::default());
                        }
                        let gloss_interop = GlossInterop::default();
                        let name = scene.get_unused_name();
                        scene.get_or_create_entity(&name).insert_builder(builder).insert(gloss_interop);
                        handled = true;
                    }
                    if let Some(fps) = codec.frame_rate {
                        let smpl_scene = SceneAnimation::new_with_fps(codec.num_frames, fps);
                        scene.add_resource(smpl_scene);
                    }
                }
                _ => {
                    info!("No known filetype {path}");
                }
            }
        }
    }
    handled
}
