fyroxed_base 0.36.2

A scene editor for Fyrox game engine
Documentation
// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

use crate::{
    command::{Command, CommandGroup},
    fyrox::{
        core::pool::Handle,
        engine::Engine,
        graph::SceneGraph,
        gui::{
            button::{ButtonBuilder, ButtonMessage},
            message::{MessageDirection, UiMessage},
            stack_panel::StackPanelBuilder,
            utils::make_simple_tooltip,
            widget::WidgetBuilder,
            window::{WindowBuilder, WindowMessage, WindowTitle},
            BuildContext, HorizontalAlignment, Thickness, UiNode, VerticalAlignment,
        },
        scene::{
            base::BaseBuilder,
            collider::{ColliderBuilder, ColliderShape, ConvexPolyhedronShape, GeometrySource},
            mesh::{
                surface::{SurfaceBuilder, SurfaceResource},
                Mesh, MeshBuilder,
            },
            node::Node,
            rigidbody::{RigidBody, RigidBodyBuilder, RigidBodyType},
            Scene,
        },
    },
    message::MessageSender,
    preview::PreviewPanel,
    scene::{
        commands::graph::{AddNodeCommand, LinkNodesCommand},
        GameScene, Selection,
    },
    world::graph::selection::GraphSelection,
    Message,
};

pub struct MeshControlPanel {
    scene_viewer_frame: Handle<UiNode>,
    pub window: Handle<UiNode>,
    create_trimesh_collider: Handle<UiNode>,
    create_convex_collider: Handle<UiNode>,
    create_trimesh_rigid_body: Handle<UiNode>,
    add_convex_collider: Handle<UiNode>,
    add_trimesh_collider: Handle<UiNode>,
}

fn make_button(text: &str, tooltip: &str, ctx: &mut BuildContext) -> Handle<UiNode> {
    ButtonBuilder::new(
        WidgetBuilder::new()
            .with_margin(Thickness::uniform(1.0))
            .with_tooltip(make_simple_tooltip(ctx, tooltip)),
    )
    .with_text(text)
    .build(ctx)
}

fn meshes_iter<'a>(
    selection: &'a GraphSelection,
    scene: &'a Scene,
) -> impl Iterator<Item = (Handle<Node>, &'a Mesh)> + 'a {
    selection.nodes.iter().filter_map(|handle| {
        scene
            .graph
            .try_get_of_type::<Mesh>(*handle)
            .map(|mesh| (*handle, mesh))
    })
}

impl MeshControlPanel {
    pub fn new(scene_viewer_frame: Handle<UiNode>, ctx: &mut BuildContext) -> Self {
        let create_trimesh_collider = make_button(
            "Create Trimesh Collider",
            "Creates a new trimesh collider and attaches it to the selected mesh(es)",
            ctx,
        );
        let create_convex_collider = make_button(
            "Create Convex Collider",
            "Creates a new convex (polyhedron) collider and attaches it to the selected mesh(es).",
            ctx,
        );
        let create_trimesh_rigid_body = make_button(
            "Create Trimesh Rigid Body",
            "Creates a new static rigid body with trimesh collider and attaches the selected \
            mesh(es) to it.",
            ctx,
        );
        let add_convex_collider = make_button(
            "Add Convex Collider",
            "Creates a new convex (polyhedron) collider and attaches it to an ancestor rigid \
            body. This option could be useful if you have multiple meshes and want to put them into \
            a single rigid body.",
            ctx,
        );
        let add_trimesh_collider = make_button(
            "Add Trimesh Collider",
            "Creates a new trimesh collider and attaches it to an ancestor rigid body. This \
            option could be useful if you have multiple meshes and want to put them into a single \
            rigid body.",
            ctx,
        );
        let window = WindowBuilder::new(
            WidgetBuilder::new()
                .with_width(210.0)
                .with_height(200.0)
                .with_name("MeshControlPanel"),
        )
        .open(false)
        .with_title(WindowTitle::text("Mesh Control Panel"))
        .with_content(
            StackPanelBuilder::new(
                WidgetBuilder::new()
                    .with_child(create_trimesh_collider)
                    .with_child(create_convex_collider)
                    .with_child(create_trimesh_rigid_body)
                    .with_child(add_convex_collider)
                    .with_child(add_trimesh_collider),
            )
            .build(ctx),
        )
        .build(ctx);

        Self {
            scene_viewer_frame,
            window,
            create_trimesh_collider,
            create_convex_collider,
            create_trimesh_rigid_body,
            add_convex_collider,
            add_trimesh_collider,
        }
    }

    pub fn handle_ui_message(
        &mut self,
        message: &UiMessage,
        editor_selection: &Selection,
        game_scene: &mut GameScene,
        engine: &mut Engine,
        sender: &MessageSender,
    ) {
        let Some(selection) = editor_selection.as_graph() else {
            return;
        };

        let scene = &engine.scenes[game_scene.scene];

        let mut commands = Vec::new();

        if let Some(ButtonMessage::Click) = message.data() {
            if message.destination() == self.create_trimesh_collider {
                for (mesh_handle, _) in meshes_iter(selection, scene) {
                    let collider =
                        ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
                            .with_shape(ColliderShape::trimesh(vec![GeometrySource(mesh_handle)]))
                            .build_node();
                    commands.push(Command::new(AddNodeCommand::new(
                        collider,
                        mesh_handle,
                        false,
                    )))
                }
            } else if message.destination() == self.create_convex_collider {
                for (mesh_handle, _) in meshes_iter(selection, scene) {
                    let collider =
                        ColliderBuilder::new(BaseBuilder::new().with_name("ConvexCollider"))
                            .with_shape(ColliderShape::Polyhedron(ConvexPolyhedronShape {
                                geometry_source: GeometrySource(mesh_handle),
                            }))
                            .build_node();
                    commands.push(Command::new(AddNodeCommand::new(
                        collider,
                        mesh_handle,
                        false,
                    )))
                }
            } else if message.destination() == self.create_trimesh_rigid_body {
                let handles = scene
                    .graph
                    .generate_free_handles(2 * meshes_iter(selection, scene).count());

                for (rb_collider_handles, (mesh_handle, mesh)) in
                    handles.chunks(2).zip(meshes_iter(selection, scene))
                {
                    let rigid_body_handle = rb_collider_handles[0];
                    let collider_handle = rb_collider_handles[1];

                    let rigid_body =
                        RigidBodyBuilder::new(BaseBuilder::new().with_name("RigidBody"))
                            .with_body_type(RigidBodyType::Static)
                            .build_node();
                    let collider =
                        ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
                            .with_shape(ColliderShape::trimesh(vec![GeometrySource(mesh_handle)]))
                            .build_node();
                    commands.extend([
                        Command::new(AddNodeCommand::new(rigid_body, mesh_handle, false)),
                        Command::new(AddNodeCommand::new(collider, rigid_body_handle, false)),
                        Command::new(LinkNodesCommand::new(rigid_body_handle, mesh.parent())),
                        Command::new(LinkNodesCommand::new(mesh_handle, rigid_body_handle)),
                        Command::new(LinkNodesCommand::new(collider_handle, rigid_body_handle)),
                    ]);
                }
            } else if message.destination() == self.add_convex_collider {
                for (mesh_handle, _) in meshes_iter(selection, scene) {
                    if let Some((ancestor_rigid_body, _)) =
                        scene.graph.find_component_up::<RigidBody>(mesh_handle)
                    {
                        let collider =
                            ColliderBuilder::new(BaseBuilder::new().with_name("ConvexCollider"))
                                .with_shape(ColliderShape::Polyhedron(ConvexPolyhedronShape {
                                    geometry_source: GeometrySource(mesh_handle),
                                }))
                                .build_node();
                        commands.push(Command::new(AddNodeCommand::new(
                            collider,
                            ancestor_rigid_body,
                            false,
                        )))
                    }
                }
            } else if message.destination() == self.add_trimesh_collider {
                for (mesh_handle, _) in meshes_iter(selection, scene) {
                    if let Some((ancestor_rigid_body, _)) =
                        scene.graph.find_component_up::<RigidBody>(mesh_handle)
                    {
                        let collider =
                            ColliderBuilder::new(BaseBuilder::new().with_name("TrimeshCollider"))
                                .with_shape(ColliderShape::trimesh(vec![GeometrySource(
                                    mesh_handle,
                                )]))
                                .build_node();
                        commands.push(Command::new(AddNodeCommand::new(
                            collider,
                            ancestor_rigid_body,
                            false,
                        )))
                    }
                }
            }
        }

        if !commands.is_empty() {
            sender.do_command(CommandGroup::from(commands));
        }
    }

    pub fn handle_message(
        &mut self,
        message: &Message,
        editor_selection: &Selection,
        game_scene: &mut GameScene,
        engine: &mut Engine,
    ) {
        let Message::SelectionChanged { .. } = message else {
            return;
        };

        let scene = &engine.scenes[game_scene.scene];
        let Some(selection) = editor_selection.as_graph() else {
            return;
        };

        let any_mesh = selection
            .nodes
            .iter()
            .any(|n| scene.graph.try_get_of_type::<Mesh>(*n).is_some());
        if any_mesh {
            engine
                .user_interfaces
                .first_mut()
                .send_message(WindowMessage::open_and_align(
                    self.window,
                    MessageDirection::ToWidget,
                    self.scene_viewer_frame,
                    HorizontalAlignment::Right,
                    VerticalAlignment::Top,
                    Thickness::top_right(5.0),
                    false,
                    false,
                ));
        } else {
            engine
                .user_interfaces
                .first_mut()
                .send_message(WindowMessage::close(
                    self.window,
                    MessageDirection::ToWidget,
                ));
        }
    }
}

pub struct SurfaceDataViewer {
    pub window: Handle<UiNode>,
    preview_panel: PreviewPanel,
}

impl SurfaceDataViewer {
    pub fn new(engine: &mut Engine) -> Self {
        let preview_panel = PreviewPanel::new(engine, 386, 386);

        let ctx = &mut engine.user_interfaces.first_mut().build_ctx();

        let window = WindowBuilder::new(WidgetBuilder::new().with_width(400.0).with_height(400.0))
            .open(false)
            .with_content(preview_panel.root)
            .build(ctx);

        Self {
            window,
            preview_panel,
        }
    }

    pub fn open(&mut self, surface_data: SurfaceResource, engine: &mut Engine) {
        let ui = engine.user_interfaces.first();
        ui.send_message(WindowMessage::open_modal(
            self.window,
            MessageDirection::ToWidget,
            true,
            true,
        ));

        let guard = surface_data.data_ref();
        let title = WindowTitle::text(format!(
            "Surface Data - Vertices: {} Triangles: {}",
            guard.vertex_buffer.vertex_count(),
            guard.geometry_buffer.len(),
        ));
        ui.send_message(WindowMessage::title(
            self.window,
            MessageDirection::ToWidget,
            title,
        ));
        drop(guard);

        let graph = &mut engine.scenes[self.preview_panel.scene()].graph;
        let mesh = MeshBuilder::new(BaseBuilder::new())
            .with_surfaces(vec![SurfaceBuilder::new(surface_data).build()])
            .build(graph);

        self.preview_panel.set_model(mesh, engine);
    }

    pub fn handle_ui_message(mut self, message: &UiMessage, engine: &mut Engine) -> Option<Self> {
        self.preview_panel.handle_message(message, engine);

        if let Some(WindowMessage::Close) = message.data() {
            if message.destination() == self.window {
                self.preview_panel.destroy(engine);
                return None;
            }
        }

        Some(self)
    }

    pub fn update(&mut self, engine: &mut Engine) {
        self.preview_panel.update(engine)
    }
}