Skip to content

Commit

Permalink
Changed resource-based to component-based ergonomics. (#17)
Browse files Browse the repository at this point in the history
* testing removal of ImageCopiers list resource

* back to image_copier list strategy

* pre-ratatuicamera commit

* added some revised component-based code

* old functionality working after major refactor

* working GPU machinery, shader in progress

* working edge detection, character selection still funky

* moved edge detect config into component

* edge character preferences, uniforms hooked up

* cleaned up readback component systems

* fixed multiple example

* switched to observers/hooks for sender/receivers

* autoprint system

* finished documentation, small adjustments

* refactored examples

* updated README for refactor

* separated strategy from camera struct

* move edge detection component to separate file
  • Loading branch information
cxreiff authored Dec 23, 2024
1 parent 36010bd commit 108320b
Show file tree
Hide file tree
Showing 25 changed files with 2,057 additions and 1,612 deletions.
105 changes: 105 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ ratatui = "0.29.0"
ratatui-image = "3.0.0"
bevy_ratatui = "0.7.0"
image = "0.25.5"
bevy_mod_debugdump = "0.12.0"
log = "0.4.22"

[dev-dependencies]
tui-logger = "0.14.0"

[profile.dev]
opt-level = 1
Expand Down
117 changes: 71 additions & 46 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Bevy inside the terminal!

Uses bevy headless rendering, [ratatui](https://github.com/ratatui-org/ratatui), and
[ratatui_image](https://github.com/benjajaja/ratatui-image) to print the rendered output
of your bevy application to the terminal using unicode halfblocks.
[ratatui_image](https://github.com/benjajaja/ratatui-image) to print your bevy application's
rendered frames to the terminal.

<p float="left">
<img src="https://assets.cxreiff.com/github/cube.gif" width="30%" alt="cube">
Expand All @@ -17,6 +17,9 @@ of your bevy application to the terminal using unicode halfblocks.
Use [bevy_ratatui](https://github.com/joshka/bevy_ratatui/tree/main) for setting ratatui up
and receiving terminal events (keyboard, focus, mouse, paste, resize) inside bevy.

> [!IMPORTANT]
> This crate was renamed from `bevy_ratatui_render` to `bevy_ratatui_camera`.
## getting started

`cargo add bevy_ratatui_render bevy_ratatui`
Expand All @@ -25,91 +28,113 @@ and receiving terminal events (keyboard, focus, mouse, paste, resize) inside bev
fn main() {
App::new()
.add_plugins((
// disable WinitPlugin as it panics in environments without a display server
// disable WinitPlugin as it panics in environments without a display server.
// disable LogPlugin as it interferes with terminal output.
DefaultPlugins.build()
.disable::<WinitPlugin>()
.disable::<LogPlugin>(),

// create windowless loop and set its frame rate
// create windowless loop and set its frame rate.
ScheduleRunnerPlugin::run_loop(Duration::from_secs_f64(1. / 60.)),

// set up the Ratatui context and forward input events
// set up the Ratatui context and forward terminal input events.
RatatuiPlugins::default(),

// connect a bevy camera target to a ratatui widget
RatatuiRenderPlugin::new("main", (256, 256)),
// add the ratatui camera plugin.
RatatuiCameraPlugin,
))
.add_systems(Startup, setup_scene_system)
.add_systems(Update, draw_scene_system.map(error))
.run();
.add_systems(PostUpdate, draw_scene_system.map(error));
}

fn setup_scene_system(
mut commands: Commands,
ratatui_render: Res<RatatuiRenderContext>,
) {
// spawn objects into your scene, and your camera

...

// add RatatuiCamera to your scene's camera.
fn setup_scene_system(mut commands: Commands) {
commands.spawn((
Camera3d::default(),
Camera {
target: ratatui_render.target("main").unwrap(),
..default()
},
RatatuiCamera::default(),
));
}

// a RatatuiCameraWidget component will be available in your camera entity.
fn draw_scene_system(
mut ratatui: ResMut<RatatuiContext>,
ratatui_render: Res<RatatuiRenderContext>,
) -> io::Result<()> {
camera_widget: Query<&RatatuiCameraWidget>,
) -> std::io::Result<()> {
ratatui.draw(|frame| {
frame.render_widget(ratatui_render.widget("main").unwrap(), frame.size());
frame.render_widget(camera_widget.single(), frame.area());
})?;

Ok(())
}
```

As shown above, `RatatuiRenderPlugin` makes a `RatatuiRenderContext` resource available that has two
primary methods:
As shown above, when `RatatuiCameraPlugin` is added to your application, any bevy camera entities that you
add a `RatatuiCamera` component to, will have a `RatatuiCameraWidget` inserted that you can query for. Each
`RatatuiCameraWidget` is a ratatui widget that when drawn will print the most recent frame rendered by the
associated bevy camera, as unicode characters.

- `target(id)`: Provides a bevy `RenderTarget` that can be set as the `target` of a normal bevy camera.
- `widget(id)`: Provides a ratatui widget that prints the latest render made to the corresponding camera,
as unicode half-blocks.
## strategies

There is a convenience function if you do not need access to the ratatui draw loop and just would
like the render to print to the full terminal (for the above example, use this instead of adding the
`draw_scene_system`):
The method by which the rendered image is converted into unicode characters depends on the
`RatatuiCameraStrategy` that you choose. Insert a variant of the component alongside the `RatatuiCamera` to
change the behavior from the default. Refer to the `RatatuiCameraStrategy` documentation for descriptions
of each variant.

For example, to use the "Luminance" strategy:

```rust
RatatuiRenderPlugin::new("main", (256, 256)).print_full_terminal()
commands.spawn((
Camera3d::default(),
RatatuiCamera::default(),
RatatuiCameraStrategy::Luminance(LuminanceConfig::default()),
));
```

There is another convenience function for autoresizing the render texture to match the terminal
dimensions, during startup and when the terminal is resized:
## autoresize

By default, the size of the texture the camera renders to will stay constant, and when rendered to the ratatui
buffer it will be resized to fit the available area while retaining its aspect ratio. If you set the
`autoresize` attribute to true, the render texture will instead be resized to fit the terminal window.

You can also supply an optional `autoresize_function` that converts the terminal dimensions to the dimensions
that will be used for resizing. This is useful for situations when you want to maintain a specific aspect ratio
or resize to some fraction of the terminal window.

```rust
RatatuiRenderPlugin::new("main", (1, 1)).autoresize()
RatatuiCamera {
autoresize: true,
autoresize_fn: |(w, h)| (w * 4, h * 3),
..default()
}
```

To customize how the texture dimensions are calculated from the terminal dimensions, provide a callback
to `autoresize_conversion_fn`:
## edge detection

When using the `RatatuiCameraStrategy::Luminance` strategy and a 3d camera, you can also optionally insert a
`RatatuiCameraEdgeDetection` component into your camera in order to add an edge detection step in the render
graph. When printing to the ratatui buffer, special characters and an override color can be used based on the
detected edges and their directions. This can be useful for certain visual effects, and distinguishing detail
when the text rendering causes edges to blend together.

Set `edge_characters` to `EdgeCharacters::Single(..)` for a single dedicated edge character, or set it to
`EdgeCharacters::Directional { .. }` to set different characters based on the "direction" of the edge, for
example using '-', '|', '/', '\' to draw edge "lines". Detecting the correct edge direction is an area of
improvement for the current code, so you may need to experiment with color/depth/normal thresholds for good
results.

```rust
RatatuiRenderPlugin::new("main", (1, 1))
.autoresize()
.autoresize_conversion_fn(|(width, height)| (width * 4, height * 3))
RatatuiCameraEdgeDetection {
thickness: 1.4,
edge_characters: EdgeCharacters::Single('+'),
edge_color: Some(ratatui::style::Color::Magenta),
..default()
}
```

## multiple renders
## multiple cameras

`RatatuiRenderPlugin` can be added to bevy multiple times. To access the correct render, use the same
string id you passed into `RatatuiRenderPlugin::new(id, dimensions)` to call the `target(id)` and
`widget(id)` methods on the `RatatuiRenderContext` resource.
`RatatuiCamera` can be added to multiple camera entities. To access the correct render, use marker components
on your cameras to use when querying `RatatuiCameraWidget`.

## supported terminals

Expand All @@ -127,7 +152,7 @@ that the following terminals display correctly:

| bevy | bevy_ratatui_render |
|-------|---------------------|
| 0.15 | 0.7 |
| 0.15 | 0.8 |
| 0.14 | 0.6 |
| 0.13 | 0.4 |

Expand Down
Loading

0 comments on commit 108320b

Please sign in to comment.