fyroxed_base 1.0.0-rc.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::{
    camera::PickingOptions,
    command::{Command, CommandGroup},
    fyrox::{
        core::{
            algebra::{UnitQuaternion, Vector2},
            math::round_to_step,
            pool::Handle,
            some_or_return,
            uuid::{uuid, Uuid},
            TypeUuidProvider,
        },
        gui::BuildContext,
    },
    interaction::{
        gizmo::rotate_gizmo::RotationGizmo, make_interaction_mode_button, InteractionMode,
    },
    message::MessageSender,
    scene::{
        commands::{graph::RotateNodeCommand, ChangeSelectionCommand},
        controller::SceneController,
        GameScene, Selection, SelectionContainer,
    },
    settings::Settings,
    world::selection::GraphSelection,
    Engine,
};
use fyrox::gui::button::Button;

pub struct RotateInteractionMode {
    initial_rotations: Vec<UnitQuaternion<f32>>,
    rotation_gizmo: RotationGizmo,
    interacting: bool,
    message_sender: MessageSender,
}

impl RotateInteractionMode {
    pub fn new(game_scene: &GameScene, engine: &mut Engine, message_sender: MessageSender) -> Self {
        Self {
            initial_rotations: Default::default(),
            rotation_gizmo: RotationGizmo::new(game_scene, engine),
            interacting: false,
            message_sender,
        }
    }
}

impl TypeUuidProvider for RotateInteractionMode {
    fn type_uuid() -> Uuid {
        uuid!("37f20364-feb5-4731-8c19-c3df922818d6")
    }
}

impl InteractionMode for RotateInteractionMode {
    fn on_left_mouse_button_down(
        &mut self,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        engine: &mut Engine,
        mouse_pos: Vector2<f32>,
        _frame_size: Vector2<f32>,
        settings: &Settings,
    ) {
        let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
            return;
        };

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

        // Pick gizmo nodes.
        if let Some(result) = game_scene.camera_controller.pick(
            graph,
            PickingOptions {
                cursor_pos: mouse_pos,
                editor_only: true,
                filter: Some(&mut |handle, _| handle != self.rotation_gizmo.origin),
                ignore_back_faces: false,
                use_picking_loop: true,
                method: Default::default(),
                settings: &settings.selection,
            },
        ) {
            if self.rotation_gizmo.handle_pick(result.node, graph) {
                let graph = &mut engine.scenes[game_scene.scene].graph;
                if let Some(selection) = editor_selection.as_graph() {
                    self.interacting = true;
                    self.initial_rotations = selection.local_rotations(graph);
                }
            }
        }
    }

    fn on_left_mouse_button_up(
        &mut self,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        engine: &mut Engine,
        mouse_pos: Vector2<f32>,
        _frame_size: Vector2<f32>,
        settings: &Settings,
    ) {
        let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
            return;
        };

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

        self.rotation_gizmo.reset_state(graph);

        if self.interacting {
            if let Some(selection) = editor_selection.as_graph() {
                if !selection.is_empty() {
                    self.interacting = false;
                    let current_rotation = selection.local_rotations(graph);
                    if current_rotation != self.initial_rotations {
                        let commands = CommandGroup::from(
                            selection
                                .nodes()
                                .iter()
                                .zip(self.initial_rotations.iter().zip(current_rotation.iter()))
                                .map(|(&node, (&old_rotation, &new_rotation))| {
                                    Command::new(RotateNodeCommand::new(
                                        node,
                                        old_rotation,
                                        new_rotation,
                                    ))
                                })
                                .collect::<Vec<Command>>(),
                        );
                        // Commit changes.
                        self.message_sender.do_command(commands);
                    }
                }
            }
        } else {
            let new_selection = game_scene
                .camera_controller
                .pick(
                    graph,
                    PickingOptions {
                        cursor_pos: mouse_pos,
                        editor_only: false,
                        filter: None,
                        ignore_back_faces: settings.selection.ignore_back_faces,
                        use_picking_loop: true,
                        method: Default::default(),
                        settings: &settings.selection,
                    },
                )
                .map(|result| {
                    if let (Some(selection), true) = (
                        editor_selection.as_graph(),
                        engine
                            .user_interfaces
                            .first_mut()
                            .keyboard_modifiers()
                            .control,
                    ) {
                        let mut selection = selection.clone();
                        selection.insert_or_exclude(result.node);
                        Selection::new(selection)
                    } else {
                        Selection::new(GraphSelection::single_or_empty(result.node))
                    }
                })
                .unwrap_or_else(|| Selection::new(GraphSelection::default()));

            if &new_selection != editor_selection {
                self.message_sender
                    .do_command(ChangeSelectionCommand::new(new_selection));
            }
        }
    }

    fn on_mouse_move(
        &mut self,
        mouse_offset: Vector2<f32>,
        mouse_position: Vector2<f32>,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        engine: &mut Engine,
        frame_size: Vector2<f32>,
        settings: &Settings,
    ) {
        let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
            return;
        };

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

        if let Some(selection) = editor_selection.as_graph() {
            if self.interacting {
                let rotation_delta = self.rotation_gizmo.calculate_rotation_delta(
                    game_scene.camera_controller.camera,
                    mouse_offset,
                    mouse_position,
                    graph,
                    frame_size,
                );
                for &node in selection.nodes().iter() {
                    let transform = graph[node].local_transform_mut();
                    let rotation = **transform.rotation();
                    let final_rotation = rotation * rotation_delta;
                    let (mut roll, mut pitch, mut yaw) = final_rotation.euler_angles();
                    if settings.rotate_mode_settings.angle_snapping {
                        pitch = round_to_step(
                            pitch,
                            settings.rotate_mode_settings.x_snap_step.to_radians(),
                        );
                        yaw = round_to_step(
                            yaw,
                            settings.rotate_mode_settings.y_snap_step.to_radians(),
                        );
                        roll = round_to_step(
                            roll,
                            settings.rotate_mode_settings.z_snap_step.to_radians(),
                        );
                    }
                    transform.set_rotation(UnitQuaternion::from_euler_angles(roll, pitch, yaw));
                }
            } else {
                let picked = game_scene
                    .camera_controller
                    .pick(
                        graph,
                        PickingOptions {
                            cursor_pos: mouse_position,
                            editor_only: true,
                            filter: None,
                            ignore_back_faces: false,
                            use_picking_loop: false,
                            method: Default::default(),
                            settings: &settings.selection,
                        },
                    )
                    .map(|r| r.node)
                    .unwrap_or_default();
                if picked.is_none() {
                    self.rotation_gizmo.reset_state(graph)
                } else {
                    self.rotation_gizmo.handle_pick(picked, graph);
                }
            }
        }
    }

    fn update(
        &mut self,
        editor_selection: &Selection,
        controller: &mut dyn SceneController,
        engine: &mut Engine,
        settings: &Settings,
    ) {
        let game_scene = some_or_return!(controller.downcast_mut::<GameScene>());
        self.rotation_gizmo.sync_with_selection(
            &mut engine.scenes[game_scene.scene].graph,
            game_scene.camera_controller.camera,
            settings,
            editor_selection,
        );
    }

    fn deactivate(&mut self, controller: &dyn SceneController, engine: &mut Engine) {
        let Some(game_scene) = controller.downcast_ref::<GameScene>() else {
            return;
        };

        let graph = &mut engine.scenes[game_scene.scene].graph;
        self.rotation_gizmo.set_visible(graph, false);
    }

    fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle<Button> {
        let rotate_mode_tooltip =
            "Rotate Object(s) - Shortcut: [3]\n\nRotation interaction mode allows you to rotate selected \
        objects. Keep in mind that rotation always works in local coordinates!\n\n\
        This also allows you to select an object or add an object to current selection using Ctrl+Click";

        make_interaction_mode_button(
            ctx,
            include_bytes!("../../resources/rotate_arrow.png"),
            rotate_mode_tooltip,
            selected,
        )
    }

    fn uuid(&self) -> Uuid {
        Self::type_uuid()
    }
}