diff --git a/Cargo.toml b/Cargo.toml index e9d4875..623a9aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] edition = "2021" diff --git a/LICENSE b/LICENSE index 070fa30..5387ac4 100644 --- a/LICENSE +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md index e2b4101..73e0f2c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ bevy_vox_scene - - - - +[![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). @@ -59,6 +58,7 @@ Take a look in the `examples/` directory for complete working examples. To run a cargo run --example ``` +- 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). @@ -67,7 +67,7 @@ cargo run --example | 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 @@ -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. diff --git a/assets/study.vox b/assets/study.vox index 74a0f75..9badcf0 100644 Binary files a/assets/study.vox and b/assets/study.vox differ diff --git a/examples/modify-scene.rs b/examples/modify-scene.rs index f3d4a0d..271d08f 100644 --- a/examples/modify-scene.rs +++ b/examples/modify-scene.rs @@ -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(); @@ -15,10 +15,8 @@ fn main() { PanOrbitCameraPlugin, VoxScenePlugin, )) - .init_resource::() .add_systems(Startup, setup) .add_systems(Update, ( - add_components_to_voxel_entities.run_if(on_event::()), toggle_black_light.run_if(on_event::()), swim_fish, )); @@ -33,6 +31,8 @@ fn main() { app.run(); } +// Systems + fn setup( mut commands: Commands, assets: Res, @@ -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, - off_material: Handle, -} - -impl EmissiveToggle { - fn toggle(&mut self) { - self.is_on = !self.is_on; - } - - fn material(&self) -> &Handle { - 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, - mesh_query: Query<&Handle>, - meshes: Res>, - mut tank_size: ResMut, - assets: Res, -) { - 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::>(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::() 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( @@ -151,19 +110,44 @@ fn toggle_black_light( fn swim_fish( mut query: Query<(&mut Transform, &Fish)>, time: Res