Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- name: Update Packages
run: sudo apt-get update -yq
- name: Install dependencies
run: sudo apt-get install -yq --no-install-recommends libudev-dev libasound2-dev libxcb-composite0-dev
run: sudo apt-get install -yq --no-install-recommends g++ pkg-config libx11-dev libasound2-dev libudev-dev libxkbcommon-x11-0 libwayland-dev libxkbcommon-dev
format:
runs-on: ubuntu-latest
needs: [setup]
Expand Down
22 changes: 12 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,29 @@ bevy_time = { version = "0.17.1", default-features = false, optional = true }
libm = { version = "0.2", default-features = false, optional = true }

[dev-dependencies]

bevy = { version = "0.17.1", default-features = false, features = [
"bevy_scene",
"bevy_render",
"bevy_post_process",
"animation",
"bevy_asset",
"bevy_color",
"bevy_gizmos",
"bevy_gltf",
"bevy_log",
"bevy_pbr",
"bevy_post_process",
"bevy_render",
"bevy_scene",
"bevy_remote",
"bevy_winit",
"default_font",
"bevy_ui",
"bevy_ui_render",
"bevy_pbr",
"bevy_gizmos",
"animation",
"bevy_window",
"x11",
"tonemapping_luts",
"bevy_winit",
"default_font",
"multi_threaded",
"png",
"reflect_auto_register",
"tonemapping_luts",
"x11",
"zstd_rust"
] }
noise = "0.9"
Expand Down
10 changes: 2 additions & 8 deletions examples/spatial_hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use core::hash::Hasher;
use noise::{NoiseFn, Simplex};
use turborand::prelude::*;

// Try bumping this up to really stress test. I'm able to push a million entities with an M3 Max.
// Try bumping this up to stress-test. I'm able to push a million entities with an M3 Max.
const HALF_WIDTH: f32 = 50.0;
const CELL_WIDTH: f32 = 10.0;
// How fast the entities should move, causing them to move into neighboring cells.
Expand All @@ -27,8 +27,6 @@ fn main() {
PartitionPlugin::default(),
PartitionChangePlugin::default(),
))
.add_plugins(bevy::remote::RemotePlugin::default()) // Core remote protocol
.add_plugins(bevy::remote::http::RemoteHttpPlugin::default()) // Enable HTTP transport
.add_systems(Startup, (spawn, setup_ui))
.add_systems(
PostUpdate,
Expand Down Expand Up @@ -390,27 +388,24 @@ fn cursor_grab(
}

// Highlight entities that changed partitions by setting their material to bright red
// and keep the highlight for 10 frames.
fn highlight_changed_entities(
mut materials: Query<&mut MeshMaterial3d<StandardMaterial>>,
material_presets: Res<MaterialPresets>,
entity_partitions: Res<PartitionChange>,
entity_partitions: Res<PartitionEntities>,
// Track highlighted entities with a countdown of remaining frames
mut active: Local<Vec<(Entity, u8)>>,
) {
// We'll rebuild the active list each frame, carrying forward countdowns
let mut next_active: Vec<(Entity, u8)> =
Vec::with_capacity(active.len() + entity_partitions.changed.len());

// 1) Apply new changes: set material to changed and (re)start countdown at 10
for entity in entity_partitions.changed.keys().copied() {
if let Ok(mut mat) = materials.get_mut(entity) {
mat.set_if_neq(material_presets.changed.clone().into());
}
next_active.push((entity, 10));
}

// 2) Carry over previous active highlights that weren't refreshed this frame
for (entity, mut frames_left) in active.drain(..) {
// If the entity also changed this frame, it's already added with 10 above
if entity_partitions.changed.contains_key(&entity) {
Expand All @@ -433,6 +428,5 @@ fn highlight_changed_entities(
}
}

// 3) Replace the active list with the updated one
*active = next_active;
}
32 changes: 27 additions & 5 deletions src/grid/cell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ use bevy_platform::time::Instant;
use bevy_reflect::prelude::*;
use bevy_transform::prelude::*;

/// The coordinate of the cell this entity is located in, within its parent's [`Grid`]. The
/// [`Transform`] of an entity with this component is relative to the center of this cell.
/// The integer coordinate of a cubic cell in a [`Grid`].
///
/// Locates an entity in a cell within its parent's [`Grid`]. The [`Transform`] of an entity with
/// this component is a transformation relative to the center of this cell.
///
/// All entities with a [`CellCoord`] must be children of an entity with a [`Grid`].
///
Expand All @@ -24,11 +26,11 @@ use bevy_transform::prelude::*;
#[reflect(Component, Default, PartialEq)]
#[require(Transform, GlobalTransform)]
pub struct CellCoord {
/// The x-index of the cell.
/// X coordinate of a cell in its parent [`Grid`].
pub x: GridPrecision,
/// The y-index of the cell.
/// Y coordinate of a cell in its parent [`Grid`].
pub y: GridPrecision,
/// The z-index of the cell.
/// Z coordinate of a cell in its parent [`Grid`].
pub z: GridPrecision,
}

Expand Down Expand Up @@ -236,3 +238,23 @@ impl core::ops::Mul<GridPrecision> for &CellCoord {
(*self).mul(rhs)
}
}

impl core::ops::Div<GridPrecision> for CellCoord {
type Output = CellCoord;

fn div(self, rhs: GridPrecision) -> Self::Output {
CellCoord {
x: self.x / rhs,
y: self.y / rhs,
z: self.z / rhs,
}
}
}

impl core::ops::Div<GridPrecision> for &CellCoord {
type Output = CellCoord;

fn div(self, rhs: GridPrecision) -> Self::Output {
(*self).div(rhs)
}
}
2 changes: 1 addition & 1 deletion src/hash/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ pub enum SpatialHashSystems {
UpdateCellLookup,
/// [`PartitionLookup`] updated.
UpdatePartitionLookup,
/// [`PartitionChange`] updated.
/// [`PartitionEntities`] updated.
UpdatePartitionChange,
}

Expand Down
24 changes: 12 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

//! A floating origin plugin that uses integer grids to extend bevy's [`Transform`] component with
//! up to 128 bits of added precision. The plugin propagates and computes [`GlobalTransform`]s
//! relative to floating origins, making the most of 32 bit rendering precision by reducing error
//! relative to floating origins, making the most of 32-bit rendering precision by reducing error
//! near the camera.
//!
//! <img src="https://raw.githubusercontent.com/aevyrie/big_space/refs/heads/main/assets/bigspacebanner.svg" style="padding:2% 15%">
Expand Down Expand Up @@ -81,9 +81,9 @@
//! from the origin of the current grid cell.
//! - High precision coordinates are invisible if you don't need them. You can move objects using
//! their `Transform` alone, which results in decent ecosystem compatibility.
//! - High precision is completely opt-in. If you don't add the `GridCell` component to an entity,
//! - High precision coordinates optional. If you don't add the `GridCell` component to an entity,
//! it behaves like a normal single precision transform, with the same performance cost, yet it
//! can exist in the high precision hierarchy. This allows you to load in GLTFs or other
//! can exist in the high-precision hierarchy. This allows you to load in GLTFs or other
//! low-precision entity hierarchies with no added effort or cost.
//!
//! While using the [`BigSpaceDefaultPlugins`], the position of entities is now defined with the [`Grid`],
Expand All @@ -92,15 +92,15 @@
//! `Transform` is used to position the entity relative to the center of its `GridCell`. If an
//! entity moves into a neighboring cell, its transform will be automatically recomputed relative to
//! the center of that new cell. This prevents `Transforms` from ever becoming larger than a single
//! grid cell, and thus prevents floating point precision artifacts.
//! grid cell and thus prevents floating point precision artifacts.
//!
//! The grid adds precision to your transforms. If you are using (32-bit) `Transform`s on an `i32`
//! grid, you will have 64 bits of precision: 32 bits to address into a large integer grid, and 32
//! bits of floating point precision within a grid cell. This plugin is generic up to `i128` grids,
//! giving you up to 160 bits of precision of translation.
//!
//! `Grid`s can be nested, like `Transform`s. This allows you to define moving grids, which can make
//! certain use cases much simpler. For example, if you have a planet rotating, and orbiting around
//! certain use cases much simpler. For example, if you have a planet rotating and orbiting around
//! its star, it would be very annoying if you had to compute this orbit and rotation for all
//! objects on the surface in high precision. Instead, you can place the planet and all objects on
//! its surface in the same grid. The motion of the planet will be inherited by all children in that
Expand All @@ -119,34 +119,34 @@
//!
//! All of the above applies to the entity marked with the [`FloatingOrigin`] component. The
//! floating origin can be any high-precision entity in a `BigSpace`, it doesn't need to be a
//! camera. The only thing special about the entity marked as the floating origin, is that it is
//! camera. The only thing special about the entity marked as the floating origin is that it is
//! used to compute the [`GlobalTransform`] of all other entities in that `BigSpace`. To an outside
//! observer, every high-precision entity within a `BigSpace` is confined to a box the size of a
//! grid cell - like a game of *Asteroids*. Only once you render the `BigSpace` from the point of
//! view of the floating origin, by calculating their `GlobalTransform`s, do entities appear very
//! distant from the floating origin.
//!
//! As described above. the `GlobalTransform` of all entities is computed relative to the floating
//! As described above, the `GlobalTransform` of all entities is computed relative to the floating
//! origin's grid cell. Because of this, entities very far from the origin will have very large,
//! imprecise positions. However, this is always relative to the camera (floating origin), so these
//! artifacts will always be too far away to be seen, no matter where the camera moves. Because this
//! only affects the `GlobalTransform` and not the `Transform`, this also means that entities will
//! never permanently lose precision just because they were far from the origin at some point. The
//! lossy calculation only occurs when computing the `GlobalTransform` of entities, the high
//! precision `GridCell` and `Transform` are not affected.
//! lossy calculation only occurs when computing the `GlobalTransform` of entities, the
//! high-precision `GridCell` and `Transform` are not affected.
//!
//! # Usage
//!
//! To start using this plugin, you will first need to choose how big your world should be! Do you
//! need an i8, or an i128? See [`precision`] for more details and documentation.
//!
//! 1. Add the [`BigSpaceDefaultPlugins`] to your `App`
//! 2. Spawn a [`BigSpace`] with [`spawn_big_space`](BigSpaceCommands::spawn_big_space), and add
//! 2. Spawn a [`BigSpace`] with [`spawn_big_space`](BigSpaceCommands::spawn_big_space) and add
//! entities to it.
//! 3. Add the [`FloatingOrigin`] to your active camera in the [`BigSpace`].
//!
//! To add more levels to the hierarchy, you can use [`Grid`]s, which themselves can contain
//! high-precision spatial entities. Grids have the same propagation behavior as `Transform`s, but
//! high-precision spatial entities. Grids have the same propagation behavior as `Transform`s but
//! with higher precision.
//!
//! Take a look at the [`Grid`] component for some useful helper methods. The component defines the
Expand Down Expand Up @@ -244,7 +244,7 @@ pub mod prelude {
CellHashingPlugin, SpatialHashSystems,
};
pub use partition::{
change_tracking::{PartitionChange, PartitionChangePlugin},
change_tracking::{PartitionChangePlugin, PartitionEntities},
map::PartitionLookup,
Partition, PartitionId, PartitionPlugin,
};
Expand Down
20 changes: 6 additions & 14 deletions src/partition/change_tracking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ impl Default for PartitionChangePlugin<()> {

impl<F: SpatialHashFilter> Plugin for PartitionChangePlugin<F> {
fn build(&self, app: &mut App) {
app.init_resource::<PartitionChange<F>>().add_systems(
app.init_resource::<PartitionEntities<F>>().add_systems(
PostUpdate,
PartitionChange::<F>::update
PartitionEntities::<F>::update
.in_set(SpatialHashSystems::UpdatePartitionChange)
.after(SpatialHashSystems::UpdatePartitionLookup),
);
Expand All @@ -48,15 +48,15 @@ impl<F: SpatialHashFilter> Plugin for PartitionChangePlugin<F> {
///
/// This only works if the [`PartitionChangePlugin`] has been added.
#[derive(Resource)]
pub struct PartitionChange<F: SpatialHashFilter = ()> {
pub struct PartitionEntities<F: SpatialHashFilter = ()> {
/// Current mapping of an entity to its partition as of the last update.
pub map: EntityHashMap<PartitionId>,
/// Entities that have changed partition in the last update.
pub changed: EntityHashMap<(Option<PartitionId>, Option<PartitionId>)>,
spooky: PhantomData<F>,
}

impl<F: SpatialHashFilter> FromWorld for PartitionChange<F> {
impl<F: SpatialHashFilter> FromWorld for PartitionEntities<F> {
fn from_world(_world: &mut World) -> Self {
Self {
map: Default::default(),
Expand All @@ -66,14 +66,14 @@ impl<F: SpatialHashFilter> FromWorld for PartitionChange<F> {
}
}

impl<F: SpatialHashFilter> PartitionChange<F> {
impl<F: SpatialHashFilter> PartitionEntities<F> {
fn update(
mut entity_partitions: ResMut<Self>,
cells: Res<CellLookup<F>>,
changed_cells: Res<ChangedCells<F>>,
all_hashes: Query<(Entity, &CellId), F>,
mut old_reverse: Local<CellHashMap<PartitionId>>,
partitions: Res<PartitionLookup>,
partitions: Res<PartitionLookup<F>>,
) {
entity_partitions.changed.clear();

Expand Down Expand Up @@ -151,14 +151,6 @@ impl<F: SpatialHashFilter> PartitionChange<F> {
}
}
}
// Entities present but not in the map yet are inserted with their current partition.
for (entity_id, cell_hash) in all_hashes.iter() {
if !entity_partitions.map.contains_key(&entity_id) {
if let Some(pid) = partitions.get(cell_hash) {
entity_partitions.map.insert(entity_id, *pid);
}
}
}

// Snapshot the cell->partition mapping to compute deltas next update
*old_reverse = partitions.reverse_map.clone();
Expand Down
21 changes: 18 additions & 3 deletions src/partition/map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,24 @@ use core::ops::Deref;

/// A resource for quickly finding connected groups of occupied grid cells in [`Partition`]s.
///
/// Partitions divide space into independent groups of cells.
///
/// The map is built from a [`CellLookup`] resource with the same `F:`[`SpatialHashFilter`].
///
/// Partitions are built on top of [`CellLookup`], only dealing with
/// [`CellCoord`](crate::grid::cell::CellCoord)s. For performance reasons, partitions do not track
/// grid occupancy at the `Entity` level. Instead, partitions are only concerned with which cells
/// are occupied. To find what entities are present, you will need to look up each of the
/// partition's [`CellId`]s in the [`CellLookup`]
#[derive(Resource)]
pub struct PartitionLookup<F = ()>
where
F: SpatialHashFilter,
{
pub(crate) partitions: HashMap<PartitionId, Partition>,
partitions: HashMap<PartitionId, Partition>,
pub(crate) reverse_map: CellHashMap<PartitionId>,
pub(crate) next_partition: u64,
pub(crate) spooky: PhantomData<F>,
next_partition: u64,
spooky: PhantomData<F>,
}

impl<F> Default for PartitionLookup<F>
Expand Down Expand Up @@ -76,6 +84,13 @@ where
pub fn iter(&self) -> impl Iterator<Item = (&PartitionId, &Partition)> {
self.partitions.iter()
}

/// Searches for the [`Partition`] that contains this cell, returning the partition's
/// [`PartitionId`] if the cell is found in any partition.
#[inline]
pub fn get_partition(&self, partition: &PartitionId) -> Option<&Partition> {
self.partitions.get(partition)
}
}

/// Private methods
Expand Down
Loading
Loading