shape editing prototype for colliders

- does not handle vec3 params of shapes yet
- does not create respective commands when releasing mouse button
- added more supported shape types
This commit is contained in:
Dmitry Stepanov
2024-06-18 19:49:56 +03:00
parent c400de19c5
commit 65bc314c0b
+487 -53
View File
@@ -7,6 +7,7 @@ use crate::{
core::{
algebra::{Vector2, Vector3},
color::Color,
math::plane::Plane,
pool::Handle,
type_traits::prelude::*,
Uuid,
@@ -29,6 +30,7 @@ use crate::{
};
enum ShapeGizmo {
NonEditable,
Cuboid {
pos_x_handle: Handle<Node>,
pos_y_handle: Handle<Node>,
@@ -53,9 +55,23 @@ enum ShapeGizmo {
radius_handle: Handle<Node>,
half_height_handle: Handle<Node>,
},
Segment {
begin_handle: Handle<Node>,
end_handle: Handle<Node>,
},
Triangle {
a_handle: Handle<Node>,
b_handle: Handle<Node>,
c_handle: Handle<Node>,
},
}
fn make_handle(scene: &mut Scene, position: Vector3<f32>, root: Handle<Node>) -> Handle<Node> {
fn make_handle(
scene: &mut Scene,
position: Vector3<f32>,
root: Handle<Node>,
visible: bool,
) -> Handle<Node> {
let mut material = Material::standard_sprite();
material
@@ -72,7 +88,7 @@ fn make_handle(scene: &mut Scene, position: Vector3<f32>, root: Handle<Node>) ->
.with_local_position(position)
.build(),
)
.with_visibility(false),
.with_visibility(visible),
)
.with_material(MaterialResource::new_ok(ResourceKind::Embedded, material))
.with_size(0.05)
@@ -85,7 +101,7 @@ fn make_handle(scene: &mut Scene, position: Vector3<f32>, root: Handle<Node>) ->
}
impl ShapeGizmo {
fn try_create(
fn create(
shape: ColliderShape,
center: Vector3<f32>,
side: Vector3<f32>,
@@ -93,71 +109,102 @@ impl ShapeGizmo {
look: Vector3<f32>,
scene: &mut Scene,
root: Handle<Node>,
) -> Option<Self> {
visible: bool,
) -> Self {
match shape {
ColliderShape::Ball(ball_shape) => Some(Self::Ball {
radius_handle: make_handle(scene, center + side.scale(ball_shape.radius), root),
}),
ColliderShape::Cylinder(cylinder_shape) => Some(Self::Cylinder {
radius_handle: make_handle(scene, center + side.scale(cylinder_shape.radius), root),
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_shape.half_height),
center + up.scale(cylinder.half_height),
root,
visible,
),
}),
ColliderShape::Cone(cone_shape) => Some(Self::Cone {
radius_handle: make_handle(scene, center + side.scale(cone_shape.radius), root),
},
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_shape.half_height),
center + up.scale(cone.half_height),
root,
visible,
),
}),
ColliderShape::Cuboid(cuboid_shape) => Some(Self::Cuboid {
},
ColliderShape::Cuboid(cuboid) => Self::Cuboid {
pos_x_handle: make_handle(
scene,
center + side.scale(cuboid_shape.half_extents.x),
center + side.scale(cuboid.half_extents.x),
root,
visible,
),
pos_y_handle: make_handle(
scene,
center + up.scale(cuboid_shape.half_extents.y),
center + up.scale(cuboid.half_extents.y),
root,
visible,
),
pos_z_handle: make_handle(
scene,
center + look.scale(cuboid_shape.half_extents.z),
center + look.scale(cuboid.half_extents.z),
root,
visible,
),
neg_x_handle: make_handle(
scene,
center - side.scale(cuboid_shape.half_extents.x),
center - side.scale(cuboid.half_extents.x),
root,
visible,
),
neg_y_handle: make_handle(
scene,
center - up.scale(cuboid_shape.half_extents.y),
center - up.scale(cuboid.half_extents.y),
root,
visible,
),
neg_z_handle: make_handle(
scene,
center - look.scale(cuboid_shape.half_extents.z),
center - look.scale(cuboid.half_extents.z),
root,
visible,
),
}),
ColliderShape::Capsule(capsule_shape) => Some(Self::Capsule {
radius_handle: make_handle(scene, center + side.scale(capsule_shape.radius), root),
begin_handle: make_handle(scene, center + capsule_shape.begin, root),
end_handle: make_handle(scene, center + capsule_shape.end, root),
}),
_ => None,
},
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<F: FnMut(Handle<Node>)>(&self, mut func: F) {
match self {
ShapeGizmo::Cuboid {
Self::NonEditable => {}
Self::Cuboid {
pos_x_handle,
pos_y_handle,
pos_z_handle,
@@ -176,8 +223,8 @@ impl ShapeGizmo {
func(*handle)
}
}
ShapeGizmo::Ball { radius_handle } => func(*radius_handle),
ShapeGizmo::Capsule {
Self::Ball { radius_handle } => func(*radius_handle),
Self::Capsule {
radius_handle,
begin_handle,
end_handle,
@@ -186,11 +233,11 @@ impl ShapeGizmo {
func(*handle)
}
}
ShapeGizmo::Cylinder {
Self::Cylinder {
radius_handle,
half_height_handle,
}
| ShapeGizmo::Cone {
| Self::Cone {
radius_handle,
half_height_handle,
} => {
@@ -198,10 +245,27 @@ impl ShapeGizmo {
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 sync_to_shape(
fn try_sync_to_shape(
&self,
shape: ColliderShape,
center: Vector3<f32>,
@@ -209,7 +273,7 @@ impl ShapeGizmo {
up: Vector3<f32>,
look: Vector3<f32>,
scene: &mut Scene,
) {
) -> bool {
let mut set_position = |handle: Handle<Node>, position: Vector3<f32>| {
scene.graph[handle]
.local_transform_mut()
@@ -219,6 +283,7 @@ impl ShapeGizmo {
match (self, shape) {
(Self::Ball { radius_handle }, ColliderShape::Ball(ball_shape)) => {
set_position(*radius_handle, center + side.scale(ball_shape.radius));
true
}
(
Self::Cylinder {
@@ -229,6 +294,7 @@ impl ShapeGizmo {
) => {
set_position(*radius_handle, center + side.scale(cylinder.radius));
set_position(*half_height_handle, center + up.scale(cylinder.half_height));
true
}
(
Self::Cone {
@@ -239,6 +305,7 @@ impl ShapeGizmo {
) => {
set_position(*radius_handle, center + side.scale(cone.radius));
set_position(*half_height_handle, center + up.scale(cone.half_height));
true
}
(
Self::Cuboid {
@@ -257,6 +324,7 @@ impl ShapeGizmo {
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 {
@@ -269,9 +337,279 @@ impl ShapeGizmo {
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<Node>,
collider: &Collider,
) -> Option<ShapeHandleValue> {
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<Node>,
value: ShapeHandleValue,
collider: &mut Collider,
) -> Option<ShapeHandleValue> {
match (self, collider.shape_mut()) {
(Self::Ball { radius_handle }, ColliderShape::Ball(ball_shape)) => {
if handle == *radius_handle {
ball_shape.radius = value.into_scalar();
}
}
(
Self::Cylinder {
radius_handle,
half_height_handle,
},
ColliderShape::Cylinder(cylinder),
) => {
if handle == *radius_handle {
cylinder.radius = value.into_scalar();
} else if handle == *half_height_handle {
cylinder.half_height = value.into_scalar();
}
}
(
Self::Cone {
radius_handle,
half_height_handle,
},
ColliderShape::Cone(cone),
) => {
if handle == *radius_handle {
cone.radius = value.into_scalar();
} else if handle == *half_height_handle {
cone.half_height = value.into_scalar();
}
}
(
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();
} else if handle == *pos_y_handle {
cuboid.half_extents.y = value.into_scalar();
} else if handle == *pos_z_handle {
cuboid.half_extents.z = value.into_scalar();
} else if handle == *neg_x_handle {
let transform = collider.local_transform_mut();
let position = **transform.position();
transform.set_position(Vector3::new(
position.x - value.into_scalar(),
position.y,
position.z,
));
} else if handle == *neg_y_handle {
let transform = collider.local_transform_mut();
let position = **transform.position();
transform.set_position(Vector3::new(
position.x,
position.y - value.into_scalar(),
position.z,
));
} else if handle == *neg_z_handle {
let transform = collider.local_transform_mut();
let position = **transform.position();
transform.set_position(Vector3::new(
position.x,
position.y,
position.z - value.into_scalar(),
));
}
}
(
Self::Capsule {
radius_handle,
begin_handle,
end_handle,
},
ColliderShape::Capsule(capsule),
) => {
if handle == *radius_handle {
capsule.radius = value.into_scalar();
} 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 reset_handles(&self, scene: &mut Scene) {
@@ -301,12 +639,42 @@ impl ShapeGizmo {
}
}
enum ShapeHandleValue {
Scalar(f32),
Vector(Vector3<f32>),
}
impl ShapeHandleValue {
fn into_scalar(self) -> f32 {
match self {
ShapeHandleValue::Scalar(scalar) => scalar,
ShapeHandleValue::Vector(_) => {
unreachable!()
}
}
}
fn into_vector(self) -> Vector3<f32> {
match self {
ShapeHandleValue::Scalar(_) => unreachable!(),
ShapeHandleValue::Vector(vector) => vector,
}
}
}
struct DragContext {
handle: Handle<Node>,
initial_position: Vector3<f32>,
plane: Plane,
initial_value: ShapeHandleValue,
}
#[derive(TypeUuidProvider)]
#[type_uuid(id = "a012dd4c-ce6d-4e7e-8879-fd8eddaa9677")]
pub struct ColliderShapeInteractionMode {
active_handle: Handle<Node>,
collider: Handle<Node>,
gizmo: ShapeGizmo,
drag_context: Option<DragContext>,
}
impl ColliderShapeInteractionMode {
@@ -330,12 +698,45 @@ 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<f32>,
controller: &mut dyn SceneController,
engine: &mut Engine,
mouse_position: Vector2<f32>,
_frame_size: Vector2<f32>,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() 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()
},
) {
if let Some(handle_value) = self
.gizmo
.value_by_handle(result.node, scene.graph[self.collider].as_collider())
{
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();
self.drag_context = Some(DragContext {
handle: result.node,
initial_position,
plane: Plane::from_normal_and_point(&-camera_view_dir, &initial_position)
.unwrap_or_default(),
initial_value: handle_value,
})
}
}
}
fn on_left_mouse_button_up(
@@ -347,6 +748,7 @@ impl InteractionMode for ColliderShapeInteractionMode {
_frame_size: Vector2<f32>,
_settings: &Settings,
) {
if let Some(_drag_context) = self.drag_context.take() {}
}
fn on_mouse_move(
@@ -356,7 +758,7 @@ impl InteractionMode for ColliderShapeInteractionMode {
_editor_selection: &Selection,
controller: &mut dyn SceneController,
engine: &mut Engine,
_frame_size: Vector2<f32>,
frame_size: Vector2<f32>,
_settings: &Settings,
) {
let Some(game_scene) = controller.downcast_mut::<GameScene>() else {
@@ -366,7 +768,6 @@ impl InteractionMode for ColliderShapeInteractionMode {
let scene = &mut engine.scenes[game_scene.scene];
self.gizmo.reset_handles(scene);
self.active_handle = Handle::NONE;
if let Some(result) = game_scene.camera_controller.pick(
&scene.graph,
@@ -380,8 +781,22 @@ impl InteractionMode for ColliderShapeInteractionMode {
scene.graph[result.node]
.as_sprite_mut()
.set_color(Color::RED);
}
}
self.active_handle = result.node;
if let Some(drag_context) = self.drag_context.as_ref() {
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 delta = drag_context.initial_position.metric_distance(&intersection);
if let ShapeHandleValue::Scalar(inital_value) = drag_context.initial_value {
self.gizmo.set_value_by_handle(
drag_context.handle,
ShapeHandleValue::Scalar(inital_value + delta),
scene.graph[self.collider].as_collider_mut(),
);
}
}
}
}
@@ -417,8 +832,26 @@ impl InteractionMode for ColliderShapeInteractionMode {
.try_normalize(f32::EPSILON)
.unwrap_or_default();
self.gizmo
.sync_to_shape(collider.shape().clone(), center, side, up, look, scene);
let shape = collider.shape().clone();
if !self
.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.gizmo, new_gizmo);
old_gizmo.destroy(scene);
}
}
fn activate(&mut self, controller: &dyn SceneController, engine: &mut Engine) {
@@ -486,7 +919,7 @@ impl EditorPlugin for ColliderShapePlugin {
.try_normalize(f32::EPSILON)
.unwrap_or_default();
if let Some(gizmo) = ShapeGizmo::try_create(
let gizmo = ShapeGizmo::create(
collider.shape().clone(),
center,
side,
@@ -494,13 +927,14 @@ impl EditorPlugin for ColliderShapePlugin {
look,
scene,
game_scene.editor_objects_root,
) {
entry.interaction_modes.add(ColliderShapeInteractionMode {
active_handle: Default::default(),
collider: *node_handle,
gizmo,
})
}
false,
);
entry.interaction_modes.add(ColliderShapeInteractionMode {
collider: *node_handle,
gizmo,
drag_context: None,
});
break;
}