diff --git a/editor/src/plugins/collider.rs b/editor/src/plugins/collider.rs deleted file mode 100644 index a63d3c189..000000000 --- a/editor/src/plugins/collider.rs +++ /dev/null @@ -1,1165 +0,0 @@ -//! Collider shape editing plugin. - -use crate::{ - camera::PickingOptions, - command::SetPropertyCommand, - fyrox::{ - asset::untyped::ResourceKind, - core::{ - algebra::{Vector2, Vector3}, - color::Color, - math::plane::Plane, - pool::Handle, - reflect::Reflect, - type_traits::prelude::*, - Uuid, - }, - engine::Engine, - graph::{BaseSceneGraph, SceneGraph}, - gui::{BuildContext, UiNode}, - material::{ - shader::{ShaderResource, ShaderResourceExtension}, - Material, MaterialResource, - }, - scene::{ - base::BaseBuilder, collider::Collider, collider::ColliderShape, node::Node, - sprite::SpriteBuilder, transform::TransformBuilder, Scene, - }, - }, - interaction::{ - gizmo::move_gizmo::MoveGizmo, make_interaction_mode_button, plane::PlaneKind, - InteractionMode, - }, - load_texture, - message::MessageSender, - plugin::EditorPlugin, - scene::{commands::GameSceneContext, controller::SceneController, GameScene, Selection}, - settings::Settings, - Editor, Message, -}; - -enum ShapeGizmo { - NonEditable, - Cuboid { - pos_x_handle: Handle, - pos_y_handle: Handle, - pos_z_handle: Handle, - neg_x_handle: Handle, - neg_y_handle: Handle, - neg_z_handle: Handle, - }, - Ball { - radius_handle: Handle, - }, - Capsule { - radius_handle: Handle, - begin_handle: Handle, - end_handle: Handle, - }, - Cylinder { - radius_handle: Handle, - half_height_handle: Handle, - }, - Cone { - radius_handle: Handle, - half_height_handle: Handle, - }, - Segment { - begin_handle: Handle, - end_handle: Handle, - }, - Triangle { - a_handle: Handle, - b_handle: Handle, - c_handle: Handle, - }, -} - -lazy_static! { - static ref GIZMO_SHADER: ShaderResource = { - ShaderResource::from_str( - include_str!("../../resources/shaders/sprite_gizmo.shader",), - Default::default(), - ) - .unwrap() - }; -} - -fn make_handle( - scene: &mut Scene, - position: Vector3, - root: Handle, - visible: bool, -) -> Handle { - let mut material = Material::from_shader(GIZMO_SHADER.clone(), None); - - material - .set_texture( - &"diffuseTexture".into(), - load_texture(include_bytes!("../../resources/circle.png")), - ) - .unwrap(); - - let handle = SpriteBuilder::new( - BaseBuilder::new() - .with_local_transform( - TransformBuilder::new() - .with_local_position(position) - .build(), - ) - .with_visibility(visible), - ) - .with_material(MaterialResource::new_ok(ResourceKind::Embedded, material)) - .with_size(0.05) - .with_color(Color::MAROON) - .build(&mut scene.graph); - - scene.graph.link_nodes(handle, root); - - handle -} - -impl ShapeGizmo { - fn create( - shape: ColliderShape, - center: Vector3, - side: Vector3, - up: Vector3, - look: Vector3, - scene: &mut Scene, - root: Handle, - visible: bool, - ) -> Self { - match shape { - ColliderShape::Ball(ball) => Self::Ball { - radius_handle: make_handle(scene, center + side.scale(ball.radius), root, visible), - }, - ColliderShape::Cylinder(cylinder) => Self::Cylinder { - radius_handle: make_handle( - scene, - center + side.scale(cylinder.radius), - root, - visible, - ), - half_height_handle: make_handle( - scene, - center + up.scale(cylinder.half_height), - root, - visible, - ), - }, - ColliderShape::Cone(cone) => Self::Cone { - radius_handle: make_handle(scene, center + side.scale(cone.radius), root, visible), - half_height_handle: make_handle( - scene, - center + up.scale(cone.half_height), - root, - visible, - ), - }, - ColliderShape::Cuboid(cuboid) => Self::Cuboid { - pos_x_handle: make_handle( - scene, - center + side.scale(cuboid.half_extents.x), - root, - visible, - ), - pos_y_handle: make_handle( - scene, - center + up.scale(cuboid.half_extents.y), - root, - visible, - ), - pos_z_handle: make_handle( - scene, - center + look.scale(cuboid.half_extents.z), - root, - visible, - ), - neg_x_handle: make_handle( - scene, - center - side.scale(cuboid.half_extents.x), - root, - visible, - ), - neg_y_handle: make_handle( - scene, - center - up.scale(cuboid.half_extents.y), - root, - visible, - ), - neg_z_handle: make_handle( - scene, - center - look.scale(cuboid.half_extents.z), - root, - visible, - ), - }, - ColliderShape::Capsule(capsule) => Self::Capsule { - radius_handle: make_handle( - scene, - center + side.scale(capsule.radius), - root, - visible, - ), - begin_handle: make_handle(scene, center + capsule.begin, root, visible), - end_handle: make_handle(scene, center + capsule.end, root, visible), - }, - ColliderShape::Segment(segment) => Self::Segment { - begin_handle: make_handle(scene, center + segment.begin, root, visible), - end_handle: make_handle(scene, center + segment.end, root, visible), - }, - ColliderShape::Triangle(triangle) => Self::Triangle { - a_handle: make_handle(scene, center + triangle.a, root, visible), - b_handle: make_handle(scene, center + triangle.b, root, visible), - c_handle: make_handle(scene, center + triangle.c, root, visible), - }, - ColliderShape::Polyhedron(_) - | ColliderShape::Heightfield(_) - | ColliderShape::Trimesh(_) => Self::NonEditable, - } - } - - fn for_each_handle)>(&self, mut func: F) { - match self { - Self::NonEditable => {} - Self::Cuboid { - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - } => { - for handle in [ - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - ] { - func(*handle) - } - } - Self::Ball { radius_handle } => func(*radius_handle), - Self::Capsule { - radius_handle, - begin_handle, - end_handle, - } => { - for handle in [radius_handle, begin_handle, end_handle] { - func(*handle) - } - } - Self::Cylinder { - radius_handle, - half_height_handle, - } - | Self::Cone { - radius_handle, - half_height_handle, - } => { - for handle in [radius_handle, half_height_handle] { - func(*handle) - } - } - Self::Segment { - begin_handle, - end_handle, - } => { - for handle in [begin_handle, end_handle] { - func(*handle) - } - } - Self::Triangle { - a_handle, - b_handle, - c_handle, - } => { - for handle in [a_handle, b_handle, c_handle] { - func(*handle) - } - } - } - } - - fn handle_major_axis(&self, handle: Handle) -> Option> { - match self { - ShapeGizmo::NonEditable => (), - ShapeGizmo::Cuboid { - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - } => { - if handle == *pos_x_handle { - return Some(Vector3::x()); - } else if handle == *pos_y_handle { - return Some(Vector3::y()); - } else if handle == *pos_z_handle { - return Some(Vector3::z()); - } else if handle == *neg_x_handle { - return Some(-Vector3::x()); - } else if handle == *neg_y_handle { - return Some(-Vector3::y()); - } else if handle == *neg_z_handle { - return Some(-Vector3::z()); - } - } - ShapeGizmo::Ball { radius_handle } => { - if handle == *radius_handle { - return Some(Vector3::x()); - } - } - ShapeGizmo::Capsule { radius_handle, .. } => { - if handle == *radius_handle { - return Some(Vector3::x()); - } - } - ShapeGizmo::Cylinder { - radius_handle, - half_height_handle, - } - | ShapeGizmo::Cone { - radius_handle, - half_height_handle, - } => { - if handle == *radius_handle { - return Some(Vector3::x()); - } else if handle == *half_height_handle { - return Some(Vector3::y()); - } - } - ShapeGizmo::Segment { .. } | ShapeGizmo::Triangle { .. } => { - // No sensible axis, because the value is a vector. - } - } - - None - } - - fn try_sync_to_shape( - &self, - shape: ColliderShape, - center: Vector3, - side: Vector3, - up: Vector3, - look: Vector3, - scene: &mut Scene, - ) -> bool { - let mut set_position = |handle: Handle, position: Vector3| { - scene.graph[handle] - .local_transform_mut() - .set_position(position); - }; - - match (self, shape) { - (Self::Ball { radius_handle }, ColliderShape::Ball(ball_shape)) => { - set_position(*radius_handle, center + side.scale(ball_shape.radius)); - true - } - ( - Self::Cylinder { - radius_handle, - half_height_handle, - }, - ColliderShape::Cylinder(cylinder), - ) => { - set_position(*radius_handle, center + side.scale(cylinder.radius)); - set_position(*half_height_handle, center + up.scale(cylinder.half_height)); - true - } - ( - Self::Cone { - radius_handle, - half_height_handle, - }, - ColliderShape::Cone(cone), - ) => { - set_position(*radius_handle, center + side.scale(cone.radius)); - set_position(*half_height_handle, center + up.scale(cone.half_height)); - true - } - ( - Self::Cuboid { - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - }, - ColliderShape::Cuboid(cuboid), - ) => { - set_position(*pos_x_handle, center + side.scale(cuboid.half_extents.x)); - set_position(*pos_y_handle, center + up.scale(cuboid.half_extents.y)); - set_position(*pos_z_handle, center + look.scale(cuboid.half_extents.z)); - set_position(*neg_x_handle, center - side.scale(cuboid.half_extents.x)); - set_position(*neg_y_handle, center - up.scale(cuboid.half_extents.y)); - set_position(*neg_z_handle, center - look.scale(cuboid.half_extents.z)); - true - } - ( - Self::Capsule { - radius_handle, - begin_handle, - end_handle, - }, - ColliderShape::Capsule(capsule_shape), - ) => { - set_position(*radius_handle, center + side.scale(capsule_shape.radius)); - set_position(*begin_handle, center + capsule_shape.begin); - set_position(*end_handle, center + capsule_shape.end); - true - } - ( - Self::Segment { - begin_handle, - end_handle, - }, - ColliderShape::Segment(segment), - ) => { - set_position(*begin_handle, center + segment.begin); - set_position(*end_handle, center + segment.end); - true - } - ( - Self::Triangle { - a_handle, - b_handle, - c_handle, - }, - ColliderShape::Triangle(triangle), - ) => { - set_position(*a_handle, center + triangle.a); - set_position(*b_handle, center + triangle.b); - set_position(*c_handle, center + triangle.c); - true - } - _ => false, - } - } - - fn value_by_handle( - &self, - handle: Handle, - collider: &Collider, - ) -> Option { - match (self, collider.shape()) { - (Self::Ball { radius_handle }, ColliderShape::Ball(ball_shape)) => { - if handle == *radius_handle { - return Some(ShapeHandleValue::Scalar(ball_shape.radius)); - } - } - ( - Self::Cylinder { - radius_handle, - half_height_handle, - }, - ColliderShape::Cylinder(cylinder), - ) => { - if handle == *radius_handle { - return Some(ShapeHandleValue::Scalar(cylinder.radius)); - } else if handle == *half_height_handle { - return Some(ShapeHandleValue::Scalar(cylinder.half_height)); - } - } - ( - Self::Cone { - radius_handle, - half_height_handle, - }, - ColliderShape::Cone(cone), - ) => { - if handle == *radius_handle { - return Some(ShapeHandleValue::Scalar(cone.radius)); - } else if handle == *half_height_handle { - return Some(ShapeHandleValue::Scalar(cone.half_height)); - } - } - ( - Self::Cuboid { - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - }, - ColliderShape::Cuboid(cuboid), - ) => { - if handle == *pos_x_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.x)); - } else if handle == *pos_y_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.y)); - } else if handle == *pos_z_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.z)); - } else if handle == *neg_x_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.x)); - } else if handle == *neg_y_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.y)); - } else if handle == *neg_z_handle { - return Some(ShapeHandleValue::Scalar(cuboid.half_extents.z)); - } - } - ( - Self::Capsule { - radius_handle, - begin_handle, - end_handle, - }, - ColliderShape::Capsule(capsule_shape), - ) => { - if handle == *radius_handle { - return Some(ShapeHandleValue::Scalar(capsule_shape.radius)); - } else if handle == *begin_handle { - return Some(ShapeHandleValue::Vector(capsule_shape.begin)); - } else if handle == *end_handle { - return Some(ShapeHandleValue::Vector(capsule_shape.end)); - } - } - ( - Self::Segment { - begin_handle, - end_handle, - }, - ColliderShape::Segment(segment), - ) => { - if handle == *begin_handle { - return Some(ShapeHandleValue::Vector(segment.begin)); - } else if handle == *end_handle { - return Some(ShapeHandleValue::Vector(segment.end)); - } - } - ( - Self::Triangle { - a_handle, - b_handle, - c_handle, - }, - ColliderShape::Triangle(triangle), - ) => { - if handle == *a_handle { - return Some(ShapeHandleValue::Vector(triangle.a)); - } else if handle == *b_handle { - return Some(ShapeHandleValue::Vector(triangle.b)); - } else if handle == *c_handle { - return Some(ShapeHandleValue::Vector(triangle.c)); - } - } - _ => (), - } - - None - } - - fn set_value_by_handle( - &self, - handle: Handle, - value: ShapeHandleValue, - collider: &mut Collider, - initial_collider_local_position: Vector3, - ) -> Option { - match (self, collider.shape_mut()) { - (Self::Ball { radius_handle }, ColliderShape::Ball(ball_shape)) => { - if handle == *radius_handle { - ball_shape.radius = value.into_scalar().max(0.0); - } - } - ( - Self::Cylinder { - radius_handle, - half_height_handle, - }, - ColliderShape::Cylinder(cylinder), - ) => { - if handle == *radius_handle { - cylinder.radius = value.into_scalar().max(0.0); - } else if handle == *half_height_handle { - cylinder.half_height = value.into_scalar().max(0.0); - } - } - ( - Self::Cone { - radius_handle, - half_height_handle, - }, - ColliderShape::Cone(cone), - ) => { - if handle == *radius_handle { - cone.radius = value.into_scalar().max(0.0); - } else if handle == *half_height_handle { - cone.half_height = value.into_scalar().max(0.0); - } - } - ( - Self::Cuboid { - pos_x_handle, - pos_y_handle, - pos_z_handle, - neg_x_handle, - neg_y_handle, - neg_z_handle, - }, - ColliderShape::Cuboid(cuboid), - ) => { - if handle == *pos_x_handle { - cuboid.half_extents.x = value.into_scalar().max(0.0); - } else if handle == *pos_y_handle { - cuboid.half_extents.y = value.into_scalar().max(0.0); - } else if handle == *pos_z_handle { - cuboid.half_extents.z = value.into_scalar().max(0.0); - } else if handle == *neg_x_handle { - cuboid.half_extents.x = value.into_scalar().max(0.0); - let transform = collider.local_transform_mut(); - transform.set_position(Vector3::new( - initial_collider_local_position.x - value.into_scalar() / 2.0, - initial_collider_local_position.y, - initial_collider_local_position.z, - )); - } else if handle == *neg_y_handle { - cuboid.half_extents.y = value.into_scalar().max(0.0); - let transform = collider.local_transform_mut(); - transform.set_position(Vector3::new( - initial_collider_local_position.x, - initial_collider_local_position.y - value.into_scalar() / 2.0, - initial_collider_local_position.z, - )); - } else if handle == *neg_z_handle { - cuboid.half_extents.z = value.into_scalar().max(0.0); - let transform = collider.local_transform_mut(); - transform.set_position(Vector3::new( - initial_collider_local_position.x, - initial_collider_local_position.y, - initial_collider_local_position.z - value.into_scalar() / 2.0, - )); - } - } - ( - Self::Capsule { - radius_handle, - begin_handle, - end_handle, - }, - ColliderShape::Capsule(capsule), - ) => { - if handle == *radius_handle { - capsule.radius = value.into_scalar().max(0.0); - } else if handle == *begin_handle { - capsule.begin = value.into_vector(); - } else if handle == *end_handle { - capsule.end = value.into_vector(); - } - } - ( - Self::Segment { - begin_handle, - end_handle, - }, - ColliderShape::Segment(segment), - ) => { - if handle == *begin_handle { - segment.begin = value.into_vector(); - } else if handle == *end_handle { - segment.end = value.into_vector(); - } - } - ( - Self::Triangle { - a_handle, - b_handle, - c_handle, - }, - ColliderShape::Triangle(triangle), - ) => { - if handle == *a_handle { - triangle.a = value.into_vector(); - } else if handle == *b_handle { - triangle.b = value.into_vector(); - } else if handle == *c_handle { - triangle.c = value.into_vector(); - } - } - _ => (), - } - - None - } - - fn is_vector_handle(&self, handle: Handle) -> bool { - match self { - ShapeGizmo::NonEditable - | ShapeGizmo::Cuboid { .. } - | ShapeGizmo::Ball { .. } - | ShapeGizmo::Cylinder { .. } - | ShapeGizmo::Cone { .. } => false, - ShapeGizmo::Capsule { - begin_handle, - end_handle, - .. - } => handle == *begin_handle || handle == *end_handle, - ShapeGizmo::Segment { - begin_handle, - end_handle, - } => handle == *begin_handle || handle == *end_handle, - ShapeGizmo::Triangle { - a_handle, - b_handle, - c_handle, - } => handle == *a_handle || handle == *b_handle || handle == *c_handle, - } - } - - fn reset_handles(&self, scene: &mut Scene) { - self.for_each_handle(|handle| { - scene.graph[handle].as_sprite_mut().set_color(Color::MAROON); - }); - } - - fn destroy(self, scene: &mut Scene) { - self.for_each_handle(|handle| scene.graph.remove_node(handle)); - } - - fn has_handle(&self, handle: Handle) -> bool { - let mut has_handle = false; - self.for_each_handle(|other_handle| { - if other_handle == handle { - has_handle = true - } - }); - has_handle - } - - fn set_visibility(&self, scene: &mut Scene, visibility: bool) { - self.for_each_handle(|handle| { - scene.graph[handle].set_visibility(visibility); - }) - } -} - -#[derive(Copy, Clone)] -enum ShapeHandleValue { - Scalar(f32), - Vector(Vector3), -} - -impl ShapeHandleValue { - fn into_scalar(self) -> f32 { - match self { - ShapeHandleValue::Scalar(scalar) => scalar, - ShapeHandleValue::Vector(_) => { - unreachable!() - } - } - } - - fn into_vector(self) -> Vector3 { - match self { - ShapeHandleValue::Scalar(_) => unreachable!(), - ShapeHandleValue::Vector(vector) => vector, - } - } -} - -struct DragContext { - handle: Handle, - initial_handle_position: Vector3, - plane: Plane, - initial_value: ShapeHandleValue, - initial_collider_local_position: Vector3, - handle_major_axis: Option>, - plane_kind: Option, - initial_shape: ColliderShape, -} - -#[derive(TypeUuidProvider)] -#[type_uuid(id = "a012dd4c-ce6d-4e7e-8879-fd8eddaa9677")] -pub struct ColliderShapeInteractionMode { - collider: Handle, - shape_gizmo: ShapeGizmo, - move_gizmo: MoveGizmo, - drag_context: Option, - selected_handle: Handle, - message_sender: MessageSender, -} - -impl ColliderShapeInteractionMode { - fn set_visibility( - &mut self, - controller: &dyn SceneController, - engine: &mut Engine, - visibility: bool, - ) { - let Some(game_scene) = controller.downcast_ref::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - self.shape_gizmo.set_visibility(scene, visibility); - } -} - -impl InteractionMode for ColliderShapeInteractionMode { - fn on_left_mouse_button_down( - &mut self, - _editor_selection: &Selection, - controller: &mut dyn SceneController, - engine: &mut Engine, - mouse_position: Vector2, - _frame_size: Vector2, - _settings: &Settings, - ) { - let Some(game_scene) = controller.downcast_mut::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - if let Some(result) = game_scene.camera_controller.pick( - &scene.graph, - PickingOptions { - cursor_pos: mouse_position, - editor_only: true, - ..Default::default() - }, - ) { - let initial_position = scene.graph[result.node].global_position(); - let camera_view_dir = scene.graph[game_scene.camera_controller.camera] - .look_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - let plane = Plane::from_normal_and_point(&-camera_view_dir, &initial_position) - .unwrap_or_default(); - let collider = scene.graph[self.collider].as_collider(); - let initial_collider_local_position = **collider.local_transform().position(); - let initial_shape = collider.shape().clone(); - - if let Some(handle_value) = self.shape_gizmo.value_by_handle(result.node, collider) { - self.selected_handle = result.node; - - self.drag_context = Some(DragContext { - handle: result.node, - initial_handle_position: initial_position, - plane, - handle_major_axis: self.shape_gizmo.handle_major_axis(result.node), - initial_value: handle_value, - initial_collider_local_position, - plane_kind: None, - initial_shape, - }) - } else if let Some(plane_kind) = - self.move_gizmo.handle_pick(result.node, &mut scene.graph) - { - let collider = scene.graph[self.collider].as_collider(); - if let Some(handle_value) = self - .shape_gizmo - .value_by_handle(self.selected_handle, collider) - { - self.drag_context = Some(DragContext { - handle: self.selected_handle, - initial_handle_position: initial_position, - plane, - handle_major_axis: None, - initial_value: handle_value, - initial_collider_local_position, - plane_kind: Some(plane_kind), - initial_shape, - }) - } - } - } - } - - fn on_left_mouse_button_up( - &mut self, - _editor_selection: &Selection, - controller: &mut dyn SceneController, - engine: &mut Engine, - _mouse_pos: Vector2, - _frame_size: Vector2, - _settings: &Settings, - ) { - let Some(game_scene) = controller.downcast_mut::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - if let Some(drag_context) = self.drag_context.take() { - let collider = self.collider; - - let value = std::mem::replace( - scene.graph[collider].as_collider_mut().shape_mut(), - drag_context.initial_shape, - ); - - let command = SetPropertyCommand::new( - "shape".into(), - Box::new(value) as Box, - move |ctx| { - ctx.get_mut::() - .scene - .graph - .node_mut(collider) - }, - ); - self.message_sender.do_command(command); - } - } - - fn on_mouse_move( - &mut self, - mouse_offset: Vector2, - mouse_position: Vector2, - _editor_selection: &Selection, - controller: &mut dyn SceneController, - engine: &mut Engine, - frame_size: Vector2, - _settings: &Settings, - ) { - let Some(game_scene) = controller.downcast_mut::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - self.shape_gizmo.reset_handles(scene); - self.move_gizmo.reset_state(&mut scene.graph); - - if let Some(result) = game_scene.camera_controller.pick( - &scene.graph, - PickingOptions { - cursor_pos: mouse_position, - editor_only: true, - ..Default::default() - }, - ) { - if self.shape_gizmo.has_handle(result.node) { - scene.graph[result.node] - .as_sprite_mut() - .set_color(Color::RED); - } - - self.move_gizmo.handle_pick(result.node, &mut scene.graph); - } - - if let Some(drag_context) = self.drag_context.as_ref() { - match drag_context.initial_value { - ShapeHandleValue::Scalar(initial_value) => { - let camera = scene.graph[game_scene.camera_controller.camera].as_camera(); - let ray = camera.make_ray(mouse_position, frame_size); - if let Some(intersection) = ray.plane_intersection_point(&drag_context.plane) { - let inv_transform = scene.graph[self.collider] - .global_transform() - .try_inverse() - .unwrap_or_default(); - let local_space_drag_dir = inv_transform.transform_vector( - &(intersection - drag_context.initial_handle_position), - ); - let sign = local_space_drag_dir - .dot(&drag_context.handle_major_axis.unwrap_or_default()) - .signum(); - let delta = sign - * drag_context - .initial_handle_position - .metric_distance(&intersection); - - self.shape_gizmo.set_value_by_handle( - drag_context.handle, - ShapeHandleValue::Scalar(initial_value + delta), - scene.graph[self.collider].as_collider_mut(), - drag_context.initial_collider_local_position, - ); - } - } - ShapeHandleValue::Vector(_) => { - if let Some(plane_kind) = drag_context.plane_kind { - let value = self - .shape_gizmo - .value_by_handle( - drag_context.handle, - scene.graph[self.collider].as_collider(), - ) - .unwrap() - .into_vector(); - - let offset = self.move_gizmo.calculate_offset( - &scene.graph, - game_scene.camera_controller.camera, - mouse_offset, - mouse_position, - frame_size, - plane_kind, - ); - - self.shape_gizmo.set_value_by_handle( - drag_context.handle, - ShapeHandleValue::Vector(value + offset), - scene.graph[self.collider].as_collider_mut(), - drag_context.initial_collider_local_position, - ); - } - } - } - } - } - - fn update( - &mut self, - _editor_selection: &Selection, - controller: &mut dyn SceneController, - engine: &mut Engine, - _settings: &Settings, - ) { - let Some(game_scene) = controller.downcast_mut::() else { - return; - }; - - let scene = &mut engine.scenes[game_scene.scene]; - - let Some(collider) = scene.graph.try_get_of_type::(self.collider) else { - return; - }; - - let center = collider.global_position(); - let side = collider - .side_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - let up = collider - .up_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - let look = collider - .look_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - - let shape = collider.shape().clone(); - if !self - .shape_gizmo - .try_sync_to_shape(shape.clone(), center, side, up, look, scene) - { - let new_gizmo = ShapeGizmo::create( - shape, - center, - side, - up, - look, - scene, - game_scene.editor_objects_root, - true, - ); - - let old_gizmo = std::mem::replace(&mut self.shape_gizmo, new_gizmo); - - old_gizmo.destroy(scene); - } - - self.move_gizmo.set_visible( - &mut scene.graph, - self.shape_gizmo.is_vector_handle(self.selected_handle), - ); - if let Some(selected_handle) = scene.graph.try_get(self.selected_handle) { - let position = selected_handle.global_position(); - self.move_gizmo.set_position(scene, position) - } - } - - fn activate(&mut self, controller: &dyn SceneController, engine: &mut Engine) { - self.set_visibility(controller, engine, true) - } - - fn deactivate(&mut self, controller: &dyn SceneController, engine: &mut Engine) { - self.set_visibility(controller, engine, false) - } - - fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle { - make_interaction_mode_button( - ctx, - include_bytes!("../../resources/triangle.png"), - "Edit Collider Shape", - selected, - ) - } - - fn uuid(&self) -> Uuid { - Self::type_uuid() - } -} - -#[derive(Default)] -pub struct ColliderShapePlugin {} - -impl EditorPlugin for ColliderShapePlugin { - fn on_message(&mut self, message: &Message, editor: &mut Editor) { - let Some(entry) = editor.scenes.current_scene_entry_mut() else { - return; - }; - - let Some(selection) = entry.selection.as_graph() else { - return; - }; - - let Some(game_scene) = entry.controller.downcast_mut::() else { - return; - }; - - let scene = &mut editor.engine.scenes[game_scene.scene]; - - if let Message::SelectionChanged { .. } = message { - if let Some(mode) = entry - .interaction_modes - .remove_typed::() - { - mode.shape_gizmo.destroy(scene); - } - - for node_handle in selection.nodes().iter() { - if let Some(collider) = scene.graph.try_get_of_type::(*node_handle) { - let center = collider.global_position(); - let side = collider - .side_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - let up = collider - .up_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - let look = collider - .look_vector() - .try_normalize(f32::EPSILON) - .unwrap_or_default(); - - let shape_gizmo = ShapeGizmo::create( - collider.shape().clone(), - center, - side, - up, - look, - scene, - game_scene.editor_objects_root, - false, - ); - - let move_gizmo = MoveGizmo::new(game_scene, &mut editor.engine); - - entry.interaction_modes.add(ColliderShapeInteractionMode { - collider: *node_handle, - shape_gizmo, - move_gizmo, - drag_context: None, - selected_handle: Default::default(), - message_sender: editor.message_sender.clone(), - }); - - break; - } - } - } - } -} diff --git a/editor/src/plugins/collider/ball.rs b/editor/src/plugins/collider/ball.rs new file mode 100644 index 000000000..45b0cf6e8 --- /dev/null +++ b/editor/src/plugins/collider/ball.rs @@ -0,0 +1,103 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{BallShape, ColliderShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct BallShapeGizmo { + radius_handle: Handle, +} + +impl BallShapeGizmo { + pub fn new( + ball: &BallShape, + center: Vector3, + side: Vector3, + root: Handle, + visible: bool, + scene: &mut Scene, + ) -> Self { + Self { + radius_handle: make_handle(scene, center + side.scale(ball.radius), root, visible), + } + } +} + +impl ShapeGizmoTrait for BallShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + func(self.radius_handle) + } + + fn handle_major_axis(&self, handle: Handle) -> Option> { + if handle == self.radius_handle { + Some(Vector3::x()) + } else { + None + } + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + _up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Ball(ball)) = try_get_collider_shape(collider, scene) else { + return false; + }; + + set_node_position(self.radius_handle, center + side.scale(ball.radius), scene); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Ball(ball)) = try_get_collider_shape(collider, scene) else { + return None; + }; + + if handle == self.radius_handle { + Some(ShapeHandleValue::Scalar(ball.radius)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Ball(ball)) = try_get_collider_shape_mut(collider, scene) else { + return; + }; + + if handle == self.radius_handle { + ball.radius = value.into_scalar(); + } + } + + fn is_vector_handle(&self, _handle: Handle) -> bool { + false + } +} diff --git a/editor/src/plugins/collider/capsule.rs b/editor/src/plugins/collider/capsule.rs new file mode 100644 index 000000000..414fdcf78 --- /dev/null +++ b/editor/src/plugins/collider/capsule.rs @@ -0,0 +1,124 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{CapsuleShape, ColliderShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct CapsuleShapeGizmo { + radius_handle: Handle, + begin_handle: Handle, + end_handle: Handle, +} + +impl CapsuleShapeGizmo { + pub fn new( + capsule: &CapsuleShape, + center: Vector3, + side: Vector3, + visible: bool, + root: Handle, + scene: &mut Scene, + ) -> Self { + Self { + radius_handle: make_handle(scene, center + side.scale(capsule.radius), root, visible), + begin_handle: make_handle(scene, center + capsule.begin, root, visible), + end_handle: make_handle(scene, center + capsule.end, root, visible), + } + } +} + +impl ShapeGizmoTrait for CapsuleShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [self.radius_handle, self.begin_handle, self.end_handle] { + func(handle) + } + } + + fn handle_major_axis(&self, handle: Handle) -> Option> { + if handle == self.radius_handle { + Some(Vector3::x()) + } else { + None + } + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + _up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape(collider, scene) else { + return false; + }; + + set_node_position( + self.radius_handle, + center + side.scale(capsule.radius), + scene, + ); + set_node_position(self.begin_handle, center + capsule.begin, scene); + set_node_position(self.end_handle, center + capsule.end, scene); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape(collider, scene) else { + return None; + }; + + if handle == self.radius_handle { + Some(ShapeHandleValue::Scalar(capsule.radius)) + } else if handle == self.begin_handle { + Some(ShapeHandleValue::Vector(capsule.begin)) + } else if handle == self.end_handle { + Some(ShapeHandleValue::Vector(capsule.end)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Capsule(capsule)) = try_get_collider_shape_mut(collider, scene) + else { + return; + }; + + if handle == self.radius_handle { + capsule.radius = value.into_scalar().max(0.0); + } else if handle == self.begin_handle { + capsule.begin = value.into_vector(); + } else if handle == self.end_handle { + capsule.end = value.into_vector(); + } + } + + fn is_vector_handle(&self, handle: Handle) -> bool { + handle == self.begin_handle || handle == self.end_handle + } +} diff --git a/editor/src/plugins/collider/cone.rs b/editor/src/plugins/collider/cone.rs new file mode 100644 index 000000000..2dd68c381 --- /dev/null +++ b/editor/src/plugins/collider/cone.rs @@ -0,0 +1,124 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{ColliderShape, ConeShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct ConeShapeGizmo { + radius_handle: Handle, + half_height_handle: Handle, +} + +impl ConeShapeGizmo { + pub fn new( + cone: &ConeShape, + center: Vector3, + side: Vector3, + up: Vector3, + visible: bool, + root: Handle, + scene: &mut Scene, + ) -> Self { + Self { + radius_handle: make_handle(scene, center + side.scale(cone.radius), root, visible), + half_height_handle: make_handle( + scene, + center + up.scale(cone.half_height), + root, + visible, + ), + } + } +} + +impl ShapeGizmoTrait for ConeShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [self.radius_handle, self.half_height_handle] { + func(handle) + } + } + + fn handle_major_axis(&self, handle: Handle) -> Option> { + if handle == self.radius_handle { + Some(Vector3::x()) + } else if handle == self.half_height_handle { + Some(Vector3::y()) + } else { + None + } + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Cone(cone)) = try_get_collider_shape(collider, scene) else { + return false; + }; + + set_node_position(self.radius_handle, center + side.scale(cone.radius), scene); + set_node_position( + self.half_height_handle, + center + up.scale(cone.half_height), + scene, + ); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Cone(cone)) = try_get_collider_shape(collider, scene) else { + return None; + }; + + if handle == self.radius_handle { + Some(ShapeHandleValue::Scalar(cone.radius)) + } else if handle == self.half_height_handle { + Some(ShapeHandleValue::Scalar(cone.half_height)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Cone(cone)) = try_get_collider_shape_mut(collider, scene) else { + return; + }; + + if handle == self.radius_handle { + cone.radius = value.into_scalar().max(0.0); + } else if handle == self.half_height_handle { + cone.half_height = value.into_scalar().max(0.0); + } + } + + fn is_vector_handle(&self, _handle: Handle) -> bool { + false + } +} diff --git a/editor/src/plugins/collider/cuboid.rs b/editor/src/plugins/collider/cuboid.rs new file mode 100644 index 000000000..4fec65679 --- /dev/null +++ b/editor/src/plugins/collider/cuboid.rs @@ -0,0 +1,235 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + graph::SceneGraph, + scene::{ + collider::{Collider, ColliderShape, CuboidShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct CuboidShapeGizmo { + pos_x_handle: Handle, + pos_y_handle: Handle, + pos_z_handle: Handle, + neg_x_handle: Handle, + neg_y_handle: Handle, + neg_z_handle: Handle, +} + +impl CuboidShapeGizmo { + pub fn new( + cuboid: &CuboidShape, + center: Vector3, + side: Vector3, + up: Vector3, + look: Vector3, + visible: bool, + root: Handle, + scene: &mut Scene, + ) -> Self { + Self { + pos_x_handle: make_handle( + scene, + center + side.scale(cuboid.half_extents.x), + root, + visible, + ), + pos_y_handle: make_handle( + scene, + center + up.scale(cuboid.half_extents.y), + root, + visible, + ), + pos_z_handle: make_handle( + scene, + center + look.scale(cuboid.half_extents.z), + root, + visible, + ), + neg_x_handle: make_handle( + scene, + center - side.scale(cuboid.half_extents.x), + root, + visible, + ), + neg_y_handle: make_handle( + scene, + center - up.scale(cuboid.half_extents.y), + root, + visible, + ), + neg_z_handle: make_handle( + scene, + center - look.scale(cuboid.half_extents.z), + root, + visible, + ), + } + } +} + +impl ShapeGizmoTrait for CuboidShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [ + self.pos_x_handle, + self.pos_y_handle, + self.pos_z_handle, + self.neg_x_handle, + self.neg_y_handle, + self.neg_z_handle, + ] { + func(handle) + } + } + + fn handle_major_axis(&self, handle: Handle) -> Option> { + if handle == self.pos_x_handle { + Some(Vector3::x()) + } else if handle == self.pos_y_handle { + Some(Vector3::y()) + } else if handle == self.pos_z_handle { + Some(Vector3::z()) + } else if handle == self.neg_x_handle { + Some(-Vector3::x()) + } else if handle == self.neg_y_handle { + Some(-Vector3::y()) + } else if handle == self.neg_z_handle { + Some(-Vector3::z()) + } else { + None + } + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + up: Vector3, + look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape(collider, scene) else { + return false; + }; + + set_node_position( + self.pos_x_handle, + center + side.scale(cuboid.half_extents.x), + scene, + ); + set_node_position( + self.pos_y_handle, + center + up.scale(cuboid.half_extents.y), + scene, + ); + set_node_position( + self.pos_z_handle, + center + look.scale(cuboid.half_extents.z), + scene, + ); + set_node_position( + self.neg_x_handle, + center - side.scale(cuboid.half_extents.x), + scene, + ); + set_node_position( + self.neg_y_handle, + center - up.scale(cuboid.half_extents.y), + scene, + ); + set_node_position( + self.neg_z_handle, + center - look.scale(cuboid.half_extents.z), + scene, + ); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Cuboid(cuboid)) = try_get_collider_shape(collider, scene) else { + return None; + }; + + if handle == self.pos_x_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.x)) + } else if handle == self.pos_y_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.y)) + } else if handle == self.pos_z_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.z)) + } else if handle == self.neg_x_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.x)) + } else if handle == self.neg_y_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.y)) + } else if handle == self.neg_z_handle { + Some(ShapeHandleValue::Scalar(cuboid.half_extents.z)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + initial_collider_local_position: Vector3, + ) { + let Some(collider) = scene.graph.try_get_mut_of_type::(collider) else { + return; + }; + + let ColliderShape::Cuboid(cuboid) = collider.shape_mut() else { + return; + }; + + if handle == self.pos_x_handle { + cuboid.half_extents.x = value.into_scalar().max(0.0); + } else if handle == self.pos_y_handle { + cuboid.half_extents.y = value.into_scalar().max(0.0); + } else if handle == self.pos_z_handle { + cuboid.half_extents.z = value.into_scalar().max(0.0); + } else if handle == self.neg_x_handle { + cuboid.half_extents.x = value.into_scalar().max(0.0); + let transform = collider.local_transform_mut(); + transform.set_position(Vector3::new( + initial_collider_local_position.x - value.into_scalar() / 2.0, + initial_collider_local_position.y, + initial_collider_local_position.z, + )); + } else if handle == self.neg_y_handle { + cuboid.half_extents.y = value.into_scalar().max(0.0); + let transform = collider.local_transform_mut(); + transform.set_position(Vector3::new( + initial_collider_local_position.x, + initial_collider_local_position.y - value.into_scalar() / 2.0, + initial_collider_local_position.z, + )); + } else if handle == self.neg_z_handle { + cuboid.half_extents.z = value.into_scalar().max(0.0); + let transform = collider.local_transform_mut(); + transform.set_position(Vector3::new( + initial_collider_local_position.x, + initial_collider_local_position.y, + initial_collider_local_position.z - value.into_scalar() / 2.0, + )); + } + } + + fn is_vector_handle(&self, _handle: Handle) -> bool { + false + } +} diff --git a/editor/src/plugins/collider/cylinder.rs b/editor/src/plugins/collider/cylinder.rs new file mode 100644 index 000000000..8b56553ed --- /dev/null +++ b/editor/src/plugins/collider/cylinder.rs @@ -0,0 +1,131 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{ColliderShape, CylinderShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct CylinderShapeGizmo { + radius_handle: Handle, + half_height_handle: Handle, +} + +impl CylinderShapeGizmo { + pub fn new( + cylinder: &CylinderShape, + center: Vector3, + side: Vector3, + up: Vector3, + visible: bool, + root: Handle, + scene: &mut Scene, + ) -> Self { + Self { + radius_handle: make_handle(scene, center + side.scale(cylinder.radius), root, visible), + half_height_handle: make_handle( + scene, + center + up.scale(cylinder.half_height), + root, + visible, + ), + } + } +} + +impl ShapeGizmoTrait for CylinderShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [self.radius_handle, self.half_height_handle] { + func(handle) + } + } + + fn handle_major_axis(&self, handle: Handle) -> Option> { + if handle == self.radius_handle { + Some(Vector3::x()) + } else if handle == self.half_height_handle { + Some(Vector3::y()) + } else { + None + } + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape(collider, scene) + else { + return false; + }; + + set_node_position( + self.radius_handle, + center + side.scale(cylinder.radius), + scene, + ); + set_node_position( + self.half_height_handle, + center + up.scale(cylinder.half_height), + scene, + ); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape(collider, scene) + else { + return None; + }; + + if handle == self.radius_handle { + Some(ShapeHandleValue::Scalar(cylinder.radius)) + } else if handle == self.half_height_handle { + Some(ShapeHandleValue::Scalar(cylinder.half_height)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Cylinder(cylinder)) = try_get_collider_shape_mut(collider, scene) + else { + return; + }; + + if handle == self.radius_handle { + cylinder.radius = value.into_scalar().max(0.0); + } else if handle == self.half_height_handle { + cylinder.half_height = value.into_scalar().max(0.0); + } + } + + fn is_vector_handle(&self, _handle: Handle) -> bool { + false + } +} diff --git a/editor/src/plugins/collider/dummy.rs b/editor/src/plugins/collider/dummy.rs new file mode 100644 index 000000000..e2b5d15e6 --- /dev/null +++ b/editor/src/plugins/collider/dummy.rs @@ -0,0 +1,52 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{node::Node, Scene}, + }, + plugins::collider::{ShapeGizmoTrait, ShapeHandleValue}, +}; + +pub struct DummyShapeGizmo; + +impl ShapeGizmoTrait for DummyShapeGizmo { + fn for_each_handle(&self, _func: &mut dyn FnMut(Handle)) {} + + fn handle_major_axis(&self, _handle: Handle) -> Option> { + None + } + + fn try_sync_to_collider( + &self, + _collider: Handle, + _center: Vector3, + _side: Vector3, + _up: Vector3, + _look: Vector3, + _scene: &mut Scene, + ) -> bool { + false + } + + fn value_by_handle( + &self, + _handle: Handle, + _collider: Handle, + _scene: &Scene, + ) -> Option { + None + } + + fn set_value_by_handle( + &self, + _handle: Handle, + _value: ShapeHandleValue, + _collider: Handle, + _scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + } + + fn is_vector_handle(&self, _handle: Handle) -> bool { + false + } +} diff --git a/editor/src/plugins/collider/mod.rs b/editor/src/plugins/collider/mod.rs new file mode 100644 index 000000000..1122624c1 --- /dev/null +++ b/editor/src/plugins/collider/mod.rs @@ -0,0 +1,647 @@ +//! Collider shape editing plugin. + +mod ball; +mod capsule; +mod cone; +mod cuboid; +mod cylinder; +mod dummy; +mod segment; +mod triangle; + +use crate::{ + camera::PickingOptions, + command::SetPropertyCommand, + fyrox::{ + asset::untyped::ResourceKind, + core::{ + algebra::{Vector2, Vector3}, + color::Color, + math::plane::Plane, + pool::Handle, + reflect::Reflect, + type_traits::prelude::*, + Uuid, + }, + engine::Engine, + graph::{BaseSceneGraph, SceneGraph}, + gui::{BuildContext, UiNode}, + material::{ + shader::{ShaderResource, ShaderResourceExtension}, + Material, MaterialResource, + }, + scene::{ + base::BaseBuilder, collider::Collider, collider::ColliderShape, node::Node, + sprite::SpriteBuilder, transform::TransformBuilder, Scene, + }, + }, + interaction::{ + gizmo::move_gizmo::MoveGizmo, make_interaction_mode_button, plane::PlaneKind, + InteractionMode, + }, + load_texture, + message::MessageSender, + plugin::EditorPlugin, + plugins::collider::{ + ball::BallShapeGizmo, capsule::CapsuleShapeGizmo, cone::ConeShapeGizmo, + cuboid::CuboidShapeGizmo, cylinder::CylinderShapeGizmo, dummy::DummyShapeGizmo, + segment::SegmentShapeGizmo, triangle::TriangleShapeGizmo, + }, + scene::{commands::GameSceneContext, controller::SceneController, GameScene, Selection}, + settings::Settings, + Editor, Message, +}; + +fn set_node_position(handle: Handle, position: Vector3, scene: &mut Scene) { + scene.graph[handle] + .local_transform_mut() + .set_position(position); +} + +fn try_get_collider_shape(collider: Handle, scene: &Scene) -> Option { + scene + .graph + .try_get_of_type::(collider) + .map(|c| c.shape().clone()) +} + +fn try_get_collider_shape_mut( + collider: Handle, + scene: &mut Scene, +) -> Option<&mut ColliderShape> { + scene + .graph + .try_get_mut_of_type::(collider) + .map(|c| c.shape_mut()) +} + +trait ShapeGizmoTrait { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)); + + fn handle_major_axis(&self, handle: Handle) -> Option>; + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + side: Vector3, + up: Vector3, + look: Vector3, + scene: &mut Scene, + ) -> bool; + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option; + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + initial_collider_local_position: Vector3, + ); + + fn is_vector_handle(&self, handle: Handle) -> bool; + + fn reset_handles(&self, scene: &mut Scene) { + self.for_each_handle(&mut |handle| { + scene.graph[handle].as_sprite_mut().set_color(Color::MAROON); + }); + } + + fn destroy(self: Box, scene: &mut Scene) { + self.for_each_handle(&mut |handle| scene.graph.remove_node(handle)); + } + + fn has_handle(&self, handle: Handle) -> bool { + let mut has_handle = false; + self.for_each_handle(&mut |other_handle| { + if other_handle == handle { + has_handle = true + } + }); + has_handle + } + + fn set_visibility(&self, scene: &mut Scene, visibility: bool) { + self.for_each_handle(&mut |handle| { + scene.graph[handle].set_visibility(visibility); + }) + } +} + +fn make_shape_gizmo( + collider: Handle, + center: Vector3, + side: Vector3, + up: Vector3, + look: Vector3, + scene: &mut Scene, + root: Handle, + visible: bool, +) -> Box { + if let Some(collider) = scene.graph.try_get_of_type::(collider) { + let shape = collider.shape().clone(); + match shape { + ColliderShape::Ball(ball) => Box::new(BallShapeGizmo::new( + &ball, center, side, root, visible, scene, + )), + ColliderShape::Cylinder(cylinder) => Box::new(CylinderShapeGizmo::new( + &cylinder, center, side, up, visible, root, scene, + )), + ColliderShape::Cone(cone) => Box::new(ConeShapeGizmo::new( + &cone, center, side, up, visible, root, scene, + )), + ColliderShape::Cuboid(cuboid) => Box::new(CuboidShapeGizmo::new( + &cuboid, center, side, up, look, visible, root, scene, + )), + ColliderShape::Capsule(capsule) => Box::new(CapsuleShapeGizmo::new( + &capsule, center, side, visible, root, scene, + )), + ColliderShape::Segment(segment) => Box::new(SegmentShapeGizmo::new( + &segment, center, root, visible, scene, + )), + ColliderShape::Triangle(triangle) => Box::new(TriangleShapeGizmo::new( + &triangle, center, root, visible, scene, + )), + ColliderShape::Trimesh(_) + | ColliderShape::Heightfield(_) + | ColliderShape::Polyhedron(_) => Box::new(DummyShapeGizmo), + } + } else { + Box::new(DummyShapeGizmo) + } +} + +lazy_static! { + static ref GIZMO_SHADER: ShaderResource = { + ShaderResource::from_str( + include_str!("../../../resources/shaders/sprite_gizmo.shader",), + Default::default(), + ) + .unwrap() + }; +} + +fn make_handle( + scene: &mut Scene, + position: Vector3, + root: Handle, + visible: bool, +) -> Handle { + let mut material = Material::from_shader(GIZMO_SHADER.clone(), None); + + material + .set_texture( + &"diffuseTexture".into(), + load_texture(include_bytes!("../../../resources/circle.png")), + ) + .unwrap(); + + let handle = SpriteBuilder::new( + BaseBuilder::new() + .with_local_transform( + TransformBuilder::new() + .with_local_position(position) + .build(), + ) + .with_visibility(visible), + ) + .with_material(MaterialResource::new_ok(ResourceKind::Embedded, material)) + .with_size(0.05) + .with_color(Color::MAROON) + .build(&mut scene.graph); + + scene.graph.link_nodes(handle, root); + + handle +} + +#[derive(Copy, Clone)] +enum ShapeHandleValue { + Scalar(f32), + Vector(Vector3), +} + +impl ShapeHandleValue { + fn into_scalar(self) -> f32 { + match self { + ShapeHandleValue::Scalar(scalar) => scalar, + ShapeHandleValue::Vector(_) => { + unreachable!() + } + } + } + + fn into_vector(self) -> Vector3 { + match self { + ShapeHandleValue::Scalar(_) => unreachable!(), + ShapeHandleValue::Vector(vector) => vector, + } + } +} + +struct DragContext { + handle: Handle, + initial_handle_position: Vector3, + plane: Plane, + initial_value: ShapeHandleValue, + initial_collider_local_position: Vector3, + handle_major_axis: Option>, + plane_kind: Option, + initial_shape: ColliderShape, +} + +#[derive(TypeUuidProvider)] +#[type_uuid(id = "a012dd4c-ce6d-4e7e-8879-fd8eddaa9677")] +pub struct ColliderShapeInteractionMode { + collider: Handle, + shape_gizmo: Box, + move_gizmo: MoveGizmo, + drag_context: Option, + selected_handle: Handle, + message_sender: MessageSender, +} + +impl ColliderShapeInteractionMode { + fn set_visibility( + &mut self, + controller: &dyn SceneController, + engine: &mut Engine, + visibility: bool, + ) { + let Some(game_scene) = controller.downcast_ref::() else { + return; + }; + + let scene = &mut engine.scenes[game_scene.scene]; + + self.shape_gizmo.set_visibility(scene, visibility); + } +} + +impl InteractionMode for ColliderShapeInteractionMode { + fn on_left_mouse_button_down( + &mut self, + _editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + mouse_position: Vector2, + _frame_size: Vector2, + _settings: &Settings, + ) { + let Some(game_scene) = controller.downcast_mut::() else { + return; + }; + + let scene = &mut engine.scenes[game_scene.scene]; + + if let Some(result) = game_scene.camera_controller.pick( + &scene.graph, + PickingOptions { + cursor_pos: mouse_position, + editor_only: true, + ..Default::default() + }, + ) { + let initial_position = scene.graph[result.node].global_position(); + let camera_view_dir = scene.graph[game_scene.camera_controller.camera] + .look_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + let plane = Plane::from_normal_and_point(&-camera_view_dir, &initial_position) + .unwrap_or_default(); + let collider = scene.graph[self.collider].as_collider(); + let initial_collider_local_position = **collider.local_transform().position(); + let initial_shape = collider.shape().clone(); + + if let Some(handle_value) = + self.shape_gizmo + .value_by_handle(result.node, self.collider, scene) + { + self.selected_handle = result.node; + + self.drag_context = Some(DragContext { + handle: result.node, + initial_handle_position: initial_position, + plane, + handle_major_axis: self.shape_gizmo.handle_major_axis(result.node), + initial_value: handle_value, + initial_collider_local_position, + plane_kind: None, + initial_shape, + }) + } else if let Some(plane_kind) = + self.move_gizmo.handle_pick(result.node, &mut scene.graph) + { + if let Some(handle_value) = + self.shape_gizmo + .value_by_handle(self.selected_handle, self.collider, scene) + { + self.drag_context = Some(DragContext { + handle: self.selected_handle, + initial_handle_position: initial_position, + plane, + handle_major_axis: None, + initial_value: handle_value, + initial_collider_local_position, + plane_kind: Some(plane_kind), + initial_shape, + }) + } + } + } + } + + fn on_left_mouse_button_up( + &mut self, + _editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + _mouse_pos: Vector2, + _frame_size: Vector2, + _settings: &Settings, + ) { + let Some(game_scene) = controller.downcast_mut::() else { + return; + }; + + let scene = &mut engine.scenes[game_scene.scene]; + + if let Some(drag_context) = self.drag_context.take() { + let collider = self.collider; + + let value = std::mem::replace( + scene.graph[collider].as_collider_mut().shape_mut(), + drag_context.initial_shape, + ); + + let command = SetPropertyCommand::new( + "shape".into(), + Box::new(value) as Box, + move |ctx| { + ctx.get_mut::() + .scene + .graph + .node_mut(collider) + }, + ); + self.message_sender.do_command(command); + } + } + + fn on_mouse_move( + &mut self, + mouse_offset: Vector2, + mouse_position: Vector2, + _editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + frame_size: Vector2, + _settings: &Settings, + ) { + let Some(game_scene) = controller.downcast_mut::() else { + return; + }; + + let scene = &mut engine.scenes[game_scene.scene]; + + self.shape_gizmo.reset_handles(scene); + self.move_gizmo.reset_state(&mut scene.graph); + + if let Some(result) = game_scene.camera_controller.pick( + &scene.graph, + PickingOptions { + cursor_pos: mouse_position, + editor_only: true, + ..Default::default() + }, + ) { + if self.shape_gizmo.has_handle(result.node) { + scene.graph[result.node] + .as_sprite_mut() + .set_color(Color::RED); + } + + self.move_gizmo.handle_pick(result.node, &mut scene.graph); + } + + if let Some(drag_context) = self.drag_context.as_ref() { + match drag_context.initial_value { + ShapeHandleValue::Scalar(initial_value) => { + let camera = scene.graph[game_scene.camera_controller.camera].as_camera(); + let ray = camera.make_ray(mouse_position, frame_size); + if let Some(intersection) = ray.plane_intersection_point(&drag_context.plane) { + let inv_transform = scene.graph[self.collider] + .global_transform() + .try_inverse() + .unwrap_or_default(); + let local_space_drag_dir = inv_transform.transform_vector( + &(intersection - drag_context.initial_handle_position), + ); + let sign = local_space_drag_dir + .dot(&drag_context.handle_major_axis.unwrap_or_default()) + .signum(); + let delta = sign + * drag_context + .initial_handle_position + .metric_distance(&intersection); + + self.shape_gizmo.set_value_by_handle( + drag_context.handle, + ShapeHandleValue::Scalar(initial_value + delta), + self.collider, + scene, + drag_context.initial_collider_local_position, + ); + } + } + ShapeHandleValue::Vector(_) => { + if let Some(plane_kind) = drag_context.plane_kind { + let value = self + .shape_gizmo + .value_by_handle(drag_context.handle, self.collider, scene) + .unwrap() + .into_vector(); + + let offset = self.move_gizmo.calculate_offset( + &scene.graph, + game_scene.camera_controller.camera, + mouse_offset, + mouse_position, + frame_size, + plane_kind, + ); + + self.shape_gizmo.set_value_by_handle( + drag_context.handle, + ShapeHandleValue::Vector(value + offset), + self.collider, + scene, + drag_context.initial_collider_local_position, + ); + } + } + } + } + } + + fn update( + &mut self, + _editor_selection: &Selection, + controller: &mut dyn SceneController, + engine: &mut Engine, + _settings: &Settings, + ) { + let Some(game_scene) = controller.downcast_mut::() else { + return; + }; + + let scene = &mut engine.scenes[game_scene.scene]; + + let Some(collider) = scene.graph.try_get_of_type::(self.collider) else { + return; + }; + + let center = collider.global_position(); + let side = collider + .side_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + let up = collider + .up_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + let look = collider + .look_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + + if !self + .shape_gizmo + .try_sync_to_collider(self.collider, center, side, up, look, scene) + { + let new_gizmo = make_shape_gizmo( + self.collider, + center, + side, + up, + look, + scene, + game_scene.editor_objects_root, + true, + ); + + let old_gizmo = std::mem::replace(&mut self.shape_gizmo, new_gizmo); + + old_gizmo.destroy(scene); + } + + self.move_gizmo.set_visible( + &mut scene.graph, + self.shape_gizmo.is_vector_handle(self.selected_handle), + ); + if let Some(selected_handle) = scene.graph.try_get(self.selected_handle) { + let position = selected_handle.global_position(); + self.move_gizmo.set_position(scene, position) + } + } + + fn activate(&mut self, controller: &dyn SceneController, engine: &mut Engine) { + self.set_visibility(controller, engine, true) + } + + fn deactivate(&mut self, controller: &dyn SceneController, engine: &mut Engine) { + self.set_visibility(controller, engine, false) + } + + fn make_button(&mut self, ctx: &mut BuildContext, selected: bool) -> Handle { + make_interaction_mode_button( + ctx, + include_bytes!("../../../resources/triangle.png"), + "Edit Collider Shape", + selected, + ) + } + + fn uuid(&self) -> Uuid { + Self::type_uuid() + } +} + +#[derive(Default)] +pub struct ColliderShapePlugin {} + +impl EditorPlugin for ColliderShapePlugin { + fn on_message(&mut self, message: &Message, editor: &mut Editor) { + let Some(entry) = editor.scenes.current_scene_entry_mut() else { + return; + }; + + let Some(selection) = entry.selection.as_graph() else { + return; + }; + + let Some(game_scene) = entry.controller.downcast_mut::() else { + return; + }; + + let scene = &mut editor.engine.scenes[game_scene.scene]; + + if let Message::SelectionChanged { .. } = message { + if let Some(mode) = entry + .interaction_modes + .remove_typed::() + { + mode.shape_gizmo.destroy(scene); + } + + for node_handle in selection.nodes().iter() { + if let Some(collider) = scene.graph.try_get_of_type::(*node_handle) { + let center = collider.global_position(); + let side = collider + .side_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + let up = collider + .up_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + let look = collider + .look_vector() + .try_normalize(f32::EPSILON) + .unwrap_or_default(); + + let shape_gizmo = make_shape_gizmo( + *node_handle, + center, + side, + up, + look, + scene, + game_scene.editor_objects_root, + false, + ); + + let move_gizmo = MoveGizmo::new(game_scene, &mut editor.engine); + + entry.interaction_modes.add(ColliderShapeInteractionMode { + collider: *node_handle, + shape_gizmo, + move_gizmo, + drag_context: None, + selected_handle: Default::default(), + message_sender: editor.message_sender.clone(), + }); + + break; + } + } + } + } +} diff --git a/editor/src/plugins/collider/segment.rs b/editor/src/plugins/collider/segment.rs new file mode 100644 index 000000000..104c11863 --- /dev/null +++ b/editor/src/plugins/collider/segment.rs @@ -0,0 +1,108 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{ColliderShape, SegmentShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct SegmentShapeGizmo { + begin_handle: Handle, + end_handle: Handle, +} + +impl SegmentShapeGizmo { + pub fn new( + segment: &SegmentShape, + center: Vector3, + root: Handle, + visible: bool, + scene: &mut Scene, + ) -> Self { + Self { + begin_handle: make_handle(scene, center + segment.begin, root, visible), + end_handle: make_handle(scene, center + segment.end, root, visible), + } + } +} + +impl ShapeGizmoTrait for SegmentShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [self.begin_handle, self.end_handle] { + func(handle) + } + } + + fn handle_major_axis(&self, _handle: Handle) -> Option> { + None + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + _side: Vector3, + _up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Segment(segment)) = try_get_collider_shape(collider, scene) else { + return false; + }; + + set_node_position(self.begin_handle, center + segment.begin, scene); + set_node_position(self.end_handle, center + segment.end, scene); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Segment(segment)) = try_get_collider_shape(collider, scene) else { + return None; + }; + + if handle == self.begin_handle { + Some(ShapeHandleValue::Vector(segment.begin)) + } else if handle == self.end_handle { + Some(ShapeHandleValue::Vector(segment.end)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Segment(segment)) = try_get_collider_shape_mut(collider, scene) + else { + return; + }; + + if handle == self.begin_handle { + segment.begin = value.into_vector(); + } else if handle == self.end_handle { + segment.end = value.into_vector(); + } + } + + fn is_vector_handle(&self, handle: Handle) -> bool { + handle == self.begin_handle || handle == self.end_handle + } +} diff --git a/editor/src/plugins/collider/triangle.rs b/editor/src/plugins/collider/triangle.rs new file mode 100644 index 000000000..9e0d46ab9 --- /dev/null +++ b/editor/src/plugins/collider/triangle.rs @@ -0,0 +1,117 @@ +use crate::{ + fyrox::{ + core::{algebra::Vector3, pool::Handle}, + scene::{ + collider::{ColliderShape, TriangleShape}, + node::Node, + Scene, + }, + }, + plugins::collider::{ + make_handle, set_node_position, try_get_collider_shape, try_get_collider_shape_mut, + ShapeGizmoTrait, ShapeHandleValue, + }, +}; + +pub struct TriangleShapeGizmo { + a_handle: Handle, + b_handle: Handle, + c_handle: Handle, +} + +impl TriangleShapeGizmo { + pub fn new( + triangle: &TriangleShape, + center: Vector3, + root: Handle, + visible: bool, + scene: &mut Scene, + ) -> Self { + Self { + a_handle: make_handle(scene, center + triangle.a, root, visible), + b_handle: make_handle(scene, center + triangle.b, root, visible), + c_handle: make_handle(scene, center + triangle.c, root, visible), + } + } +} + +impl ShapeGizmoTrait for TriangleShapeGizmo { + fn for_each_handle(&self, func: &mut dyn FnMut(Handle)) { + for handle in [self.a_handle, self.b_handle, self.c_handle] { + func(handle) + } + } + + fn handle_major_axis(&self, _handle: Handle) -> Option> { + None + } + + fn try_sync_to_collider( + &self, + collider: Handle, + center: Vector3, + _side: Vector3, + _up: Vector3, + _look: Vector3, + scene: &mut Scene, + ) -> bool { + let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape(collider, scene) + else { + return false; + }; + + set_node_position(self.a_handle, center + triangle.a, scene); + set_node_position(self.b_handle, center + triangle.b, scene); + set_node_position(self.c_handle, center + triangle.c, scene); + + true + } + + fn value_by_handle( + &self, + handle: Handle, + collider: Handle, + scene: &Scene, + ) -> Option { + let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape(collider, scene) + else { + return None; + }; + + if handle == self.a_handle { + Some(ShapeHandleValue::Vector(triangle.a)) + } else if handle == self.b_handle { + Some(ShapeHandleValue::Vector(triangle.b)) + } else if handle == self.c_handle { + Some(ShapeHandleValue::Vector(triangle.c)) + } else { + None + } + } + + fn set_value_by_handle( + &self, + handle: Handle, + value: ShapeHandleValue, + collider: Handle, + scene: &mut Scene, + _initial_collider_local_position: Vector3, + ) { + let Some(ColliderShape::Triangle(triangle)) = try_get_collider_shape_mut(collider, scene) + else { + return; + }; + + if handle == self.a_handle { + triangle.a = value.into_vector(); + } else if handle == self.b_handle { + triangle.b = value.into_vector(); + } else if handle == self.c_handle { + triangle.c = value.into_vector(); + } + } + + fn is_vector_handle(&self, handle: Handle) -> bool { + handle == self.a_handle || handle == self.b_handle || handle == self.c_handle + } +}