Cursor cross
I added a cursor cross to make it easier to align Structure
s. This is especially handy when placing Belt
s.
Structure container improvements
Over the past weeks I applied several improvements to how Structure
s are tracked.
Deduplicate code
Initially both the Blueprint
and Planet
kept track of their Structure
s individually.
While there was a shared container type both were using, the Planet
tracked more data as part of various optimizations.
I moved all that into StructContainer
so Blueprint
could also benefit from existing and future optimizations.
Quad tree for find and visibility
I previously optimized the rendering to only consider visibile Structure
s. This was a great improvement, but still had to iterate all Structure
s:
for (id, element) in self.planet.elements().iter().enumerate() {
if Self::is_visible(self.module_store, ¤t_area, &element) {
//render here
With a QuadTree as member of the StructContainer
it’s now possible to query it for Structure
s in an area:
impl StructContainer {
...
pub fn for_all_in<F>(&self, area: &Area, f: &mut F)
where
F: FnMut(&PosElement),
{
...
self.pos_tree.borrow().for_all_in(area, f);
}
...
}
Which can then be used during rendering:
self.planet
.structs()
.for_all_in(¤t_area, &mut |x: &PosElement| {
//render here
It’s now also used to find Structure
s by position:
impl StructContainer {
...
pub fn get_id(&self, ms: &dyn MS, pos: &Pos<i64>) -> Option<StructureID> {
...
self.pos_tree
.borrow()
.find(pos.x as f64, pos.y as f64)
.map(|e| e.id)
}
...
}
Position storage
Before using the QuadTree
as described above, the positions were stored in a map:
pub struct StructContainer {
positions: BTreeMap<Pos<i64>, StructureID>,
...
}
Since all lookups now happen via the tree, there’s no need to store the positions separately anymore, since Structure
s are already stored with their positions attached:
pub struct ContainerElement {
pub pos: PosAnchor<i64>,
pub rot: Rot,
pub sm: StructureModule,
}
...
pub struct StructContainer {
elements: Vec<ContainerElement>,
...
}
Dedicated visibility tree
The above approach still had one major flaw:
Structure
s that are not inside the current view aren’t rendered. This sounds like the expected result, but some Structure
s have a greater visual impact than their size. For example the Influence
has only a size of 1x1
, but has a large visual indicator for the Influence
area.
To keep track of that I extended Structure
s with:
fn visibility_range(&self, ms: &dyn MS) -> u32;
And am using an AABB-Tree
(axis aligned bounding box) to keep track of of the visibilities:
pub struct VisibilityElement {
pub pos: Pos<i64>,
pub range: u32,
pub id: StructureID,
}
...
pub struct StructContainer {
...
vis_tree: RefCell<AABBTree2D<VisibilityElement>>,
...
}
which is now used for the visibility check during rendering instead of the QuadTree
.
Dirty flags
I also introduced dirty flags for all the search structures to avoid unnecessary recalculation of those.
pub struct StructContainer {
...
vis_tree_dirty: RefCell<bool>,
...
}
...
impl StructContainer {
...
pub fn add(&mut self, ms: &dyn MS, element: ContainerElement) -> AddResult {
...
*self.vis_tree_dirty.borrow_mut() = true;
Ok(())
}
...
pub fn for_all_visible_in<F>(&self, ms: &dyn MS, area: &Area, f: &mut F)
where
F: FnMut(&VisibilityElement),
{
self.update_search_trees(ms);
...
self.vis_tree.borrow().for_each_collision_candidate(&bb, f)
}
...
fn update_search_trees(&self, ms: &dyn MS) {
...
if *self.vis_tree_dirty.borrow() {
...
*self.vis_tree_dirty.borrow_mut() = false;
}
}
}