Skip to content

Commit

Permalink
Merge branch 'main' into emilk/better-redo-shortcut
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Dec 8, 2024
2 parents 39ffc9f + 7107267 commit 4d07a08
Show file tree
Hide file tree
Showing 14 changed files with 401 additions and 241 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ impl PoseRotationAxisAngle {
impl From<PoseRotationAxisAngle> for glam::Affine3A {
#[inline]
fn from(val: PoseRotationAxisAngle) -> Self {
Self::from_axis_angle(val.0.axis.into(), val.0.angle.radians())
if let Some(normalized) = glam::Vec3::from(val.0.axis).try_normalize() {
Self::from_axis_angle(normalized, val.0.angle.radians())
} else {
// If the axis is zero length, we can't normalize it, so we just use the identity rotation.
Self::IDENTITY
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ impl RotationAxisAngle {
impl From<RotationAxisAngle> for glam::Affine3A {
#[inline]
fn from(val: RotationAxisAngle) -> Self {
Self::from_axis_angle(val.0.axis.into(), val.0.angle.radians())
if let Some(normalized) = glam::Vec3::from(val.0.axis).try_normalize() {
Self::from_axis_angle(normalized, val.0.angle.radians())
} else {
// If the axis is zero length, we can't normalize it, so we just use the identity rotation.
Self::IDENTITY
}
}
}
9 changes: 8 additions & 1 deletion crates/viewer/re_renderer/src/renderer/mesh_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,8 +220,15 @@ impl MeshDrawData {
count_with_outlines += instance.outline_mask_ids.is_some() as u32;

let world_from_mesh_mat3 = instance.world_from_mesh.matrix3;
// If the matrix is not invertible the draw result is likely invalid as well.
// However, at this point it's really hard to bail out!
// Also, by skipping drawing here, we'd make the result worse as there would be no mesh draw calls that could be debugged.
let world_from_mesh_normal =
instance.world_from_mesh.matrix3.inverse().transpose();
if instance.world_from_mesh.matrix3.determinant() != 0.0 {
instance.world_from_mesh.matrix3.inverse().transpose()
} else {
glam::Mat3A::ZERO
};
instance_buffer_staging.push(gpu_data::InstanceData {
world_from_mesh_row_0: world_from_mesh_mat3
.row(0)
Expand Down
137 changes: 59 additions & 78 deletions crates/viewer/re_space_view_graph/src/layout/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,115 +27,86 @@ impl<'a> From<&'a NodeTemplate> for fj::Node {

pub struct ForceLayoutProvider {
simulation: fj::Simulation,
node_index: ahash::HashMap<NodeId, usize>,
pub request: LayoutRequest,
}

fn considered_edges(request: &LayoutRequest) -> Vec<(usize, usize)> {
let node_index: ahash::HashMap<NodeId, usize> = request
.all_nodes()
.enumerate()
.map(|(i, (id, _))| (id, i))
.collect();
request
.all_edges()
.filter(|(id, _)| !id.is_self_edge())
.map(|(id, _)| (node_index[&id.source], node_index[&id.target]))
.collect()
}

impl ForceLayoutProvider {
pub fn new(request: LayoutRequest) -> Self {
Self::new_impl(request, None)
}

pub fn new_with_previous(request: LayoutRequest, layout: &Layout) -> Self {
Self::new_impl(request, Some(layout))
}

// TODO(grtlr): Consider consuming the old layout to avoid re-allocating the extents.
// That logic has to be revised when adding the blueprints anyways.
fn new_impl(request: LayoutRequest, layout: Option<&Layout>) -> Self {
let nodes = request.graphs.iter().flat_map(|(_, graph_template)| {
graph_template.nodes.iter().map(|n| {
let mut fj_node = fj::Node::from(n.1);
if let Some(rect) = layout.and_then(|l| l.get_node(n.0)) {
let pos = rect.center();
fj_node = fj_node.position(pos.x as f64, pos.y as f64);
}

(n.0, fj_node)
})
});

let mut node_index = ahash::HashMap::default();
let all_nodes: Vec<fj::Node> = nodes
.enumerate()
.map(|(i, n)| {
node_index.insert(*n.0, i);
n.1
})
.collect();

let all_edges_iter = request
.graphs
.iter()
.flat_map(|(_, graph_template)| graph_template.edges.iter());

// Looking at self-edges does not make sense in a force-based layout, so we filter those out.
let considered_edges = all_edges_iter
.clone()
.filter(|(id, _)| !id.is_self_edge())
.map(|(id, _)| (node_index[&id.source], node_index[&id.target]));
let nodes = request.all_nodes().map(|(_, v)| fj::Node::from(v));
let edges = considered_edges(&request);

// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints.
let simulation = fj::SimulationBuilder::default()
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo
.build(all_nodes)
.add_force(
"link",
fj::Link::new(considered_edges).distance(50.0).iterations(2),
)
.build(nodes)
.add_force("link", fj::Link::new(edges).distance(50.0).iterations(2))
.add_force("charge", fj::ManyBody::new())
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away.
.add_force("x", fj::PositionX::new().strength(0.01))
.add_force("y", fj::PositionY::new().strength(0.01));

Self {
simulation,
node_index,
request,
}
}

pub fn init(&self) -> Layout {
let positions = self.simulation.positions().collect::<Vec<_>>();
let mut extents = ahash::HashMap::default();

for graph in self.request.graphs.values() {
for (id, node) in &graph.nodes {
let i = self.node_index[id];
let [x, y] = positions[i];
let pos = Pos2::new(x as f32, y as f32);
extents.insert(*id, Rect::from_center_size(pos, node.size));
pub fn new_with_previous(request: LayoutRequest, layout: &Layout) -> Self {
let nodes = request.all_nodes().map(|(id, v)| {
if let Some(rect) = layout.get_node(&id) {
let pos = rect.center();
fj::Node::from(v).position(pos.x as f64, pos.y as f64)
} else {
fj::Node::from(v)
}
}
});
let edges = considered_edges(&request);

// TODO(grtlr): Currently we guesstimate good forces. Eventually these should be exposed as blueprints.
let simulation = fj::SimulationBuilder::default()
.with_alpha_decay(0.01) // TODO(grtlr): slows down the simulation for demo
.build(nodes)
.add_force("link", fj::Link::new(edges).distance(50.0).iterations(2))
.add_force("charge", fj::ManyBody::new())
// TODO(grtlr): This is a small stop-gap until we have blueprints to prevent nodes from flying away.
.add_force("x", fj::PositionX::new().strength(0.01))
.add_force("y", fj::PositionY::new().strength(0.01));

Layout {
nodes: extents,
// Without any real node positions, we probably don't want to draw edges either.
edges: ahash::HashMap::default(),
entities: Vec::new(),
Self {
simulation,
request,
}
}

/// Returns `true` if finished.
pub fn tick(&mut self, layout: &mut Layout) -> bool {
self.simulation.tick(1);

let positions = self.simulation.positions().collect::<Vec<_>>();
fn layout(&self) -> Layout {
// We make use of the fact here that the simulation is stable, i.e. the
// order of the nodes is the same as in the `request`.
let mut positions = self.simulation.positions();

// We clear all unnecessary data from the previous layout, but keep its space allocated.
layout.entities.clear();
layout.edges.clear();
let mut layout = Layout::empty();

for (entity, graph) in &self.request.graphs {
let mut current_rect = Rect::NOTHING;

for node in graph.nodes.keys() {
let extent = layout.nodes.get_mut(node).expect("node has to be present");
let i = self.node_index[node];
let [x, y] = positions[i];
for (node, template) in &graph.nodes {
let [x, y] = positions.next().expect("positions has to match the layout");
let pos = Pos2::new(x as f32, y as f32);
extent.set_center(pos);
current_rect = current_rect.union(*extent);
let extent = Rect::from_center_size(pos, template.size);
current_rect = current_rect.union(extent);
layout.nodes.insert(*node, extent);
}

layout.entities.push((entity.clone(), current_rect));
Expand Down Expand Up @@ -248,6 +219,16 @@ impl ForceLayoutProvider {
}
}

layout
}

/// Returns `true` if finished.
pub fn tick(&mut self) -> Layout {
self.simulation.tick(1);
self.layout()
}

pub fn is_finished(&self) -> bool {
self.simulation.finished()
}
}
Expand Down
14 changes: 14 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,18 @@ impl LayoutRequest {

request
}

/// Returns all nodes from all graphs in this request.
pub(super) fn all_nodes(&self) -> impl Iterator<Item = (NodeId, &NodeTemplate)> + '_ {
self.graphs
.iter()
.flat_map(|(_, graph)| graph.nodes.iter().map(|(k, v)| (*k, v)))
}

/// Returns all edges from all graphs in this request.
pub(super) fn all_edges(&self) -> impl Iterator<Item = (EdgeId, &[EdgeTemplate])> + '_ {
self.graphs
.iter()
.flat_map(|(_, graph)| graph.edges.iter().map(|(k, v)| (*k, v.as_slice())))
}
}
9 changes: 9 additions & 0 deletions crates/viewer/re_space_view_graph/src/layout/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,15 @@ fn bounding_rect_from_iter(rectangles: impl Iterator<Item = egui::Rect>) -> egui
}

impl Layout {
/// Creates an empty layout
pub fn empty() -> Self {
Self {
nodes: ahash::HashMap::default(),
edges: ahash::HashMap::default(),
entities: Vec::new(),
}
}

/// Returns the bounding rectangle of the layout.
pub fn bounding_rect(&self) -> Rect {
// TODO(grtlr): We mostly use this for debugging, but we should probably
Expand Down
21 changes: 10 additions & 11 deletions crates/viewer/re_space_view_graph/src/ui/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,34 +105,33 @@ impl LayoutState {
}
// We need to recompute the layout.
Self::None => {
let provider = ForceLayoutProvider::new(new_request);
let layout = provider.init();

let mut provider = ForceLayoutProvider::new(new_request);
let layout = provider.tick();
Self::InProgress { layout, provider }
}
Self::Finished { layout, .. } => {
let mut provider = ForceLayoutProvider::new_with_previous(new_request, &layout);
let mut layout = provider.init();
provider.tick(&mut layout);

let layout = provider.tick();
Self::InProgress { layout, provider }
}
Self::InProgress {
layout, provider, ..
} if provider.request != new_request => {
let mut provider = ForceLayoutProvider::new_with_previous(new_request, &layout);
let mut layout = provider.init();
provider.tick(&mut layout);
let layout = provider.tick();

Self::InProgress { layout, provider }
}
// We keep iterating on the layout until it is stable.
Self::InProgress {
mut layout,
mut provider,
} => match provider.tick(&mut layout) {
layout,
} => match provider.is_finished() {
true => Self::Finished { layout, provider },
false => Self::InProgress { layout, provider },
false => Self::InProgress {
layout: provider.tick(),
provider,
},
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -555,8 +555,14 @@ fn query_and_resolve_tree_transform_at_entity(
if let Some(mat3x3) = result.component_instance::<TransformMat3x3>(0) {
transform *= glam::Affine3A::from(mat3x3);
}

if result.component_instance::<TransformRelation>(0) == Some(TransformRelation::ChildFromParent)
{
if transform.matrix3.determinant() != 0.0 {
// TODO(andreas): Should we warn? This might be intentionally caused by zero scale.
transform = transform.inverse();
}

transform = transform.inverse();
}

Expand Down
14 changes: 6 additions & 8 deletions crates/viewer/re_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,21 @@ re_log.workspace = true
re_log_types.workspace = true # syntax-highlighting for EntityPath
re_tracing.workspace = true

egui.workspace = true
eframe = { workspace = true, default-features = false, features = ["wgpu"] }
egui_commonmark = { workspace = true, features = ["pulldown_cmark"] }
egui_extras.workspace = true
egui_tiles.workspace = true
egui.workspace = true
once_cell.workspace = true
parking_lot.workspace = true
rand.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
strum.workspace = true
smallvec.workspace = true
strum_macros.workspace = true
strum.workspace = true
sublime_fuzzy.workspace = true


eframe = { workspace = true, default-features = false, features = ["wgpu"] }
egui_tiles.workspace = true
rand.workspace = true
smallvec.workspace = true


[dev-dependencies]
egui_kittest.workspace = true
Loading

0 comments on commit 4d07a08

Please sign in to comment.