pub mod cull;
pub mod pick;
pub mod rte;
pub mod source;
pub use cull::{CullIndex, CullReport};
pub use pick::{LayerId, PickHit, PickId, PickIndex, PickShape};
pub use rte::{RtePos, Split};
pub use source::{
Band, Bands, ElevationSource, Flat, Flatten, Hierarchy, Identity, Layout, Metric, PositionSource,
Projected, Sampled, WorldAabb, WorldPos,
};
use crate::legibility::{FrameInput, FrameOpts, FramePlan, plan_frame};
use egui::{Pos2, Rect, Vec2};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct View {
pub centre: WorldPos,
pub scale: f64,
pub viewport: Rect,
pub epoch: u64,
}
impl Default for View {
fn default() -> Self {
Self {
centre: WorldPos::default(),
scale: 1.0,
viewport: Rect::from_min_size(Pos2::ZERO, Vec2::new(1.0, 1.0)),
epoch: 0,
}
}
}
impl View {
#[must_use]
pub fn to_screen(&self, p: WorldPos) -> Pos2 {
let c = self.viewport.center();
Pos2::new(
c.x + ((p.x - self.centre.x) * self.scale) as f32,
c.y + ((p.y - self.centre.y) * self.scale) as f32,
)
}
#[must_use]
pub fn to_world(&self, p: Pos2) -> WorldPos {
let c = self.viewport.center();
let s = if self.scale.abs() < f64::MIN_POSITIVE { 1.0 } else { self.scale };
WorldPos::flat(self.centre.x + (p.x - c.x) as f64 / s, self.centre.y + (p.y - c.y) as f64 / s)
}
#[must_use]
pub fn world_aabb(&self) -> WorldAabb {
let a = self.to_world(self.viewport.min);
let b = self.to_world(self.viewport.max);
WorldAabb {
min: WorldPos::flat(a.x.min(b.x), a.y.min(b.y)),
max: WorldPos::flat(a.x.max(b.x), a.y.max(b.y)),
}
}
#[must_use]
pub fn eye_rte(&self) -> RtePos {
RtePos::of(self.centre)
}
}
pub struct LayerRef<'a> {
pub id: LayerId,
pub position: &'a dyn PositionSource,
pub elevation: &'a dyn ElevationSource,
pub hierarchy: &'a dyn Hierarchy,
pub pick_shape: pick::PickShape,
pub label_size: &'a [Vec2],
pub label_priority: &'a [f32],
pub label_pinned: &'a [bool],
pub edges: &'a [(u32, u32)],
}
impl<'a> LayerRef<'a> {
#[must_use]
pub fn new(
id: LayerId,
position: &'a dyn PositionSource,
elevation: &'a dyn ElevationSource,
hierarchy: &'a dyn Hierarchy,
) -> Self {
Self {
id,
position,
elevation,
hierarchy,
pick_shape: pick::PickShape::default(),
label_size: &[],
label_priority: &[],
label_pinned: &[],
edges: &[],
}
}
#[must_use]
pub fn generation(&self) -> u64 {
self.position.generation()
}
}
#[derive(Clone, Debug, Default)]
pub struct LayerPlan {
pub id: LayerId,
pub visible: Vec<u32>,
pub screen: Vec<Pos2>,
pub elevation: Vec<f64>,
pub level: u32,
pub cull: CullReport,
}
#[derive(Debug, Default)]
pub struct EnginePlan {
pub layers: Vec<LayerPlan>,
pub frame: FramePlan,
pub pick: PickIndex,
}
impl EnginePlan {
#[must_use]
pub fn state_json(&self) -> serde_json::Value {
serde_json::json!({
"layers": self.layers.iter().map(|l| serde_json::json!({
"id": l.id,
"visible": l.visible.len(),
"level": l.level,
"cull": l.cull.state_json(),
})).collect::<Vec<_>>(),
"frame": self.frame.report.state_json(),
"pick_layers": self.pick.layer_count(),
})
}
#[must_use]
pub fn visible(&self) -> usize {
self.layers.iter().map(|l| l.visible.len()).sum()
}
}
#[derive(Debug, Default)]
pub struct EngineCache {
indices: Vec<(LayerId, u64, CullIndex)>,
}
impl EngineCache {
fn index_for(&mut self, layer: &LayerRef<'_>, level: u32) -> &CullIndex {
let key = layer.generation() ^ ((level as u64) << 48);
let slot = self.indices.iter().position(|(id, k, _)| *id == layer.id && *k == key);
let pos = match slot {
Some(i) => i,
None => {
let members = layer.hierarchy.members(level);
let items: Vec<(u32, WorldAabb)> =
members.iter().map(|&f| (f, layer.position.bounds(f))).collect();
let idx = CullIndex::build(&items);
if let Some(existing) = self.indices.iter().position(|(id, _, _)| *id == layer.id) {
self.indices[existing] = (layer.id, key, idx);
existing
} else {
self.indices.push((layer.id, key, idx));
self.indices.len() - 1
}
}
};
&self.indices[pos].2
}
#[must_use]
pub fn len(&self) -> usize {
self.indices.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.indices.is_empty()
}
}
#[must_use]
pub fn plan(
layers: &[LayerRef<'_>],
view: &View,
opts: FrameOpts,
cache: &mut EngineCache,
) -> EnginePlan {
let mut out = EnginePlan::default();
let view_box = view.world_aabb();
let mut world = Vec::new();
for layer in layers {
let level = layer.hierarchy.level_for(view.scale as f32);
let idx = cache.index_for(layer, level);
let mut visible = Vec::new();
let mut cull_rep = idx.query(view_box, &mut visible);
for &f in idx.oversize() {
if !visible.contains(&f) {
visible.push(f);
cull_rep.oversize_forced += 1;
}
}
cull_rep.visible = visible.len();
layer.position.positions(&visible, &mut world);
let mut elevation = Vec::new();
if layer.elevation.z_range() != (0.0, 0.0) {
layer.elevation.elevations(&visible, &world, &mut elevation);
for (w, &z) in world.iter_mut().zip(elevation.iter()) {
w.z = z;
}
} else {
elevation.clear();
elevation.resize(visible.len(), 0.0);
}
let screen: Vec<Pos2> = world.iter().map(|&w| view.to_screen(w)).collect();
out.layers.push(LayerPlan { id: layer.id, visible, screen, elevation, level, cull: cull_rep });
}
let total: usize = out.layers.iter().map(|l| l.screen.len()).sum();
let mut centres = Vec::with_capacity(total);
let mut label_size = Vec::with_capacity(total);
let mut label_priority = Vec::with_capacity(total);
let mut label_pinned = Vec::with_capacity(total);
let mut edges: Vec<(u32, u32)> = Vec::new();
let mut offset = 0u32;
for (layer, lp) in layers.iter().zip(out.layers.iter()) {
let mut rank = std::collections::HashMap::with_capacity(lp.visible.len());
for (i, &f) in lp.visible.iter().enumerate() {
rank.insert(f, offset + i as u32);
}
for (i, &f) in lp.visible.iter().enumerate() {
centres.push(lp.screen[i]);
let fi = f as usize;
label_size.push(layer.label_size.get(fi).copied().unwrap_or(Vec2::ZERO));
label_priority.push(layer.label_priority.get(fi).copied().unwrap_or(0.0));
label_pinned.push(layer.label_pinned.get(fi).copied().unwrap_or(false));
}
for &(a, b) in layer.edges {
if let (Some(&pa), Some(&pb)) = (rank.get(&a), rank.get(&b)) {
edges.push((pa, pb));
}
}
offset += lp.visible.len() as u32;
}
out.frame = plan_frame(
&FrameInput {
centres: ¢res,
label_size: &label_size,
label_priority: &label_priority,
label_pinned: &label_pinned,
edges: &edges,
edge_always: &[],
edge_weight: &[],
viewport: view.viewport,
},
opts,
);
let mut key = view.epoch;
for l in layers {
key = key.rotate_left(7) ^ l.generation();
}
out.pick.begin(key);
for (layer, lp) in layers.iter().zip(out.layers.iter()) {
out.pick.push_layer(layer.id, &lp.screen, lp.visible.clone(), layer.pick_shape);
}
out
}
#[cfg(test)]
mod tests;