# map_scatter
[](https://github.com/morgenthum/map_scatter#license)
[](https://docs.rs/map_scatter)
[](https://crates.io/crates/map_scatter)
[](https://github.com/morgenthum/map_scatter/actions/workflows/ci.yml)
Rule-based object scattering library with field-graph evaluation and sampling.

## Overview
**map_scatter** is a fast, composable scattering library for games and tools. You define:
- Scalar fields as a directed acyclic graph (DAG), including textures and distance-field utilities.
- Candidate positions via a selection of samplers (Poisson disk, jittered grid, Halton, best-candidate, clustered, and more).
- A multi-layer plan describing which “kinds” can be placed, their probabilities, and gating rules.
At runtime, map_scatter evaluates the field graph over chunked grids with caching, selects valid placements according to your strategy, and optionally produces overlay textures for downstream layers.
### Highlights
- Field graph authoring and compilation into an efficient program
- Chunked evaluation with raster caching for speed
- Multiple sampling strategies for candidate generation
- Per-layer selection strategies (weighted random, highest probability)
- Optional overlay generation to feed subsequent layers
- Event stream for inspection, logging, and tooling
## Architecture
For a high-level architecture overview, see [ARCHITECTURE.md](./ARCHITECTURE.md).
## Status
This crate is actively developed. The core APIs are designed to be practical and composable for real projects. Feedback and contributions are welcome.
## Quick Start
Add the dependency:
```toml
[dependencies]
map_scatter = "0.2"
rand = "0.9"
glam = { version = "0.30", features = ["mint"] }
mint = "0.5"
```
Hello, scatter:
```rust
use glam::Vec2;
use rand::{SeedableRng, rngs::StdRng};
use map_scatter::prelude::*;
fn main() {
// 1) Author a field graph for a “kind”
// Here, we tag a constant=1.0 as the Probability field (always placeable).
let mut spec = FieldGraphSpec::default();
spec.add_with_semantics(
"probability",
NodeSpec::constant(1.0),
FieldSemantics::Probability,
);
let grass = Kind::new("grass", spec);
// 2) Build a layer using a sampling strategy (e.g., jittered grid)
let layer = Layer::new_with(
"layer_grass",
vec![grass],
JitterGridSampling::new(0.35, 5.0), // jitter, cell_size
)
// Optional: produce an overlay mask to reuse in later layers (name: "mask_layer_grass")
.with_overlay((256, 256), 3);
// 3) Assemble a plan (one or more layers)
let plan = Plan::new().with_layer(layer);
// 4) Prepare runtime
let mut cache = FieldProgramCache::new();
let textures = TextureRegistry::new(); // Register textures as needed
let cfg = RunConfig::new(Vec2::new(100.0, 100.0))
.with_chunk_extent(32.0)
.with_raster_cell_size(1.0)
.with_grid_halo(2);
// 5) Run
let mut rng = StdRng::seed_from_u64(42);
let mut runner = ScatterRunner::new(cfg, &textures, &mut cache);
let result = runner.run(&plan, &mut rng);
println!(
"Placed {} instances (evaluated: {}, rejected: {}).",
result.placements.len(),
result.positions_evaluated,
result.positions_rejected
);
}
```
Observing events:
```rust
use rand::{SeedableRng, rngs::StdRng};
use map_scatter::prelude::*;
fn run_with_events(plan: &Plan) {
let mut cache = FieldProgramCache::new();
let textures = TextureRegistry::new();
let cfg = RunConfig::new(glam::Vec2::new(64.0, 64.0));
let mut rng = StdRng::seed_from_u64(7);
let mut runner = ScatterRunner::new(cfg, &textures, &mut cache);
// Capture events for inspection (warnings, per-position evaluations, overlays, etc.)
let mut sink = VecSink::new();
let result = runner.run_with_events(plan, &mut rng, &mut sink);
for event in sink.into_inner() {
match event {
ScatterEvent::PlacementMade { placement, .. } => {
println!("Placed '{}' at {:?}", placement.kind_id, placement.position);
}
ScatterEvent::Warning { context, message } => {
eprintln!("[WARN] {context}: {message}");
}
_ => {}
}
}
println!("Total placed: {}", result.placements.len());
}
```
## Performance Notes
- Chunked evaluation: Keeps working sets small and cache-friendly.
- Raster cell size and chunk extent control performance/quality trade-offs.
- Field programs are cached and reused per (Kind, Chunk).
- Overlays are generated only when configured on the layer.
## API Tips
- Bring common types into scope with:
```rust
use map_scatter::prelude::*;
```
- Start simple: one kind with a constant Probability field, then introduce gates/overlays.
- Tune `RunConfig`:
- `chunk_extent`: larger chunks reduce overhead but can increase evaluation cost
- `raster_cell_size`: smaller cells improve accuracy at higher cost
- `grid_halo`: extra cells for filters/EDT at chunk borders
- Overlays: bridge layers by enabling `with_overlay`, then refer to the registered texture `mask_<layer_id>` in subsequent field graphs.
## Compatibility
- 2D domains (Vec2 positions); usable for 3D by feeding height/slope textures and augmenting the 2D placement with a height component in your engine
- No engine lock-in; pair with your renderer/tooling of choice
- Integrates well with `tracing` for diagnostics
- Use `rand` RNGs; examples commonly use `StdRng`
## Benchmarks
Some micro-benchmarks are included:
```bash
cargo bench -p map_scatter
```
## Roadmap
- Additional field nodes and utilities
- Advanced distance-field ops and compositors
- More sampling controls and distributions
- Higher-level authoring ergonomics
Contributions and ideas are welcome—please open issues or PRs.
## License
map_scatter is dual-licensed under either:
- MIT License ([LICENSE-MIT](https://github.com/morgenthum/map_scatter/blob/main/LICENSE-MIT))
- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/morgenthum/map_scatter/blob/main/LICENSE-APACHE))
at your option.
## Links
- Documentation: https://docs.rs/map_scatter
- Crate: https://crates.io/crates/map_scatter
- Repository: https://github.com/morgenthum/map_scatter