nightshade-editor 0.13.4

An interactive editor for the Nightshade game engine
use nalgebra_glm::{Quat, Vec3, Vec4};
use nightshade::ecs::bounding_volume::components::BoundingVolume;
use nightshade::ecs::text::components::{TextAlignment, TextProperties, VerticalAlignment};
use nightshade::prelude::*;

fn hue_to_rgb(hue: f32) -> Vec3 {
    let h = hue / 60.0;
    let c = 1.0;
    let x = c * (1.0 - ((h % 2.0) - 1.0).abs());

    let (r, g, b) = match h as i32 {
        0 => (c, x, 0.0),
        1 => (x, c, 0.0),
        2 => (0.0, c, x),
        3 => (0.0, x, c),
        4 => (x, 0.0, c),
        _ => (c, 0.0, x),
    };

    Vec3::new(r, g, b)
}

pub fn spawn_lines(world: &mut World) {
    const LINE_COUNT: usize = 2_000;

    let entity = world.spawn_entities(
        LINES | NAME | LOCAL_TRANSFORM | GLOBAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | VISIBILITY,
        1,
    )[0];

    if let Some(name) = world.core.get_name_mut(entity) {
        *name = Name(format!("{} Lines", LINE_COUNT));
    }

    if let Some(transform) = world.core.get_local_transform_mut(entity) {
        *transform = LocalTransform::default();
    }

    if let Some(lines) = world.core.get_lines_mut(entity) {
        let mut test_lines = Vec::new();

        (0..LINE_COUNT).for_each(|index| {
            let angle = (index as f32 / LINE_COUNT as f32) * std::f32::consts::PI * 20.0;
            let radius = 5.0 + (index as f32 / LINE_COUNT as f32) * 50.0;
            let height = (index as f32 / 100.0).sin() * 20.0;

            let start = Vec3::new(radius * angle.cos(), height, radius * angle.sin());
            let end = Vec3::new(
                radius * (angle + 0.05).cos(),
                height + 2.0,
                radius * (angle + 0.05).sin(),
            );

            let hue = (index as f32 / LINE_COUNT as f32) * 360.0;
            let color = hue_to_rgb(hue);

            test_lines.push(Line {
                start,
                end,
                color: Vec4::new(color.x, color.y, color.z, 1.0),
            });
        });

        lines.lines = test_lines;
    }
}

pub fn spawn_meshes(world: &mut World) {
    const MESH_COUNT: usize = 10_000;
    const GRID_SIZE: usize = 22;
    const SPACING: f32 = 2.0;

    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
        1,
    )[0];

    world
        .core
        .set_name(parent_entity, Name("10,000 Meshes Group".to_string()));
    world.core.set_local_transform(
        parent_entity,
        LocalTransform {
            translation: Vec3::new(0.0, 0.0, 0.0),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world
        .core
        .set_local_transform_dirty(parent_entity, LocalTransformDirty);
    world
        .core
        .set_global_transform(parent_entity, GlobalTransform::default());

    let mut mesh_count = 0;
    let mesh_types = ["Cube", "Sphere", "Cylinder", "Cone", "Torus", "Plane"];
    let offset = -(GRID_SIZE as f32 * SPACING) / 2.0;
    let y_offset = 1.0;

    'outer: for x in 0..GRID_SIZE {
        for y in 0..GRID_SIZE {
            for z in 0..GRID_SIZE {
                if mesh_count >= MESH_COUNT {
                    break 'outer;
                }

                let entity = world.spawn_entities(
                    NAME | LOCAL_TRANSFORM
                        | LOCAL_TRANSFORM_DIRTY
                        | GLOBAL_TRANSFORM
                        | RENDER_MESH
                        | MATERIAL_REF
                        | VISIBILITY
                        | CASTS_SHADOW
                        | PARENT
                        | BOUNDING_VOLUME,
                    1,
                )[0];

                world.update_parent(entity, Some(Parent(Some(parent_entity))));

                world
                    .core
                    .set_name(entity, Name(format!("Mesh {}", mesh_count + 1)));

                world.core.set_local_transform(
                    entity,
                    LocalTransform {
                        translation: Vec3::new(
                            offset + x as f32 * SPACING,
                            offset + y as f32 * SPACING + y_offset,
                            offset + z as f32 * SPACING,
                        ),
                        rotation: Quat::identity(),
                        scale: Vec3::new(0.5, 0.5, 0.5),
                    },
                );
                world
                    .core
                    .set_local_transform_dirty(entity, LocalTransformDirty);
                world
                    .core
                    .set_global_transform(entity, GlobalTransform::default());

                let mesh_type = mesh_types[mesh_count % mesh_types.len()];
                world
                    .core
                    .set_render_mesh(entity, RenderMesh::new(mesh_type));
                world.resources.mesh_render_state.mark_entity_added(entity);
                world
                    .core
                    .set_bounding_volume(entity, BoundingVolume::from_mesh_type(mesh_type));

                let material_name = match mesh_count % 6 {
                    0 => "Red",
                    1 => "Green",
                    2 => "Blue",
                    3 => "Yellow",
                    4 => "Magenta",
                    _ => "Cyan",
                };
                if let Some(&index) = world
                    .resources
                    .material_registry
                    .registry
                    .name_to_index
                    .get(material_name)
                {
                    world
                        .resources
                        .material_registry
                        .registry
                        .add_reference(index);
                }
                world
                    .core
                    .set_material_ref(entity, MaterialRef::new(material_name.to_string()));

                world
                    .core
                    .set_visibility(entity, Visibility { visible: true });
                world.core.set_casts_shadow(entity, CastsShadow);

                mesh_count += 1;
            }
        }
    }
}

pub fn spawn_3d_text(world: &mut World) {
    let text_index = world.resources.text_cache.add_text("3D Text Object");
    let entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM | TEXT | VISIBILITY,
        1,
    )[0];

    world.core.set_name(entity, Name("3D Text".to_string()));

    world.core.set_local_transform(
        entity,
        LocalTransform {
            translation: Vec3::new(
                rand::random::<f32>() * 10.0 - 5.0,
                3.0,
                rand::random::<f32>() * 10.0 - 5.0,
            ),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world
        .core
        .set_local_transform_dirty(entity, LocalTransformDirty);
    world
        .core
        .set_global_transform(entity, GlobalTransform::default());

    if let Some(text) = world.core.get_text_mut(entity) {
        text.text_index = text_index;
        text.properties = TextProperties {
            font_size: 32.0,
            color: Vec4::new(
                rand::random::<f32>(),
                rand::random::<f32>(),
                rand::random::<f32>(),
                1.0,
            ),
            alignment: TextAlignment::Center,
            vertical_alignment: VerticalAlignment::Middle,
            ..Default::default()
        };
        text.dirty = true;
    }
}

pub fn spawn_text_lattice(world: &mut World) {
    const TEXT_COUNT: usize = 5_000;
    const GRID_X: usize = 25;
    const GRID_Y: usize = 20;
    const GRID_Z: usize = 10;
    const SPACING: f32 = 3.0;

    let parent_entity = world.spawn_entities(
        NAME | LOCAL_TRANSFORM | LOCAL_TRANSFORM_DIRTY | GLOBAL_TRANSFORM,
        1,
    )[0];

    world.core.set_name(
        parent_entity,
        Name("Text Lattice (Stress Test)".to_string()),
    );
    world.core.set_local_transform(
        parent_entity,
        LocalTransform {
            translation: Vec3::new(0.0, 5.0, 0.0),
            rotation: Quat::identity(),
            scale: Vec3::new(1.0, 1.0, 1.0),
        },
    );
    world
        .core
        .set_local_transform_dirty(parent_entity, LocalTransformDirty);
    world
        .core
        .set_global_transform(parent_entity, GlobalTransform::default());

    let text_variations = [
        "Text", "Hello", "World", "Test", "Lorem", "Ipsum", "3D", "Engine", "Render", "Buffer",
        "Dynamic", "Scale", "Vertex", "Index", "GPU", "SDF",
    ];

    let offset_x = -(GRID_X as f32 * SPACING) / 2.0;
    let offset_y = -(GRID_Y as f32 * SPACING) / 2.0;
    let offset_z = -(GRID_Z as f32 * SPACING) / 2.0;

    let mut text_count = 0;

    'outer: for x in 0..GRID_X {
        for y in 0..GRID_Y {
            for z in 0..GRID_Z {
                if text_count >= TEXT_COUNT {
                    break 'outer;
                }

                let text_content = format!(
                    "{} #{}",
                    text_variations[text_count % text_variations.len()],
                    text_count + 1
                );
                let text_index = world.resources.text_cache.add_text(&text_content);

                let entity = world.spawn_entities(
                    NAME | LOCAL_TRANSFORM
                        | LOCAL_TRANSFORM_DIRTY
                        | GLOBAL_TRANSFORM
                        | TEXT
                        | VISIBILITY
                        | PARENT,
                    1,
                )[0];

                world.core.set_parent(entity, Parent(Some(parent_entity)));
                world
                    .core
                    .set_name(entity, Name(format!("Text {}", text_count + 1)));

                world.core.set_local_transform(
                    entity,
                    LocalTransform {
                        translation: Vec3::new(
                            offset_x + x as f32 * SPACING,
                            offset_y + y as f32 * SPACING,
                            offset_z + z as f32 * SPACING,
                        ),
                        rotation: Quat::identity(),
                        scale: Vec3::new(1.0, 1.0, 1.0),
                    },
                );
                world
                    .core
                    .set_local_transform_dirty(entity, LocalTransformDirty);
                world
                    .core
                    .set_global_transform(entity, GlobalTransform::default());

                if let Some(text) = world.core.get_text_mut(entity) {
                    text.text_index = text_index;
                    text.properties = TextProperties {
                        font_size: 16.0,
                        color: Vec4::new(
                            rand::random::<f32>(),
                            rand::random::<f32>(),
                            rand::random::<f32>(),
                            1.0,
                        ),
                        alignment: TextAlignment::Center,
                        vertical_alignment: VerticalAlignment::Middle,
                        ..Default::default()
                    };
                    text.dirty = true;
                }

                text_count += 1;
            }
        }
    }
}