Skip to content

Commit

Permalink
Merge pull request #4 from Utsira/feature/scene-hooks
Browse files Browse the repository at this point in the history
Add scene hooks
  • Loading branch information
Utsira authored Jan 14, 2024
2 parents 412afd9 + eeb34b1 commit e4bf6ba
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 114 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "bevy_vox_scene"
description = "A Bevy engine plugin for loading Magica Voxel world files and render materials directly in Bevy as a scene graph."
keywords = ["Bevy", "voxel", "Magica-Voxel"]
keywords = ["bevy", "voxel", "Magica-Voxel"]
categories = ["game-development", "graphics", "rendering", "rendering::data-formats"]
license = "MIT"
version = "0.10.4"
version = "0.11.0"
repository = "https://github.com/Utsira/bevy_vox_scene"
authors = ["Oliver Dew <olidew@gmail.com>"]
edition = "2021"
Expand Down
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MIT License

Copyright (c) 2021 Lucas A.
Copyright (c) 2024 Oliver Dew

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@
<code>bevy_vox_scene</code>
</h1>

<a href="https://crates.io/crates/bevy_vox_scene">
<img height="24" src="https://img.shields.io/crates/v/bevy_vox_scene?style=for-the-badge"/>
</a>

[![Latest version](https://img.shields.io/crates/v/bevy_vox_scene.svg)](https://crates.io/crates/bevy_vox_scene)
[![docs.rs](https://docs.rs/bevy_vox_scene/badge.svg)](https://docs.rs/bevy_vox_scene)
[![CI](https://github.com/Utsira/bevy_vox_scene/actions/workflows/ci.yml/badge.svg)](https://github.com/Utsira/bevy_vox_scene/actions/workflows/ci.yml)
[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://bevyengine.org/learn/book/plugin-development/#main-branch-tracking)

A plugin for [the Bevy Engine](https://bevyengine.org) which allows loading [Magica Voxel](https://ephtracy.github.io) `.vox` files directly into a Bevy scene graph.
`bevy_vox_scene` is forked from the excellent [`bevy_vox_mesh` crate](https://crates.io/crates/bevy_vox_mesh).
Expand Down Expand Up @@ -59,6 +58,7 @@ Take a look in the `examples/` directory for complete working examples. To run a
cargo run --example <example name>
```

- To modify entities within a scene hierarchy using scene hooks, see the [`modify-scene` example](/examples/modify-scene.rs).
- If you want glowing emissive voxels, add an HDR and bloom-enabled camera. See the [`emissive-model` example](/examples/emissive-model.rs).
- Enabling Screen-Space Ambient Occlusion can give your voxel scenes more pop. See the [`ssao-model` example](/examples/ssao-model.rs).
- If you want glass voxels to refract other objects in the scene, enable specular transmission on your camera3d. See the [`transmission-scene` example](/examples/transmission-scene.rs).
Expand All @@ -67,7 +67,7 @@ cargo run --example <example name>

| Bevy version | Magica Voxel version | `bevy-vox-scene` version |
| ------------ | -------------- | --- |
| 0.12 | 0.99.6 | 0.9, 0.10 |
| 0.12 | 0.99.6 | 0.9, 0.10, 0.11 |

## Limitations and workarounds

Expand Down Expand Up @@ -98,4 +98,6 @@ cargo test --lib

Forked from the excellent [`bevy_vox_mesh` crate](https://crates.io/crates/bevy_vox_mesh) by Lucas A.

Like `bevy-vox-mesh`, `bevy-vox-scene` uses `dot-vox` to parse the vox files and the greedy mesher from [`block-mesh-rs`] (https://github.com/bonsairobo/block-mesh-rs) to create efficient meshes.
Like `bevy-vox-mesh`, `bevy-vox-scene` uses [`dot-vox`](https://github.com/dust-engine/dot_vox) to parse the vox files and the greedy mesher from [`block-mesh-rs`] (https://github.com/bonsairobo/block-mesh-rs) to create efficient meshes.

`VoxelSceneHook` is adapted from [bevy-scene-hook](https://github.com/nicopap/bevy-scene-hook) by Nicola Papale.
Binary file modified assets/study.vox
Binary file not shown.
140 changes: 62 additions & 78 deletions examples/modify-scene.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use std::f32::consts::PI;
use rand::Rng;

use bevy::{prelude::*, core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping, core_3d::ScreenSpaceTransmissionQuality, experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasBundle}}, input::keyboard::KeyboardInput};
use bevy_vox_scene::{VoxScenePlugin, VoxelSceneBundle, VoxelEntityReady};
use bevy::{prelude::*, core_pipeline::{bloom::BloomSettings, tonemapping::Tonemapping, core_3d::ScreenSpaceTransmissionQuality, experimental::taa::{TemporalAntiAliasPlugin, TemporalAntiAliasBundle}}, input::keyboard::KeyboardInput, ecs::system::EntityCommands};
use bevy_vox_scene::{VoxScenePlugin, VoxelSceneHook, VoxelSceneHookBundle};
use bevy_panorbit_camera::{PanOrbitCameraPlugin, PanOrbitCamera};

/// Uses the [`bevy_vox_scene::VoxelEntityReady`] event to add extra components into the scene graph.
/// Uses the [`bevy_vox_scene::VoxelSceneHook`] component to add extra components into the scene graph.
/// Press any key to toggle the fish tank black-light on and off
fn main() {
let mut app = App::new();
Expand All @@ -15,10 +15,8 @@ fn main() {
PanOrbitCameraPlugin,
VoxScenePlugin,
))
.init_resource::<TankSize>()
.add_systems(Startup, setup)
.add_systems(Update, (
add_components_to_voxel_entities.run_if(on_event::<VoxelEntityReady>()),
toggle_black_light.run_if(on_event::<KeyboardInput>()),
swim_fish,
));
Expand All @@ -33,6 +31,8 @@ fn main() {
app.run();
}

// Systems

fn setup(
mut commands: Commands,
assets: Res<AssetServer>,
Expand Down Expand Up @@ -64,77 +64,36 @@ fn setup(
specular_map: assets.load("pisa_specular.ktx2"),
},
));

commands.spawn(VoxelSceneBundle {
// "tank" is the name of the group containing the glass walls, the body of water, the scenery in the tank and the fish
scene: assets.load("study.vox#tank"),
transform: Transform::from_scale(Vec3::splat(0.05)),
..default()
});
}

#[derive(Component)]
struct EmissiveToggle {
is_on: bool,
on_material: Handle<StandardMaterial>,
off_material: Handle<StandardMaterial>,
}

impl EmissiveToggle {
fn toggle(&mut self) {
self.is_on = !self.is_on;
}

fn material(&self) -> &Handle<StandardMaterial> {
match self.is_on {
true => &self.on_material,
false => &self.off_material,
}
}
}

#[derive(Component)]
struct Fish(f32);

#[derive(Resource, Default)]
struct TankSize(Vec3);
let asset_server = assets.clone();
commands.spawn((
VoxelSceneHookBundle {
// "tank" is the name of the group containing the glass walls, the body of water, the scenery in the tank and the fish
scene: assets.load("study.vox#tank"),

fn add_components_to_voxel_entities(
mut commands: Commands,
mut event_reader: EventReader<VoxelEntityReady>,
mesh_query: Query<&Handle<Mesh>>,
meshes: Res<Assets<Mesh>>,
mut tank_size: ResMut<TankSize>,
assets: Res<AssetServer>,
) {
let mut rng = rand::thread_rng();
for event in event_reader.read() {
// If we are spawning multiple scenes we could match on the scene that the entity was spawned from. Here we just check it is the scene we're expecting.
if event.scene_name != "study.vox#tank" { return };
match event.name.as_str() {
// Node names give the path to the asset, with components separated by /. Here, "black-light" belongs to the "tank" group
"tank/black-light" => {
commands.entity(event.entity).insert(EmissiveToggle {
is_on: true,
on_material: assets.load("study.vox#material"), // emissive texture
off_material: assets.load("study.vox#material-no-emission"), // non-emissive texture
});
},
"tank/goldfish" | "tank/tetra" => {
// Make fish go brrrrr
commands.entity(event.entity).insert(Fish(rng.gen_range(5.0..10.0)));
}
"tank/water" => {
// measure size of tank
let Ok(mesh_handle) = mesh_query.get_component::<Handle<Mesh>>(event.entity) else { return };
let Some(mesh) = meshes.get(mesh_handle) else { return };
let Some(aabb) = mesh.compute_aabb() else { return };
let half_extent: Vec3 = aabb.half_extents.into();
tank_size.0 = half_extent - Vec3::splat(6.0); // add a margin
}
_ => {},
}
}
// This closure will be run against every child Entity that gets spawned in the scene
hook: VoxelSceneHook::new(move |entity, commands| {
let Some(name) = entity.get::<Name>() else { return };
match name.as_str() {
// Node names give the path to the asset, with components separated by /. Here, "black-light" belongs to the "tank" group
"tank/black-light" => {
commands.insert(EmissiveToggle {
is_on: true,
on_material: asset_server.load("study.vox#material"), // emissive texture
off_material: asset_server.load("study.vox#material-no-emission"), // non-emissive texture
});
},
"tank/goldfish" | "tank/tetra" => {
// Make fish go brrrrr
let mut rng = rand::thread_rng(); // random speed
commands.insert(Fish(rng.gen_range(5.0..10.0)));
}
_ => {},
}
}),
transform: Transform::from_scale(Vec3::splat(0.05)),
..default()
},
));
}

fn toggle_black_light(
Expand All @@ -151,19 +110,44 @@ fn toggle_black_light(
fn swim_fish(
mut query: Query<(&mut Transform, &Fish)>,
time: Res<Time>,
tank_size: Res<TankSize>,
) {
let tank_half_extents = Vec3::new(29.0, 20.0, 25.0);
for (mut transform, fish) in query.iter_mut() {
let x_direction = transform.forward().dot(Vec3::X);
if (x_direction < -0.5 && transform.translation.x < -tank_size.0.x) || (x_direction > 0.5 && transform.translation.x > tank_size.0.x) {
if (x_direction < -0.5 && transform.translation.x < -tank_half_extents.x) || (x_direction > 0.5 && transform.translation.x > tank_half_extents.x) {
// change direction at tank edges
transform.rotate_axis(Vec3::Y, PI);
}
// slow down when near the edge
let slow_down = 1.0 - ((transform.translation.x.abs() - (tank_size.0.x - 4.2)) / 5.0).clamp(0.0, 1.0);
let slow_down = 1.0 - ((transform.translation.x.abs() - (tank_half_extents.x - 4.2)) / 5.0).clamp(0.0, 1.0);
let forward = transform.forward();
transform.translation += forward * (time.delta_seconds() * fish.0 * slow_down);
// make them weave up and down
transform.translation.y = (transform.translation.x * 0.1).sin() * 6.0;
}
}

// Components

#[derive(Component)]
struct EmissiveToggle {
is_on: bool,
on_material: Handle<StandardMaterial>,
off_material: Handle<StandardMaterial>,
}

impl EmissiveToggle {
fn toggle(&mut self) {
self.is_on = !self.is_on;
}

fn material(&self) -> &Handle<StandardMaterial> {
match self.is_on {
true => &self.on_material,
false => &self.off_material,
}
}
}

#[derive(Component)]
struct Fish(f32);
11 changes: 6 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@
//!```
use bevy::{
app::{App, Plugin, SpawnScene},
asset::AssetApp,
asset::AssetApp, ecs::schedule::IntoSystemConfigs,
};

mod loader;
mod voxel_scene;
pub use voxel_scene::VoxelEntityReady;
pub use voxel_scene::{VoxelSceneBundle, VoxelScene, VoxelLayer};
pub use voxel_scene::{VoxelSceneBundle, VoxelSceneHookBundle, VoxelScene, VoxelLayer, VoxelSceneHook};
pub use loader::VoxLoaderSettings;
#[doc(inline)]
use loader::VoxSceneLoader;
Expand All @@ -57,7 +56,9 @@ impl Plugin for VoxScenePlugin {
app
.init_asset::<voxel_scene::VoxelScene>()
.register_asset_loader(VoxSceneLoader)
.add_event::<VoxelEntityReady>()
.add_systems(SpawnScene, voxel_scene::spawn_vox_scenes);
.add_systems(SpawnScene, (
voxel_scene::spawn_vox_scenes,
voxel_scene::run_hooks
).chain());
}
}
Loading

0 comments on commit e4bf6ba

Please sign in to comment.