From c400de19c522ec77ac47c652c81a3fe1f0976fd7 Mon Sep 17 00:00:00 2001 From: Dmitry Stepanov Date: Mon, 17 Jun 2024 22:59:27 +0300 Subject: [PATCH] separate interaction mode for collider shape editing --- editor/resources/triangle.png | Bin 0 -> 17283 bytes editor/src/plugins/collider.rs | 233 ++++++++++++++++++++++----------- 2 files changed, 153 insertions(+), 80 deletions(-) create mode 100644 editor/resources/triangle.png diff --git a/editor/resources/triangle.png b/editor/resources/triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..4dbce43448a85b4e76723faceae4f048c9891135 GIT binary patch literal 17283 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�Bend<2H8OqVX`-Q>(uvN;ofe~ zh(An(5d!PL;@{h4HRiXOLXAvgR>*)VXUF}?E_^eW}Q`Ay6 zxS6&gV7*o(VrjNxWUlsbFWh{Qci?G|QnHBnDefo7-YnzG&q?aGD92Os#%!6`p?kM|t@WU31dcRmb;3O zp1bs-kY^+j^I&)Sqhr?@my`>I$?xoMXC_bkZaFmNx&;K@KE!htv9AiB@)hR+BO8-n zs44{AN?1sbCkag67OrUQ_!2P5?tNRUC)h%}O^6E_Ne4Qg1~G8y12fN@5mJ#^mD-37 zK4eJpw}LuXmGDZkqqf`RiPD=Sn7j$W*GwX{0v@rTxlhp2?62O-k2wK)_L1M>8PbxH z#PgS1E0mk`%RjZW+K3}Q1km4&7d0|R2Xfc^+G$!?k7`j%R7{QRG3A>aU$icWenoMq zD_cvmzs-AEFueMy7ra|i2^`}o+#v4RNj5x|n%4|G{4)fgd(-^(C)8>qd|re9@GMnXuc(&P`y8G}73D5&cRdquyH#t& z)oL+vsAg#3*O#t>*0pOVZPWy{kxdJEbPD&im@}7sYk{9LODTn@QiZDEhwSw?`_?-9 zo{FG5>;CeCr^=0md?3{l$?v}VL;C3T3Pe+)>$!w#+ZocY^e`qk=B%+6fO%9kAUKrfuyY*Y86I%Z1s{)wDhOPUh5QH_#NAZNp(Q z3fND%{t!5zo-|$kfqa;1|MBOIwVG67%+0E=3gohJhH<%5+JDGq=)|Oy4Jx+)qsxa3 z{p2@1%v(IP>paljM9NTj@YTPH7?#-2V>v6?kYR(Y_}w~8#IIRA?pRw6PqgqV!P9|qt&{9Y8Z8-q&rxe4_MVMa1o5*=^L33 zT1V;sB)MyaEM5rRp_n&INv^zlV?X+?-Y~YMu3Mq(0f9yR<#zXemloG?ca4l`RX91% zIy-##NV6ms3ca(K)TyEMO_Dq#tMW2+Atc>3+M388n%n;*n{p=P8kcIny7{Tt=xBdT zhnqP&6#lVfB*A(83I3a8#mVuj5{n$_FNdgmeUUCRitKj{nbO|(_4w%oR7r9oX0)_i(L6G5xT@%X$UwLs+_+1`px>g zs9u;~f#vE=B>h_DdqciT`eWnTpoi?#D=Px}W7lm|U!<-J=vRH^<`30~zj;eP%`fZH zh(^f8-qymOyowMcWTlXF;HrpMP9?5Ie&`M+q(B*ZozbtZ8BQGi`a?V}B}8)qyPsEH zV+Whf(I6(|t#@YCNyHY+jc!LNLFs(8dg6>@8tIC@fQfg_Uf0-!COmFKBM*CJbLS`% zvZHSPVhZGv1lMT)0sc_kA5WYbUfho3(Of+$^+DE!`Rpio*xId^L%n~OK--d6_rpE9 z^|vg-(9DrnS+As?5wQHB23|)XXWk9dPb|A49e1_*!M;KBVz{&}>TZp#?1Ns8W&$QM z)`HGOutvn)oQdoI3K1?0aUCc8u46k#Rjs&>lX1n?YXvs`PR6j5ePHMfQR{T;I4d-j zq(c9jCi1aqj^>2Q(E^F87eXqZW*$f}2mj5zIW+y`So%+1D17`2b+c0b4a+XUt&X^H z>`5u{%HU}k5q;+7#tQU}*XP)-?xWt9_BfLUobc)dPN06KHTo)%7aUZZmlQ|S5%0hV zp_a%m3F!gCDG$!I7&;x6xJd9niZ+TbSau4ATOufj3waV{%T9l86<126zgn;+JSVV1 zylQH#`1BF1jae1i^+nJo|9Ii~p5~g&YUe+%aaX{dBU3^&hQ;grqb06Tz8_B}DIMRk z`yI2vM93S;GT(*WltZdqk^K2V2rJ3V8BZ~!qDjC;^q`u#4E@*9iaK29c8TngKRMR; z(np0~{mB^A962jFWP}d%g>$(dR+!!U@5kW*1~i{eLs6Yc13n3Z?zZWjd5hVyhlBog zK6}J?6u1z0+Be$^uM7SCYb)z^jxe{E_0C{$Z`Q*Ix!Q<4n)y9=O_fjxJbPyTuJ(K3 z2-CIK!S-$kFB@$64h*T>Pd*5>%zlLL-?e7`Q*5iVa)l7P88*08Uv>EN2g1m*HY%$! zI4k{k1!BuG=%W?Dc5D1pLDN9b&=&mrBoVWaTNdN5zmJRr9F&Bi~6J#BNfBLu=5B#!aAxzitNmp6@s{{Yjk zq|JT?quRH%U3f8asQxTzYayg@>}?$0F5c_#s2a@bi(K0-m-cdwnf-JDx+hjmpn$Lr zf5;>5ROoHL^AZ`P_qym}wPk9-%H(sdrJsx@Y0TVBpLedZihdp(;0d}Vjp{^UlI=&g zGL~)sq`t986eeG?w{-^xHq&9jt1UVU^!vNpF4<83e)Pl*{(__&+QmMlB6*|LHE^-d zSlr^Lk7_b*^T&2Y0jpZkJl_?V_X}cT{Xi+0<=Hf=pU8NxlKlq^D*I7B3X_vadH?PO z7Ca95m21V-&op9WztXjnf>&+8GkB;Y4;Lr$G0Wm1xzW5sS|>&>Q@imI6K4rVw@(ho zR@pV(;RK*;@xL-{U&yheCtB(a`M67o<7Aj5}hBhgDr?$$Ov9s?egI3AOLbvq; zX17y!ma;rDPTkq`q0>%CtMSxIc+dwx^S&wYC1LA^7UMVTN^q}c~$OV3Yk&&lJz zugq?<2}AFw^lCie0l`sIoY>BRuPS*HlZD}@)8xI@YJUALDY*S%*>&UQlPY{XT5I=h zRCLt~g#Rulq?JjT2>ed@R!6qi*bMUcHPRtjp)4cU$r3tET^UAdvd0MR((^L9E(Bd2 zF$MI_DAiAScm>@yZM>%T^KqJEi6bNKNvTh_kN+u1=lPQ;!3v_{pvhYs?uCnOJLS8# zzWXpVuTjTmk(yyK-$bCO<0DP;{3`i?#u*ss$5HV6!C&8!IEL@#yOP0h9n2v|tSuGS zasrK#R36i*iMekcg>n&?}Qj z-SD*tQBXqN_lQx0-HfNg+*vM>>OnV4=axG?Da}>vtp_FL;aZKS-5@!GE~LPr_S2b= z3+w-Z#+|UC!pu5?!%&88t_?FJ-uB&d7X87KbKxPgMBaogA&>e#t3k)6 zPa6N6`9)`MUfhfY!%0PyH9uUpojrcxp=e2FiyUKs`~8icR=cv^Zhv`D&*o7k^qyZf z*=Yg|Jb^Hey?O~0*)4q=x+zI*-AByqSixpyH3$Od(NHqVop0NuwnPh^8o#|Lx@%+4 z5A_SKbn?|c$W{|rLT5MC^MUGHs$x{9$@H^v&cUkTbaz+qTs^(Ro+h>A`H>f2m!*R5 z-v>R#<}*{(aVa@%J#vXkqT6nN?(eS~@{vp%z2{|JPbce+o*%ufNXN7943-gMGh*#E|Yb zyIol2I>RxYaakXvLd~86)hrI5_!A}-EQODB5GAPlp06Gig@IPtCQ;0(a&^KKH!s zkW5tev7cvY-dJPa66ePk%}$upHC^2_z#IWuDlm5U5)~U|@?E_MBi|%<>!C!bBRfnQ zgj1Nyez#YH2_ydN;9n@1+9+#y>bs}ZH`jd(j9+???PFp4SK?72Nv!%?3O&Sf-EI9Y z;g@@XO-G$4xj|tphyCxx-#N9n`goEq`gEp15R1OsCh20Sck9^k?y&AxiDU66V`P0` z1U#Y?rS&v(LY*-CmltwTsQM*o(O~HLN(ez#B{ws!wUydMe#w(;Ugq>GYVq~COn%D# zNpoC*<1imwUbifUL6vc9olJ<|S@Ji^ao}GRsD^nsc!$&#SoLE~EUW9l{eUeyct}R5 zuk9d3VA=rOe(N?#&E{Wk(1+A@K`1M%vAme-lxi<)iQGDGJ5#?~W}2G2aw!6YxtIQ~ z=Qet)eTG>P4cXO}#bwC_R=1?ZmZOMZrql9b2)UQT-%12>&=SL6Dsf~EQ)1*pq}p3p zvc;cVHj~H8&rxrCRCe|G!^SN|FJ6ROa$2 Vj@, pos_y_handle: Handle, @@ -57,11 +66,13 @@ fn make_handle(scene: &mut Scene, position: Vector3, root: Handle) -> .unwrap(); let handle = SpriteBuilder::new( - BaseBuilder::new().with_local_transform( - TransformBuilder::new() - .with_local_position(position) - .build(), - ), + BaseBuilder::new() + .with_local_transform( + TransformBuilder::new() + .with_local_position(position) + .build(), + ) + .with_visibility(false), ) .with_material(MaterialResource::new_ok(ResourceKind::Embedded, material)) .with_size(0.05) @@ -73,7 +84,7 @@ fn make_handle(scene: &mut Scene, position: Vector3, root: Handle) -> handle } -impl ShapeHandles { +impl ShapeGizmo { fn try_create( shape: ColliderShape, center: Vector3, @@ -146,7 +157,7 @@ impl ShapeHandles { fn for_each_handle)>(&self, mut func: F) { match self { - ShapeHandles::Cuboid { + ShapeGizmo::Cuboid { pos_x_handle, pos_y_handle, pos_z_handle, @@ -165,8 +176,8 @@ impl ShapeHandles { func(*handle) } } - ShapeHandles::Ball { radius_handle } => func(*radius_handle), - ShapeHandles::Capsule { + ShapeGizmo::Ball { radius_handle } => func(*radius_handle), + ShapeGizmo::Capsule { radius_handle, begin_handle, end_handle, @@ -175,11 +186,11 @@ impl ShapeHandles { func(*handle) } } - ShapeHandles::Cylinder { + ShapeGizmo::Cylinder { radius_handle, half_height_handle, } - | ShapeHandles::Cone { + | ShapeGizmo::Cone { radius_handle, half_height_handle, } => { @@ -282,83 +293,111 @@ impl ShapeHandles { }); has_handle } + + fn set_visibility(&self, scene: &mut Scene, visibility: bool) { + self.for_each_handle(|handle| { + scene.graph[handle].set_visibility(visibility); + }) + } } -#[derive(Default)] -pub struct ColliderShapePlugin { - collider: Handle, +#[derive(TypeUuidProvider)] +#[type_uuid(id = "a012dd4c-ce6d-4e7e-8879-fd8eddaa9677")] +pub struct ColliderShapeInteractionMode { active_handle: Handle, - shape_handles: Option, + collider: Handle, + gizmo: ShapeGizmo, } -impl EditorPlugin for ColliderShapePlugin { - fn on_ui_message(&mut self, message: &mut UiMessage, editor: &mut Editor) { - if message.destination() != editor.scene_viewer.frame() { - return; - } - - let frame = editor - .engine - .user_interfaces - .first() - .node(editor.scene_viewer.frame()); - - let origin = frame.screen_position(); - - let Some(entry) = editor.scenes.current_scene_entry_mut() else { +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 Some(game_scene) = entry.controller.downcast_mut::() else { + let scene = &mut engine.scenes[game_scene.scene]; + + self.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_pos: Vector2, + _frame_size: Vector2, + _settings: &Settings, + ) { + } + + 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, + ) { + } + + 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 editor.engine.scenes[game_scene.scene]; + let scene = &mut engine.scenes[game_scene.scene]; - if let Some(WidgetMessage::MouseMove { pos, .. }) = message.data() { - let cursor_pos = *pos - origin; + self.gizmo.reset_handles(scene); + self.active_handle = Handle::NONE; - if let Some(shape) = self.shape_handles.as_ref() { - shape.reset_handles(scene); - self.active_handle = Handle::NONE; + if let Some(result) = game_scene.camera_controller.pick( + &scene.graph, + PickingOptions { + cursor_pos: mouse_position, + editor_only: true, + ..Default::default() + }, + ) { + if self.gizmo.has_handle(result.node) { + scene.graph[result.node] + .as_sprite_mut() + .set_color(Color::RED); - if let Some(result) = game_scene.camera_controller.pick( - &scene.graph, - PickingOptions { - cursor_pos, - editor_only: true, - filter: None, - ignore_back_faces: false, - use_picking_loop: false, - only_meshes: false, - }, - ) { - if shape.has_handle(result.node) { - scene.graph[result.node] - .as_sprite_mut() - .set_color(Color::RED); - - self.active_handle = result.node; - } - } + self.active_handle = result.node; } } } - fn on_update(&mut self, editor: &mut Editor) { - let Some(shape) = self.shape_handles.as_ref() else { + 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 Some(entry) = editor.scenes.current_scene_entry_mut() else { - return; - }; - - let Some(game_scene) = entry.controller.downcast_mut::() else { - return; - }; - - let scene = &mut editor.engine.scenes[game_scene.scene]; + let scene = &mut engine.scenes[game_scene.scene]; let Some(collider) = scene.graph.try_get_of_type::(self.collider) else { return; @@ -378,9 +417,36 @@ impl EditorPlugin for ColliderShapePlugin { .try_normalize(f32::EPSILON) .unwrap_or_default(); - shape.sync_to_shape(collider.shape().clone(), center, side, up, look, scene); + self.gizmo + .sync_to_shape(collider.shape().clone(), center, side, up, look, scene); } + 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; @@ -397,14 +463,15 @@ impl EditorPlugin for ColliderShapePlugin { let scene = &mut editor.engine.scenes[game_scene.scene]; if let Message::SelectionChanged { .. } = message { - if let Some(shape_handles) = self.shape_handles.take() { - shape_handles.destroy(scene); + if let Some(mode) = entry + .interaction_modes + .remove_typed::() + { + mode.gizmo.destroy(scene); } - for node in selection.nodes().iter() { - if let Some(collider) = scene.graph.try_get_of_type::(*node) { - self.collider = *node; - + 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() @@ -419,7 +486,7 @@ impl EditorPlugin for ColliderShapePlugin { .try_normalize(f32::EPSILON) .unwrap_or_default(); - self.shape_handles = ShapeHandles::try_create( + if let Some(gizmo) = ShapeGizmo::try_create( collider.shape().clone(), center, side, @@ -427,7 +494,13 @@ impl EditorPlugin for ColliderShapePlugin { look, scene, game_scene.editor_objects_root, - ); + ) { + entry.interaction_modes.add(ColliderShapeInteractionMode { + active_handle: Default::default(), + collider: *node_handle, + gizmo, + }) + } break; }