Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add display for double doors #138

Merged
merged 12 commits into from
Sep 4, 2023
2 changes: 1 addition & 1 deletion rmf_site_editor/src/interaction/outline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ pub fn update_outline_visualization(
mut commands: Commands,
outlinable: Query<
(Entity, &Hovered, &Selected, &OutlineVisualization),
Or<(Changed<Hovered>, Changed<Selected>)>,
Or<(Changed<Hovered>, Changed<Selected>, Changed<Children>)>,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unfortunate side effect of this PR. Changing a door from single to double will spawn an additional mesh but the system that updates the hovering and displays outlines will not be triggered since the Hovered and Selected components didn't change, meaning that the newly spawned door will not have its outline displayed until the mouse hovers on it again.

This approach works but the performance implication might not be great since now this system will be triggered much more often (Changed<Children> is fairly common).

Other approaches that I considered:

  • Just triggering a change detection by doing a mutable access to the root door entity's Hovered component when a new door is spawned. Works and only a few lines but a bit hacky.
  • Manually copying the Outline components when spawning a new door, probably the best option from a performance point of view but would introduce a fair bit of outline related code in the door module

Copy link
Member Author

@luca-della-vedova luca-della-vedova Jun 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5461c93 is a tentative approach to optimize this, it's a new system that looks at newly spawned meshes, iterates over their parents and copies the outline components to the new mesh if any is found. It's a bit hard to benchmark performance but Added<Handle<Mesh>> should be triggered quite a bit less than Changed<Children>. Also AncestorIter goes up the parentage and should perform way less iterations than a full iteration of all descendents.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outline copying system for new meshes is an interesting idea, but outline behavior is supposed to be based on VisualCue settings, not just simple propagation.

Overall I actually like the idea of dirtying the Hover component to trigger the recalculation. We do something similar here to update lifts using .set_changed().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to manual set_changed to Hovered component e2b35dd

>,
descendants: Query<(Option<&Children>, Option<&ComputedVisualCue>)>,
) {
Expand Down
208 changes: 171 additions & 37 deletions rmf_site_editor/src/site/door.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,50 @@ pub const DOOR_CUE_HEIGHT: f32 = 0.004;
pub const DOOR_STOP_LINE_THICKNESS: f32 = 0.01;
pub const DOOR_STOP_LINE_LENGTH: f32 = 3.0 * DEFAULT_DOOR_THICKNESS;
pub const DOOR_SWEEP_THICKNESS: f32 = 0.05;
pub const DOUBLE_DOOR_GAP: f32 = 0.05;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DoorBodyType {
SingleSwing { body: Entity },
DoubleSwing { left: Entity, right: Entity },
SingleSliding { body: Entity },
DoubleSliding { left: Entity, right: Entity },
Model { body: Entity },
}

impl DoorBodyType {
pub fn from_door_type(door_type: &DoorType, entities: &Vec<Entity>) -> Self {
match door_type {
DoorType::SingleSwing(_) => DoorBodyType::SingleSwing { body: entities[0] },
DoorType::DoubleSwing(_) => DoorBodyType::DoubleSwing {
left: entities[0],
right: entities[1],
},
DoorType::SingleSliding(_) => DoorBodyType::SingleSliding { body: entities[0] },
DoorType::DoubleSliding(_) => DoorBodyType::DoubleSliding {
left: entities[0],
right: entities[1],
},
DoorType::Model(_) => DoorBodyType::Model { body: entities[0] },
}
}

pub fn entities(&self) -> Vec<Entity> {
match self {
DoorBodyType::SingleSwing { body }
| DoorBodyType::SingleSliding { body }
| DoorBodyType::Model { body } => {
vec![*body]
}
DoorBodyType::DoubleSwing { left, right }
| DoorBodyType::DoubleSliding { left, right } => vec![*left, *right],
}
}
}

#[derive(Debug, Clone, Copy, Component)]
pub struct DoorSegments {
// TODO(MXG): When it's time to animate the doors we should replace this
// with an enum for the different possible door types: Single/Double Swing/Sliding
pub body: Entity,
pub body: DoorBodyType,
pub cue_inner: Entity,
pub cue_outline: Entity,
}
Expand All @@ -41,7 +79,7 @@ fn make_door_visuals(
edge: &Edge<Entity>,
anchors: &AnchorParams,
kind: &DoorType,
) -> (Transform, Transform, Mesh, Mesh) {
) -> (Transform, Vec<Transform>, Mesh, Mesh) {
let p_start = anchors
.point_in_parent_frame_of(edge.left(), Category::Door, entity)
.unwrap();
Expand All @@ -55,17 +93,59 @@ fn make_door_visuals(
let center = (p_start + p_end) / 2.0;

let (inner, outline) = make_door_cues(length, kind);

let get_double_door_tfs = |mid_offset: f32| -> Vec<Transform> {
let left_door_length = (length - DOUBLE_DOOR_GAP) / 2.0 - mid_offset;
let right_door_length = (length - DOUBLE_DOOR_GAP) / 2.0 + mid_offset;
vec![
Transform {
translation: Vec3::new(
0.,
(length + DOUBLE_DOOR_GAP) / 4.0 + mid_offset / 2.0,
DEFAULT_LEVEL_HEIGHT / 2.0,
),
scale: Vec3::new(
DEFAULT_DOOR_THICKNESS,
left_door_length,
DEFAULT_LEVEL_HEIGHT,
),
..default()
},
Transform {
translation: Vec3::new(
0.,
-(length + DOUBLE_DOOR_GAP) / 4.0 + mid_offset / 2.0,
DEFAULT_LEVEL_HEIGHT / 2.0,
),
scale: Vec3::new(
DEFAULT_DOOR_THICKNESS,
right_door_length,
DEFAULT_LEVEL_HEIGHT,
),
..default()
},
]
};

let door_tfs = match kind {
// TODO(luca) implement model variant
DoorType::SingleSwing(_) | DoorType::SingleSliding(_) | DoorType::Model(_) => {
vec![Transform {
translation: Vec3::new(0., 0., DEFAULT_LEVEL_HEIGHT / 2.0),
scale: Vec3::new(DEFAULT_DOOR_THICKNESS, length, DEFAULT_LEVEL_HEIGHT),
..default()
}]
}
DoorType::DoubleSwing(door) => get_double_door_tfs(door.compute_offset(length)),
DoorType::DoubleSliding(door) => get_double_door_tfs(door.compute_offset(length)),
};
(
Transform {
translation: Vec3::new(center.x, center.y, 0.),
rotation: Quat::from_rotation_z(yaw),
..default()
},
Transform {
translation: Vec3::new(0., 0., DEFAULT_LEVEL_HEIGHT / 2.0),
scale: Vec3::new(DEFAULT_DOOR_THICKNESS, length, DEFAULT_LEVEL_HEIGHT),
..default()
},
door_tfs,
inner,
outline,
)
Expand Down Expand Up @@ -97,10 +177,16 @@ fn door_slide_arrows(start: f32, stop: f32) -> MeshBuffer {
door_slide_arrow(start, stop, -1.0).merge_with(door_slide_arrow(start, stop, 1.0))
}

fn door_swing_arc(door_width: f32, door_count: u32, pivot_on: Side, swing: Swing) -> MeshBuffer {
fn door_swing_arc(
door_width: f32,
door_count: u32,
offset: f32,
pivot_on: Side,
swing: Swing,
) -> MeshBuffer {
let pivot = pivot_on.sign() * door_width / 2.0;
let pivot = Vec3::new(0.0, pivot, DOOR_CUE_HEIGHT);
let door_width = door_width / door_count as f32;
let door_width = door_width / door_count as f32 + offset;
let (initial_angle, sweep) = swing.swing_on_pivot(pivot_on);
flat_arc(
pivot,
Expand Down Expand Up @@ -149,11 +235,14 @@ fn make_door_cues(door_width: f32, kind: &DoorType) -> (Mesh, Mesh) {
.into_mesh_and_outline()
}
DoorType::SingleSwing(door) => {
door_swing_arc(door_width, 1, door.pivot_on, door.swing).into_mesh_and_outline()
door_swing_arc(door_width, 1, 0.0, door.pivot_on, door.swing).into_mesh_and_outline()
}
DoorType::DoubleSwing(door) => {
let mid = door.compute_offset(door_width);
door_swing_arc(door_width, 2, -mid, Side::Left, door.swing)
.merge_with(door_swing_arc(door_width, 2, mid, Side::Right, door.swing))
.into_mesh_and_outline()
}
DoorType::DoubleSwing(door) => door_swing_arc(door_width, 2, Side::Left, door.swing)
.merge_with(door_swing_arc(door_width, 2, Side::Right, door.swing))
.into_mesh_and_outline(),
_ => {
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, Vec::<[f32; 3]>::new());
Expand All @@ -179,20 +268,26 @@ pub fn add_door_visuals(
mut meshes: ResMut<Assets<Mesh>>,
) {
for (e, edge, kind, visibility) in &new_doors {
let (pose_tf, shape_tf, cue_inner_mesh, cue_outline_mesh) =
let (pose_tf, door_tfs, cue_inner_mesh, cue_outline_mesh) =
make_door_visuals(e, edge, &anchors, kind);

let mut commands = commands.entity(e);
let (body, cue_inner, cue_outline) = commands.add_children(|parent| {
let body = parent
.spawn(PbrBundle {
mesh: assets.box_mesh.clone(),
material: assets.door_body_material.clone(),
transform: shape_tf,
..default()
let bodies = door_tfs
.iter()
.map(|tf| {
parent
.spawn(PbrBundle {
mesh: assets.box_mesh.clone(),
material: assets.door_body_material.clone(),
transform: *tf,
..default()
})
.insert(Selectable::new(e))
.id()
})
.insert(Selectable::new(e))
.id();
.collect::<Vec<_>>();
let body = DoorBodyType::from_door_type(kind, &bodies);

let cue_inner = parent
.spawn(PbrBundle {
Expand Down Expand Up @@ -245,6 +340,7 @@ pub fn add_door_visuals(
}

fn update_door_visuals(
commands: &mut Commands,
entity: Entity,
edge: &Edge<Entity>,
kind: &DoorType,
Expand All @@ -253,45 +349,80 @@ fn update_door_visuals(
transforms: &mut Query<&mut Transform>,
mesh_handles: &mut Query<&mut Handle<Mesh>>,
mesh_assets: &mut ResMut<Assets<Mesh>>,
) {
let (pose_tf, shape_tf, cue_inner_mesh, cue_outline_mesh) =
assets: &Res<SiteAssets>,
) -> Option<DoorBodyType> {
let (pose_tf, door_tfs, cue_inner_mesh, cue_outline_mesh) =
make_door_visuals(entity, edge, anchors, kind);
let mut door_transform = transforms.get_mut(entity).unwrap();
*door_transform = pose_tf;
let mut shape_transform = transforms.get_mut(segments.body).unwrap();
*shape_transform = shape_tf;
let mut entities = segments.body.entities();
for (door_tf, e) in door_tfs.iter().zip(entities.iter()) {
let mut door_transform = transforms.get_mut(*e).unwrap();
*door_transform = *door_tf;
}
for door_tf in door_tfs.iter().skip(entities.len()) {
// New doors were added, we need to spawn them
let id = commands
.spawn(PbrBundle {
mesh: assets.box_mesh.clone(),
material: assets.door_body_material.clone(),
transform: *door_tf,
..default()
})
.insert(Selectable::new(entity))
.id();
entities.push(id);
commands.entity(entity).add_child(id);
}
for e in entities.iter().skip(door_tfs.len()) {
// Doors were removed, we need to despawn them
commands.entity(*e).despawn_recursive();
}
let mut cue_inner = mesh_handles.get_mut(segments.cue_inner).unwrap();
*cue_inner = mesh_assets.add(cue_inner_mesh);
let mut cue_outline = mesh_handles.get_mut(segments.cue_outline).unwrap();
*cue_outline = mesh_assets.add(cue_outline_mesh);
let new_segments = DoorBodyType::from_door_type(kind, &entities);
if new_segments != segments.body {
Some(new_segments)
} else {
None
}
}

pub fn update_changed_door(
doors: Query<
(Entity, &Edge<Entity>, &DoorType, &DoorSegments),
mut commands: Commands,
mut doors: Query<
(Entity, &Edge<Entity>, &DoorType, &mut DoorSegments),
Or<(Changed<Edge<Entity>>, Changed<DoorType>)>,
>,
anchors: AnchorParams,
mut transforms: Query<&mut Transform>,
mut mesh_handles: Query<&mut Handle<Mesh>>,
mut mesh_assets: ResMut<Assets<Mesh>>,
assets: Res<SiteAssets>,
) {
for (entity, edge, kind, segments) in &doors {
update_door_visuals(
for (entity, edge, kind, mut segments) in &mut doors {
if let Some(new_body) = update_door_visuals(
&mut commands,
entity,
edge,
kind,
segments,
&segments,
&anchors,
&mut transforms,
&mut mesh_handles,
&mut mesh_assets,
);
&assets,
) {
segments.body = new_body;
}
}
}

pub fn update_door_for_moved_anchors(
doors: Query<(Entity, &Edge<Entity>, &DoorType, &DoorSegments)>,
mut commands: Commands,
mut doors: Query<(Entity, &Edge<Entity>, &DoorType, &DoorSegments)>,
anchors: AnchorParams,
changed_anchors: Query<
&Dependents,
Expand All @@ -303,19 +434,22 @@ pub fn update_door_for_moved_anchors(
mut transforms: Query<&mut Transform>,
mut mesh_handles: Query<&mut Handle<Mesh>>,
mut mesh_assets: ResMut<Assets<Mesh>>,
assets: Res<SiteAssets>,
) {
for dependents in &changed_anchors {
for dependent in dependents.iter() {
if let Some((entity, edge, kind, segments)) = doors.get(*dependent).ok() {
if let Some((entity, edge, kind, mut segments)) = doors.get_mut(*dependent).ok() {
update_door_visuals(
&mut commands,
entity,
edge,
kind,
segments,
&segments,
&anchors,
&mut transforms,
&mut mesh_handles,
&mut mesh_assets,
&assets,
);
}
}
Expand Down
23 changes: 14 additions & 9 deletions rmf_site_editor/src/widgets/inspector/inspect_door.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ impl<'a> InspectDoorType<'a> {
});
});

fn left_right_ratio_ui(ui: &mut Ui, mut ratio: &mut f32) {
ui.horizontal(|ui| {
ui.label("Left : Right");
ui.add(
DragValue::new(ratio)
.speed(0.01)
.clamp_range(0.01..=std::f32::INFINITY),
)
.on_hover_text("(Left Door Length)/(Right Door Length)");
});
};

match &mut new_kind {
DoorType::SingleSliding(door) => {
ui.horizontal(|ui| {
Expand All @@ -57,15 +69,7 @@ impl<'a> InspectDoorType<'a> {
});
}
DoorType::DoubleSliding(door) => {
ui.horizontal(|ui| {
ui.label("Left : Right");
ui.add(
DragValue::new(&mut door.left_right_ratio)
.speed(0.01)
.clamp_range(0.0..=std::f32::INFINITY),
)
.on_hover_text("(Left Door Length)/(Right Door Length)");
});
left_right_ratio_ui(ui, &mut door.left_right_ratio);
}
DoorType::SingleSwing(door) => {
ui.horizontal(|ui| {
Expand All @@ -77,6 +81,7 @@ impl<'a> InspectDoorType<'a> {
}
DoorType::DoubleSwing(door) => {
InspectSwing::new(&mut door.swing).show(ui);
left_right_ratio_ui(ui, &mut door.left_right_ratio);
}
DoorType::Model(_) => {
ui.label("Not yet supported");
Expand Down
Loading