nightshade 0.8.2

A cross-platform data-oriented game engine.
Documentation
use super::HierarchyNode;
use crate::ecs::bounding_volume::components::BoundingVolume;
use crate::ecs::grass::{GrassInteractor, GrassRegion};
use crate::ecs::navmesh::NavMeshAgent;
use crate::ecs::particles::components::ParticleEmitter;
use crate::ecs::physics::components::CharacterControllerComponent;
use crate::ecs::transform::components::IgnoreParentScale;
use crate::ecs::world::{CASTS_SHADOW, IGNORE_PARENT_SCALE};
use crate::prelude::*;

#[derive(Clone, serde::Serialize, serde::Deserialize)]
#[serde(crate = "serde")]
pub struct EntitySnapshot {
    pub name: Option<Name>,
    pub local_transform: Option<LocalTransform>,
    #[serde(skip)]
    pub global_transform: Option<GlobalTransform>,
    #[serde(skip)]
    pub parent_entity: Option<Entity>,
    pub camera: Option<Camera>,
    pub light: Option<Light>,
    pub visibility: Option<Visibility>,
    pub render_mesh: Option<RenderMesh>,
    pub instanced_mesh: Option<InstancedMesh>,
    pub material_ref: Option<MaterialRef>,
    pub render_layer: Option<RenderLayer>,
    pub script: Option<Script>,
    pub text: Option<Text>,
    pub hud_text: Option<HudText>,
    pub lines: Option<Lines>,
    pub sprite: Option<Sprite>,
    pub sprite_animator: Option<SpriteAnimator>,
    pub decal: Option<Decal>,
    pub water: Option<Water>,
    pub animation_player: Option<AnimationPlayer>,
    pub bounding_volume: Option<BoundingVolume>,
    pub casts_shadow: bool,
    pub ignore_parent_scale: bool,
    #[serde(skip)]
    pub particle_emitter: Option<ParticleEmitter>,
    #[serde(skip)]
    pub grass_region: Option<GrassRegion>,
    #[serde(skip)]
    pub grass_interactor: Option<GrassInteractor>,
    #[serde(skip)]
    pub character_controller: Option<CharacterControllerComponent>,
    #[serde(skip)]
    pub navmesh_agent: Option<NavMeshAgent>,
}

impl EntitySnapshot {
    pub fn capture(world: &World, entity: Entity) -> Self {
        let parent_entity = world.get_parent(entity).and_then(|p| p.0);

        Self {
            name: world.get_name(entity).cloned(),
            local_transform: world.get_local_transform(entity).cloned(),
            global_transform: world.get_global_transform(entity).cloned(),
            parent_entity,
            camera: world.get_camera(entity).cloned(),
            light: world.get_light(entity).cloned(),
            visibility: world.get_visibility(entity).cloned(),
            render_mesh: world.get_render_mesh(entity).cloned(),
            instanced_mesh: world.get_instanced_mesh(entity).cloned(),
            material_ref: world.get_material_ref(entity).cloned(),
            render_layer: world.get_render_layer(entity).cloned(),
            script: world.get_script(entity).cloned(),
            text: world.get_text(entity).cloned(),
            hud_text: world.get_hud_text(entity).cloned(),
            lines: world.get_lines(entity).cloned(),
            sprite: world.get_sprite(entity).cloned(),
            sprite_animator: world.get_sprite_animator(entity).cloned(),
            decal: world.get_decal(entity).cloned(),
            water: world.get_water(entity).cloned(),
            animation_player: world.get_animation_player(entity).cloned(),
            bounding_volume: world.get_bounding_volume(entity).cloned(),
            casts_shadow: world.entity_has_casts_shadow(entity),
            ignore_parent_scale: world.entity_has_ignore_parent_scale(entity),
            particle_emitter: world.get_particle_emitter(entity).cloned(),
            grass_region: world.get_grass_region(entity).cloned(),
            grass_interactor: world.get_grass_interactor(entity).cloned(),
            character_controller: world.get_character_controller(entity).cloned(),
            navmesh_agent: world.get_navmesh_agent(entity).cloned(),
        }
    }
}

pub fn capture_hierarchy(world: &World, entity: Entity) -> HierarchyNode {
    let snapshot = EntitySnapshot::capture(world, entity);
    let children = query_children(world, entity)
        .into_iter()
        .map(|child| capture_hierarchy(world, child))
        .collect();
    HierarchyNode {
        entity,
        snapshot,
        children,
    }
}

pub fn recreate_hierarchy_with_mapping(
    world: &mut World,
    node: &HierarchyNode,
    parent: Option<Entity>,
) -> (Entity, std::collections::HashMap<Entity, Entity>) {
    let mut mapping = std::collections::HashMap::new();
    let root_entity = recreate_hierarchy_internal_with_mapping(world, node, parent, &mut mapping);
    (root_entity, mapping)
}

fn recreate_hierarchy_internal_with_mapping(
    world: &mut World,
    node: &HierarchyNode,
    parent: Option<Entity>,
    mapping: &mut std::collections::HashMap<Entity, Entity>,
) -> Entity {
    let new_entity = recreate_entity(world, &node.snapshot, parent);
    mapping.insert(node.entity, new_entity);

    for child_node in &node.children {
        recreate_hierarchy_internal_with_mapping(world, child_node, Some(new_entity), mapping);
    }

    new_entity
}

pub fn recreate_entity(
    world: &mut World,
    snapshot: &EntitySnapshot,
    parent: Option<Entity>,
) -> Entity {
    let entities = EntityBuilder::new()
        .with_local_transform(snapshot.local_transform.unwrap_or_default())
        .with_global_transform(snapshot.global_transform.unwrap_or_default())
        .spawn(world, 1);
    let entity = entities[0];

    if let Some(name) = &snapshot.name {
        world.set_name(entity, name.clone());
    }

    if let Some(parent_entity) = parent
        && world.get_local_transform(parent_entity).is_some()
    {
        world.set_parent(entity, Parent(Some(parent_entity)));
    }

    if let Some(camera) = &snapshot.camera {
        world.set_camera(entity, *camera);
    }
    if let Some(light) = &snapshot.light {
        world.set_light(entity, light.clone());
    }
    if let Some(visibility) = &snapshot.visibility {
        world.set_visibility(entity, visibility.clone());
    }
    if let Some(render_mesh) = &snapshot.render_mesh {
        world.set_render_mesh(entity, render_mesh.clone());
        world.resources.mesh_render_state.mark_entity_added(entity);
        if let Some(&index) = world
            .resources
            .mesh_cache
            .registry
            .name_to_index
            .get(&render_mesh.name)
        {
            world.resources.mesh_cache.registry.add_reference(index);
        }
    }
    if let Some(material_ref) = &snapshot.material_ref {
        world.set_material_ref(entity, material_ref.clone());
        if let Some(&index) = world
            .resources
            .material_registry
            .registry
            .name_to_index
            .get(&material_ref.name)
        {
            world
                .resources
                .material_registry
                .registry
                .add_reference(index);
        }
    }
    if let Some(instanced_mesh) = &snapshot.instanced_mesh {
        world.set_instanced_mesh(entity, instanced_mesh.clone());
    }
    if let Some(render_layer) = &snapshot.render_layer {
        world.set_render_layer(entity, *render_layer);
    }
    if let Some(script) = &snapshot.script {
        world.set_script(entity, script.clone());
    }
    if let Some(text) = &snapshot.text {
        world.set_text(entity, text.clone());
    }
    if let Some(hud_text) = &snapshot.hud_text {
        world.set_hud_text(entity, hud_text.clone());
    }
    if let Some(lines) = &snapshot.lines {
        world.set_lines(entity, lines.clone());
    }
    if let Some(sprite) = &snapshot.sprite {
        world.set_sprite(entity, sprite.clone());
    }
    if let Some(sprite_animator) = &snapshot.sprite_animator {
        world.set_sprite_animator(entity, sprite_animator.clone());
    }
    if let Some(decal) = &snapshot.decal {
        world.set_decal(entity, decal.clone());
    }
    if let Some(water) = &snapshot.water {
        world.set_water(entity, water.clone());
    }
    if let Some(animation_player) = &snapshot.animation_player {
        world.set_animation_player(entity, animation_player.clone());
    }
    if let Some(bounding_volume) = &snapshot.bounding_volume {
        world.set_bounding_volume(entity, *bounding_volume);
    }
    if snapshot.casts_shadow {
        world.add_components(entity, CASTS_SHADOW);
    }
    if snapshot.ignore_parent_scale {
        world.set_ignore_parent_scale(entity, IgnoreParentScale);
    }
    if let Some(particle_emitter) = &snapshot.particle_emitter {
        world.set_particle_emitter(entity, particle_emitter.clone());
    }
    if let Some(grass_region) = &snapshot.grass_region {
        world.set_grass_region(entity, grass_region.clone());
    }
    if let Some(grass_interactor) = &snapshot.grass_interactor {
        world.set_grass_interactor(entity, grass_interactor.clone());
    }
    if let Some(character_controller) = &snapshot.character_controller {
        world.set_character_controller(entity, character_controller.clone());
    }
    if let Some(navmesh_agent) = &snapshot.navmesh_agent {
        world.set_navmesh_agent(entity, navmesh_agent.clone());
    }

    entity
}

pub fn apply_snapshot_to_entity(world: &mut World, entity: Entity, snapshot: &EntitySnapshot) {
    macro_rules! apply_optional {
        ($field:ident, $set:ident, $remove:ident) => {
            if let Some(value) = &snapshot.$field {
                world.$set(entity, value.clone());
            } else {
                world.$remove(entity);
            }
        };
    }

    apply_optional!(name, set_name, remove_name);
    apply_optional!(camera, set_camera, remove_camera);
    apply_optional!(light, set_light, remove_light);
    apply_optional!(visibility, set_visibility, remove_visibility);
    apply_optional!(render_layer, set_render_layer, remove_render_layer);
    apply_optional!(script, set_script, remove_script);
    apply_optional!(text, set_text, remove_text);
    apply_optional!(hud_text, set_hud_text, remove_hud_text);
    apply_optional!(lines, set_lines, remove_lines);
    apply_optional!(sprite, set_sprite, remove_sprite);
    apply_optional!(sprite_animator, set_sprite_animator, remove_sprite_animator);
    apply_optional!(decal, set_decal, remove_decal);
    apply_optional!(water, set_water, remove_water);
    apply_optional!(
        animation_player,
        set_animation_player,
        remove_animation_player
    );
    apply_optional!(bounding_volume, set_bounding_volume, remove_bounding_volume);
    apply_optional!(
        particle_emitter,
        set_particle_emitter,
        remove_particle_emitter
    );
    apply_optional!(grass_region, set_grass_region, remove_grass_region);
    apply_optional!(
        grass_interactor,
        set_grass_interactor,
        remove_grass_interactor
    );
    apply_optional!(
        character_controller,
        set_character_controller,
        remove_character_controller
    );
    apply_optional!(navmesh_agent, set_navmesh_agent, remove_navmesh_agent);

    if let Some(render_mesh) = &snapshot.render_mesh {
        world.set_render_mesh(entity, render_mesh.clone());
        world.resources.mesh_render_state.mark_entity_added(entity);
    } else {
        world.remove_render_mesh(entity);
    }

    if let Some(material_ref) = &snapshot.material_ref {
        world.set_material_ref(entity, material_ref.clone());
    } else {
        world.remove_material_ref(entity);
    }

    if let Some(instanced_mesh) = &snapshot.instanced_mesh {
        world.set_instanced_mesh(entity, instanced_mesh.clone());
    } else {
        world.remove_instanced_mesh(entity);
    }

    if snapshot.casts_shadow {
        world.add_components(entity, CASTS_SHADOW);
    } else {
        world.remove_components(entity, CASTS_SHADOW);
    }

    if snapshot.ignore_parent_scale {
        world.set_ignore_parent_scale(entity, IgnoreParentScale);
    } else {
        world.remove_components(entity, IGNORE_PARENT_SCALE);
    }

    if let Some(local_transform) = &snapshot.local_transform {
        world.set_local_transform(entity, *local_transform);
        mark_local_transform_dirty(world, entity);
    }
}