Some gltf improvements #4
|
@ -83,6 +83,28 @@
|
||||||
},
|
},
|
||||||
"args": [],
|
"args": [],
|
||||||
"cwd": "${workspaceFolder}/lyra-ecs"
|
"cwd": "${workspaceFolder}/lyra-ecs"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "lldb",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Debug unit tests in executable 'lyra-scene'",
|
||||||
|
"cargo": {
|
||||||
|
"args": [
|
||||||
|
"test",
|
||||||
|
"--no-run",
|
||||||
|
"--lib",
|
||||||
|
"--package=lyra-scene",
|
||||||
|
//"command::tests::deferred_commands",
|
||||||
|
//"--",
|
||||||
|
//"--exact --nocapture"
|
||||||
|
],
|
||||||
|
"filter": {
|
||||||
|
"name": "lyra-scene",
|
||||||
|
"kind": "lib"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": [],
|
||||||
|
"cwd": "${workspaceFolder}/lyra-scene"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1845,7 +1845,9 @@ dependencies = [
|
||||||
"gltf",
|
"gltf",
|
||||||
"image",
|
"image",
|
||||||
"infer",
|
"infer",
|
||||||
|
"instant",
|
||||||
"lyra-ecs",
|
"lyra-ecs",
|
||||||
|
"lyra-math",
|
||||||
"mime",
|
"mime",
|
||||||
"notify",
|
"notify",
|
||||||
"notify-debouncer-full",
|
"notify-debouncer-full",
|
||||||
|
@ -1855,6 +1857,14 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lyra-scene"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"lyra-ecs",
|
||||||
|
"lyra-math",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lyra-scripting"
|
name = "lyra-scripting"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -3093,7 +3103,6 @@ dependencies = [
|
||||||
"async-std",
|
"async-std",
|
||||||
"fps_counter",
|
"fps_counter",
|
||||||
"lyra-engine",
|
"lyra-engine",
|
||||||
"lyra-scripting",
|
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ members = [
|
||||||
"lyra-ecs",
|
"lyra-ecs",
|
||||||
"lyra-reflect",
|
"lyra-reflect",
|
||||||
"lyra-scripting",
|
"lyra-scripting",
|
||||||
"lyra-game", "lyra-math"]
|
"lyra-game", "lyra-math", "lyra-scene"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
scripting = ["dep:lyra-scripting"]
|
scripting = ["dep:lyra-scripting"]
|
||||||
|
|
|
@ -6,8 +6,9 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] }
|
lyra-engine = { path = "../../", version = "0.0.1" }
|
||||||
lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] }
|
#lyra-engine = { path = "../../", version = "0.0.1", features = ["lua_scripting"] }
|
||||||
|
#lyra-scripting = { path = "../../lyra-scripting", features = ["lua", "teal"] }
|
||||||
#lyra-ecs = { path = "../../lyra-ecs"}
|
#lyra-ecs = { path = "../../lyra-ecs"}
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
async-std = "1.12.0"
|
async-std = "1.12.0"
|
||||||
|
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
||||||
|
Sponza*
|
||||||
|
Textures
|
|
@ -0,0 +1 @@
|
||||||
|
To keep the size of this repository down, the Sponza scene is omitted from this repo. The files were downloaded from https://github.com/toji/sponza-optimized
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
|
|
||||||
use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList};
|
use lyra_engine::{assets::gltf::Gltf, ecs::{system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight, SpotLight}, scene::{CameraComponent, MeshComponent}, DeltaTime};
|
||||||
use lyra_engine::assets::{ResourceManager, Model};
|
use lyra_engine::assets::ResourceManager;
|
||||||
|
|
||||||
mod free_fly_camera;
|
mod free_fly_camera;
|
||||||
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
|
use free_fly_camera::{FreeFlyCameraPlugin, FreeFlyCamera};
|
||||||
|
@ -16,6 +16,13 @@ pub enum ActionLabel {
|
||||||
LookRoll,
|
LookRoll,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ACTLBL_MOVE_UP_DOWN: &str = "MoveUpDown";
|
||||||
|
const ACTLBL_MOVE_LEFT_RIGHT: &str = "MoveLeftRight";
|
||||||
|
const ACTLBL_MOVE_FORWARD_BACKWARD: &str = "MoveForwardBackward";
|
||||||
|
const ACTLBL_LOOK_LEFT_RIGHT: &str = "LookLeftRight";
|
||||||
|
const ACTLBL_LOOK_UP_DOWN: &str = "LookUpDown";
|
||||||
|
const ACTLBL_LOOK_ROLL: &str = "LookRoll";
|
||||||
|
|
||||||
struct FixedTimestep {
|
struct FixedTimestep {
|
||||||
max_tps: u32,
|
max_tps: u32,
|
||||||
fixed_time: f32,
|
fixed_time: f32,
|
||||||
|
@ -86,30 +93,45 @@ async fn main() {
|
||||||
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
|
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
|
||||||
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
|
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
|
||||||
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
|
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();
|
||||||
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
|
/* let cube_gltf = resman.request::<Gltf>("assets/texture-sep/texture-sep.gltf").unwrap();
|
||||||
let crate_model = resman.request::<Model>("assets/crate/crate.gltf").unwrap();
|
let crate_gltf = resman.request::<Gltf>("assets/crate/crate.gltf").unwrap();
|
||||||
//let sponza_model = resman.request::<Model>("assets/sponza/Sponza.gltf").unwrap();
|
|
||||||
|
let separate_gltf = resman.request::<Gltf>("assets/pos-testing/child-node-cubes.glb").unwrap(); */
|
||||||
|
//drop(resman);
|
||||||
|
|
||||||
|
/* let cube_mesh = &cube_gltf.data_ref()
|
||||||
|
.unwrap().meshes[0];
|
||||||
|
let crate_mesh = &crate_gltf.data_ref()
|
||||||
|
.unwrap().meshes[0];
|
||||||
|
|
||||||
|
let separate_scene = &separate_gltf.data_ref()
|
||||||
|
.unwrap().scenes[0]; */
|
||||||
|
|
||||||
|
let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap();
|
||||||
drop(resman);
|
drop(resman);
|
||||||
|
|
||||||
|
let sponza_scene = &sponza_model.data_ref()
|
||||||
|
.unwrap().scenes[0];
|
||||||
|
|
||||||
|
world.spawn((
|
||||||
|
sponza_scene.clone(),
|
||||||
|
Transform::from_xyz(0.0, 0.0, 0.0),
|
||||||
|
));
|
||||||
|
|
||||||
/* world.spawn((
|
/* world.spawn((
|
||||||
ModelComponent(antique_camera_model),
|
separate_scene.clone(),
|
||||||
Transform::from_xyz(0.0, -5.0, -10.0),
|
Transform::from_xyz(0.0, -5.0, -10.0),
|
||||||
)); */
|
)); */
|
||||||
|
|
||||||
/* world.spawn((
|
/* {
|
||||||
ModelComponent(sponza_model),
|
let cube_tran = Transform::from_xyz(-5.9026427, -1.8953488, -10.0);
|
||||||
Transform::from_xyz(0.0, 0.0, 0.0),
|
|
||||||
)); */
|
|
||||||
|
|
||||||
{
|
|
||||||
let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0);
|
|
||||||
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
//cube_tran.rotate_y(math::Angle::Degrees(180.0));
|
||||||
world.spawn((
|
world.spawn((
|
||||||
cube_tran,
|
cube_tran,
|
||||||
ModelComponent(crate_model.clone()),
|
crate_mesh.clone(),
|
||||||
CubeFlag,
|
CubeFlag,
|
||||||
));
|
));
|
||||||
}
|
} */
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
|
||||||
|
@ -124,11 +146,11 @@ async fn main() {
|
||||||
specular: 1.3,
|
specular: 1.3,
|
||||||
},
|
},
|
||||||
light_tran,
|
light_tran,
|
||||||
ModelComponent(cube_model.clone()),
|
//cube_mesh.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
/* {
|
||||||
let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5);
|
let mut light_tran = Transform::from_xyz(-3.5, 0.2, -4.5);
|
||||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||||
world.spawn((
|
world.spawn((
|
||||||
|
@ -146,10 +168,32 @@ async fn main() {
|
||||||
specular: 1.0,
|
specular: 1.0,
|
||||||
},
|
},
|
||||||
Transform::from(light_tran),
|
Transform::from(light_tran),
|
||||||
ModelComponent(cube_model.clone()),
|
cube_mesh.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
|
||||||
|
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||||
|
world.spawn((
|
||||||
|
PointLight {
|
||||||
|
color: Vec3::new(0.0, 0.0, 1.0),
|
||||||
|
|
||||||
|
intensity: 3.3,
|
||||||
|
|
||||||
|
constant: 1.0,
|
||||||
|
linear: 0.09,
|
||||||
|
quadratic: 0.032,
|
||||||
|
|
||||||
|
ambient: 0.2,
|
||||||
|
diffuse: 1.0,
|
||||||
|
specular: 1.3,
|
||||||
|
},
|
||||||
|
Transform::from(light_tran),
|
||||||
|
cube_mesh.clone(),
|
||||||
|
));
|
||||||
|
} */
|
||||||
|
|
||||||
/* {
|
/* {
|
||||||
let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5);
|
let mut light_tran = Transform::from_xyz(-5.0, 2.5, -9.5);
|
||||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
||||||
|
@ -172,28 +216,6 @@ async fn main() {
|
||||||
));
|
));
|
||||||
} */
|
} */
|
||||||
|
|
||||||
{
|
|
||||||
let mut light_tran = Transform::from_xyz(2.0, 2.5, -9.5);
|
|
||||||
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
|
|
||||||
world.spawn((
|
|
||||||
PointLight {
|
|
||||||
color: Vec3::new(0.0, 0.0, 1.0),
|
|
||||||
|
|
||||||
intensity: 3.3,
|
|
||||||
|
|
||||||
constant: 1.0,
|
|
||||||
linear: 0.09,
|
|
||||||
quadratic: 0.032,
|
|
||||||
|
|
||||||
ambient: 0.2,
|
|
||||||
diffuse: 1.0,
|
|
||||||
specular: 1.3,
|
|
||||||
},
|
|
||||||
Transform::from(light_tran),
|
|
||||||
ModelComponent(cube_model),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut camera = CameraComponent::new_3d();
|
let mut camera = CameraComponent::new_3d();
|
||||||
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
|
||||||
world.spawn(( camera, FreeFlyCamera::default() ));
|
world.spawn(( camera, FreeFlyCamera::default() ));
|
||||||
|
@ -211,7 +233,7 @@ async fn main() {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let fps_plugin = move |game: &mut Game| {
|
let _fps_plugin = move |game: &mut Game| {
|
||||||
let world = game.world_mut();
|
let world = game.world_mut();
|
||||||
world.add_resource(fps_counter::FPSCounter::new());
|
world.add_resource(fps_counter::FPSCounter::new());
|
||||||
};
|
};
|
||||||
|
@ -246,54 +268,54 @@ async fn main() {
|
||||||
};
|
};
|
||||||
|
|
||||||
let action_handler_plugin = |game: &mut Game| {
|
let action_handler_plugin = |game: &mut Game| {
|
||||||
/* let action_handler = ActionHandler::builder()
|
let action_handler = ActionHandler::builder()
|
||||||
.add_layout(LayoutId::from(0))
|
.add_layout(LayoutId::from(0))
|
||||||
|
|
||||||
.add_action(CommonActionLabel::MoveForwardBackward, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
|
||||||
.add_action(CommonActionLabel::MoveLeftRight, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||||
.add_action(CommonActionLabel::MoveUpDown, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
|
||||||
.add_action(CommonActionLabel::LookLeftRight, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
|
||||||
.add_action(CommonActionLabel::LookUpDown, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
|
||||||
.add_action(CommonActionLabel::LookRoll, Action::new(ActionKind::Axis))
|
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
|
||||||
|
|
||||||
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
.add_mapping(ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
|
||||||
.bind(CommonActionLabel::MoveForwardBackward, &[
|
.bind(ACTLBL_MOVE_FORWARD_BACKWARD, &[
|
||||||
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
|
||||||
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
|
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0)
|
||||||
])
|
])
|
||||||
.bind(CommonActionLabel::MoveLeftRight, &[
|
.bind(ACTLBL_MOVE_LEFT_RIGHT, &[
|
||||||
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
|
||||||
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0)
|
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0)
|
||||||
])
|
])
|
||||||
.bind(CommonActionLabel::MoveUpDown, &[
|
.bind(ACTLBL_MOVE_UP_DOWN, &[
|
||||||
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
|
||||||
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0)
|
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0)
|
||||||
])
|
])
|
||||||
.bind(CommonActionLabel::LookLeftRight, &[
|
.bind(ACTLBL_LOOK_LEFT_RIGHT, &[
|
||||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
|
||||||
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
|
||||||
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
|
||||||
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
|
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickX)).into_binding(),
|
||||||
])
|
])
|
||||||
.bind(CommonActionLabel::LookUpDown, &[
|
.bind(ACTLBL_LOOK_UP_DOWN, &[
|
||||||
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
|
||||||
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
|
||||||
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
|
||||||
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
|
//ActionSource::Gamepad(GamepadFormat::DualAxis, GamepadInput::Axis(GamepadAxis::RThumbstickY)).into_binding(),
|
||||||
])
|
])
|
||||||
.bind(CommonActionLabel::LookRoll, &[
|
.bind(ACTLBL_LOOK_ROLL, &[
|
||||||
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
|
||||||
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
|
||||||
])
|
])
|
||||||
.finish()
|
.finish()
|
||||||
);
|
).finish();
|
||||||
|
|
||||||
let world = game.world_mut();
|
let world = game.world_mut();
|
||||||
world.add_resource(action_handler); */
|
world.add_resource(action_handler);
|
||||||
game.with_plugin(InputActionPlugin);
|
game.with_plugin(InputActionPlugin);
|
||||||
};
|
};
|
||||||
|
|
||||||
let script_test_plugin = |game: &mut Game| {
|
/* let script_test_plugin = |game: &mut Game| {
|
||||||
game.with_plugin(LuaScriptingPlugin);
|
game.with_plugin(LuaScriptingPlugin);
|
||||||
|
|
||||||
let world = game.world_mut();
|
let world = game.world_mut();
|
||||||
|
@ -306,13 +328,13 @@ async fn main() {
|
||||||
let scripts = ScriptList::new(vec![script]);
|
let scripts = ScriptList::new(vec![script]);
|
||||||
world.spawn((scripts,));
|
world.spawn((scripts,));
|
||||||
|
|
||||||
};
|
}; */
|
||||||
|
|
||||||
Game::initialize().await
|
Game::initialize().await
|
||||||
.with_plugin(lyra_engine::DefaultPlugins)
|
.with_plugin(lyra_engine::DefaultPlugins)
|
||||||
.with_startup_system(setup_sys.into_system())
|
.with_startup_system(setup_sys.into_system())
|
||||||
.with_plugin(action_handler_plugin)
|
.with_plugin(action_handler_plugin)
|
||||||
.with_plugin(script_test_plugin)
|
//.with_plugin(script_test_plugin)
|
||||||
//.with_plugin(fps_plugin)
|
//.with_plugin(fps_plugin)
|
||||||
.with_plugin(jiggle_plugin)
|
.with_plugin(jiggle_plugin)
|
||||||
.with_plugin(FreeFlyCameraPlugin)
|
.with_plugin(FreeFlyCameraPlugin)
|
||||||
|
|
|
@ -55,7 +55,6 @@ impl ComponentColumn {
|
||||||
/// Set a component from pointer at an entity index.
|
/// Set a component from pointer at an entity index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
|
||||||
/// This column must have space to fit the component, if it does not have room it will panic.
|
/// This column must have space to fit the component, if it does not have room it will panic.
|
||||||
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
pub unsafe fn set_at(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||||
assert!(entity_index < self.capacity);
|
assert!(entity_index < self.capacity);
|
||||||
|
@ -76,6 +75,23 @@ impl ComponentColumn {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts an entity and its component at a specific index.
|
||||||
|
pub unsafe fn insert_entity(&mut self, entity_index: usize, comp_src: NonNull<u8>, tick: Tick) {
|
||||||
|
assert!(entity_index < self.capacity);
|
||||||
|
|
||||||
|
let mut data = self.data.borrow_mut();
|
||||||
|
let data = data.deref_mut();
|
||||||
|
|
||||||
|
let size = self.info.layout().size();
|
||||||
|
let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size));
|
||||||
|
ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size);
|
||||||
|
|
||||||
|
// check if a component spot is being set twice and that the entity's tick is
|
||||||
|
// already stored
|
||||||
|
self.entity_ticks.push(tick);
|
||||||
|
self.len += 1;
|
||||||
|
}
|
||||||
|
|
||||||
/// Get a component at an entities index.
|
/// Get a component at an entities index.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -153,6 +169,8 @@ impl ComponentColumn {
|
||||||
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
pub unsafe fn remove_component(&mut self, entity_index: usize, tick: &Tick) -> Option<usize> {
|
||||||
let _ = tick; // may be used at some point
|
let _ = tick; // may be used at some point
|
||||||
|
|
||||||
|
debug_assert!(self.len > 0, "There are no entities in the Archetype to remove from!");
|
||||||
|
|
||||||
let mut data = self.data.borrow_mut();
|
let mut data = self.data.borrow_mut();
|
||||||
let data = data.deref_mut();
|
let data = data.deref_mut();
|
||||||
|
|
||||||
|
@ -170,8 +188,7 @@ impl ComponentColumn {
|
||||||
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr
|
||||||
|
|
||||||
// make sure to keep entity indexes correct in the ticks list as well
|
// make sure to keep entity indexes correct in the ticks list as well
|
||||||
self.entity_ticks.swap(moved_index, entity_index);
|
self.entity_ticks.swap_remove(entity_index);
|
||||||
self.entity_ticks.pop();
|
|
||||||
|
|
||||||
Some(moved_index)
|
Some(moved_index)
|
||||||
} else { None };
|
} else { None };
|
||||||
|
@ -283,8 +300,7 @@ impl Archetype {
|
||||||
|
|
||||||
bundle.take(|data, type_id, _size| {
|
bundle.take(|data, type_id, _size| {
|
||||||
let col = self.get_column_mut(type_id).unwrap();
|
let col = self.get_column_mut(type_id).unwrap();
|
||||||
unsafe { col.set_at(entity_index.0 as usize, data, *tick); }
|
unsafe { col.insert_entity(entity_index.0 as usize, data, *tick); }
|
||||||
col.len += 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
entity_index
|
entity_index
|
||||||
|
@ -293,21 +309,21 @@ impl Archetype {
|
||||||
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
/// Removes an entity from the Archetype and frees its components. Returns the entity record
|
||||||
/// that took its place in the component column.
|
/// that took its place in the component column.
|
||||||
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> {
|
||||||
let entity_index = *self.entity_ids.get(&entity)
|
let entity_index = self.entity_ids.remove(&entity)
|
||||||
.expect("The entity is not in this Archetype!");
|
.expect("The entity is not in this Archetype!");
|
||||||
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None;
|
||||||
|
|
||||||
for c in self.columns.iter_mut() {
|
for c in self.columns.iter_mut() {
|
||||||
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) };
|
||||||
|
|
||||||
if let Some(res) = moved_entity {
|
if let Some(moved_idx) = moved_entity {
|
||||||
if let Some((_, aid)) = removed_entity {
|
if let Some((_, aid)) = removed_entity {
|
||||||
// Make sure that the moved entity is the same as what was moved in other columns.
|
// Make sure that the moved entity is the same as what was moved in other columns.
|
||||||
assert!(res as u64 == aid.0);
|
assert!(moved_idx as u64 == aid.0);
|
||||||
} else {
|
} else {
|
||||||
// This is the first move, so find the EntityId that points to the column index.
|
// This is the first move, so find the Entity that was moved into this index.
|
||||||
let just_removed = self.entities[res];
|
let just_removed = self.entities[moved_idx];
|
||||||
removed_entity = Some((just_removed, ArchetypeEntityId(res as u64)));
|
removed_entity = Some((just_removed, ArchetypeEntityId(moved_idx as u64)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If there wasn't a moved entity, make sure no other columns moved something.
|
// If there wasn't a moved entity, make sure no other columns moved something.
|
||||||
|
@ -316,12 +332,15 @@ impl Archetype {
|
||||||
}
|
}
|
||||||
|
|
||||||
// safe from the .expect at the start of this method.
|
// safe from the .expect at the start of this method.
|
||||||
self.entity_ids.remove(&entity).unwrap();
|
//self.entity_ids.remove(&entity).unwrap();
|
||||||
if self.entities.len() > 1 {
|
|
||||||
let len = self.entities.len();
|
// update the archetype index of the moved entity
|
||||||
self.entities.swap(entity_index.0 as _, len - 1);
|
if let Some((moved, _old_idx)) = removed_entity {
|
||||||
|
self.entity_ids.insert(moved, entity_index);
|
||||||
}
|
}
|
||||||
self.entities.pop().unwrap();
|
|
||||||
|
let removed = self.entities.swap_remove(entity_index.0 as _);
|
||||||
|
assert_eq!(removed, entity);
|
||||||
|
|
||||||
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
// now change the ArchetypeEntityId to be the index that the moved entity was moved into.
|
||||||
removed_entity.map(|(e, _a)| (e, entity_index))
|
removed_entity.map(|(e, _a)| (e, entity_index))
|
||||||
|
@ -390,7 +409,8 @@ impl Archetype {
|
||||||
self.capacity = new_cap;
|
self.capacity = new_cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced");
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced");
|
||||||
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64);
|
||||||
self.entity_ids.insert(entity, entity_index);
|
self.entity_ids.insert(entity, entity_index);
|
||||||
self.entities.push(entity);
|
self.entities.push(entity);
|
||||||
|
@ -402,6 +422,12 @@ impl Archetype {
|
||||||
entity_index
|
entity_index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure that the internal entity lists are synced in length
|
||||||
|
pub(crate) fn ensure_synced(&self) {
|
||||||
|
debug_assert_eq!(self.entity_ids.len(), self.entities.len(),
|
||||||
|
"Somehow the Archetype's entity storage got unsynced");
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves the entity from this archetype into another one.
|
/// Moves the entity from this archetype into another one.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -453,6 +479,36 @@ impl Archetype {
|
||||||
pub fn has_entity(&self, e: Entity) -> bool {
|
pub fn has_entity(&self, e: Entity) -> bool {
|
||||||
self.entity_ids.contains_key(&e)
|
self.entity_ids.contains_key(&e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extend the Archetype by adding more columns.
|
||||||
|
///
|
||||||
|
/// In order to extend the Archetype, the archetype needs the components for the entities
|
||||||
|
/// it already has. These are provided through the `new_columns` parameter. **If the Vec
|
||||||
|
/// does not have the same amount of bundles in it as the amount of entities in the
|
||||||
|
/// Archetype, it will panic!**
|
||||||
|
pub fn extend<B: Bundle>(&mut self, tick: &Tick, new_columns: Vec<B>) {
|
||||||
|
debug_assert_eq!(new_columns.len(), self.len(), "The amount of provided column does not \
|
||||||
|
match the amount of entities");
|
||||||
|
|
||||||
|
let column_info = new_columns.iter()
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.info();
|
||||||
|
|
||||||
|
for coli in column_info.into_iter() {
|
||||||
|
let col = unsafe { ComponentColumn::new(coli, self.capacity) };
|
||||||
|
self.columns.push(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (eid, bundle) in new_columns.into_iter().enumerate() {
|
||||||
|
bundle.take(|ptr, tyid, _size| {
|
||||||
|
unsafe {
|
||||||
|
let col = self.get_column_mut(tyid).unwrap();
|
||||||
|
col.insert_entity(eid, ptr, tick.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -666,4 +722,24 @@ mod tests {
|
||||||
assert_eq!(unsafe { *ptr.cast::<u32>().as_ref() }, comp);
|
assert_eq!(unsafe { *ptr.cast::<u32>().as_ref() }, comp);
|
||||||
assert_eq!(col.info, info);
|
assert_eq!(col.info, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Tests removing an entity from the Archetype when it is the only entity in it.
|
||||||
|
#[test]
|
||||||
|
fn remove_single_entity() {
|
||||||
|
let info = (Vec2::new(0.0, 0.0),).info();
|
||||||
|
let mut a = Archetype::from_bundle_info(super::ArchetypeId(0), info);
|
||||||
|
|
||||||
|
let ae = Entity {
|
||||||
|
id: EntityId(0),
|
||||||
|
generation: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
a.add_entity(
|
||||||
|
ae,
|
||||||
|
Vec2::new(10.0, 50.0),
|
||||||
|
&Tick::default()
|
||||||
|
);
|
||||||
|
|
||||||
|
a.remove_entity(ae, &Tick::default());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -30,7 +30,7 @@ pub use component::*;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
//pub use query::*;
|
//pub use query::*;
|
||||||
|
|
||||||
mod relation;
|
pub mod relation;
|
||||||
pub use relation::Relation;
|
pub use relation::Relation;
|
||||||
|
|
||||||
mod component_info;
|
mod component_info;
|
||||||
|
|
|
@ -177,10 +177,10 @@ where
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let col = archetype.get_column(self.type_id)
|
let col = archetype.get_column(self.type_id)
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
let col = NonNull::from(col);
|
|
||||||
|
|
||||||
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
|
// TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated.
|
||||||
// the fetcher needs to tick the entities tick in the archetype
|
// the fetcher needs to tick the entities tick in the archetype
|
||||||
|
let col = NonNull::from(col);
|
||||||
|
|
||||||
FetchBorrowMut {
|
FetchBorrowMut {
|
||||||
col,
|
col,
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use crate::{query::{AsQuery, Query}, Archetype, Component, DynTypeId, World};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Has<C: Component> {
|
||||||
|
_marker: PhantomData<C>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Copy for Has<C> {}
|
||||||
|
|
||||||
|
impl<C: Component> Clone for Has<C> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { _marker: self._marker.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> Query for Has<C> {
|
||||||
|
type Item<'a> = ();
|
||||||
|
|
||||||
|
type Fetch<'a> = ();
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Has {
|
||||||
|
_marker: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
|
||||||
|
archetype.has_column(DynTypeId::of::<C>())
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C: Component> AsQuery for Has<C> {
|
||||||
|
type Query = Self;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod has_component;
|
||||||
|
pub use has_component::*;
|
||||||
|
|
||||||
|
pub mod or;
|
||||||
|
pub use or::*;
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::{query::{AsQuery, Query}, Archetype, World};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Or<Q1: AsQuery, Q2: AsQuery> {
|
||||||
|
left: Q1::Query,
|
||||||
|
right: Q2::Query,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Q1: AsQuery, Q2: AsQuery> Copy for Or<Q1, Q2> {}
|
||||||
|
|
||||||
|
impl<Q1: AsQuery, Q2: AsQuery> Clone for Or<Q1, Q2> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self { left: self.left.clone(), right: self.right.clone() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Q1: AsQuery, Q2: AsQuery> Query for Or<Q1, Q2> {
|
||||||
|
type Item<'a> = ();
|
||||||
|
|
||||||
|
type Fetch<'a> = ();
|
||||||
|
|
||||||
|
fn new() -> Self {
|
||||||
|
Or {
|
||||||
|
left: Q1::Query::new(),
|
||||||
|
right: Q2::Query::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_visit_archetype(&self, archetype: &Archetype) -> bool {
|
||||||
|
self.left.can_visit_archetype(archetype) || self.right.can_visit_archetype(archetype)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn fetch<'a>(&self, _world: &'a World, _: &'a Archetype, _: crate::Tick) -> Self::Fetch<'a> {
|
||||||
|
()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Q1: AsQuery, Q2: AsQuery> AsQuery for Or<Q1, Q2> {
|
||||||
|
type Query = Self;
|
||||||
|
}
|
|
@ -29,6 +29,8 @@ pub use world::*;
|
||||||
|
|
||||||
pub mod dynamic;
|
pub mod dynamic;
|
||||||
|
|
||||||
|
pub mod filter;
|
||||||
|
|
||||||
/// A [`Fetch`]er implementation gets data out of an archetype.
|
/// A [`Fetch`]er implementation gets data out of an archetype.
|
||||||
pub trait Fetch<'a> {
|
pub trait Fetch<'a> {
|
||||||
/// The type that this Fetch yields
|
/// The type that this Fetch yields
|
||||||
|
|
|
@ -90,7 +90,7 @@ where
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES {
|
if Q::ALWAYS_FETCHES {
|
||||||
// only fetch this query once.
|
// only fetch this query once.
|
||||||
// fetcher gets set to Some after this `next` call.
|
// fetcher gets set to Some after this `next` call.
|
||||||
if self.fetcher.is_none() {
|
if self.fetcher.is_none() {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
use super::Relation;
|
||||||
|
|
||||||
|
// TODO: Delete child entities when the parent is deleted
|
||||||
|
pub struct ChildOf;
|
||||||
|
|
||||||
|
impl Relation for ChildOf {
|
||||||
|
|
||||||
|
}
|
|
@ -10,19 +10,20 @@ use crate::lyra_engine;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
mod relates_to;
|
mod relates_to;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use relates_to::*;
|
pub use relates_to::*;
|
||||||
|
|
||||||
mod relate_pair;
|
mod relate_pair;
|
||||||
#[doc(hidden)]
|
|
||||||
pub use relate_pair::*;
|
pub use relate_pair::*;
|
||||||
|
|
||||||
|
mod child_of;
|
||||||
|
pub use child_of::*;
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Relation: 'static {
|
pub trait Relation: 'static {
|
||||||
/// called when a relation of this type is set on a target
|
/// called when a relation of this type is set on a target
|
||||||
fn relation_add(&self, origin: Entity, target: Entity) { }
|
fn relation_add(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||||
/// called when a relation is removed
|
/// called when a relation is removed
|
||||||
fn relation_remove(&self, origin: Entity, target: Entity) { }
|
fn relation_remove(&mut self, world: &mut World, origin: Entity, target: Entity) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A component that stores the target of a relation.
|
/// A component that stores the target of a relation.
|
||||||
|
@ -31,10 +32,16 @@ pub trait Relation: 'static {
|
||||||
/// entities that the relation targets.
|
/// entities that the relation targets.
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct RelationOriginComponent<R: Relation> {
|
pub struct RelationOriginComponent<R: Relation> {
|
||||||
pub(crate) relation: R,
|
pub relation: R,
|
||||||
target: Entity,
|
target: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<R: Relation> RelationOriginComponent<R> {
|
||||||
|
pub fn target(&self) -> Entity {
|
||||||
|
self.target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A component that stores the origin of a relation.
|
/// A component that stores the origin of a relation.
|
||||||
///
|
///
|
||||||
/// This component is on the target of the relation and can be used to find the
|
/// This component is on the target of the relation and can be used to find the
|
||||||
|
@ -79,12 +86,12 @@ impl World {
|
||||||
};
|
};
|
||||||
self.insert(target, comp);
|
self.insert(target, comp);
|
||||||
|
|
||||||
let comp = RelationOriginComponent {
|
let mut comp = RelationOriginComponent {
|
||||||
relation,
|
relation,
|
||||||
target,
|
target,
|
||||||
};
|
};
|
||||||
|
|
||||||
comp.relation.relation_add(origin, target);
|
comp.relation.relation_add(self, origin, target);
|
||||||
self.insert(origin, comp);
|
self.insert(origin, comp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ where
|
||||||
|
|
||||||
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
|
||||||
let _ = tick;
|
let _ = tick;
|
||||||
let col = archetype.get_column(self.type_id())
|
let col = archetype.get_column(TypeId::of::<RelationOriginComponent<R>>())
|
||||||
.expect("You ignored 'can_visit_archetype'!");
|
.expect("You ignored 'can_visit_archetype'!");
|
||||||
|
|
||||||
FetchRelatePair {
|
FetchRelatePair {
|
||||||
|
|
|
@ -133,8 +133,6 @@ impl World {
|
||||||
where
|
where
|
||||||
B: Bundle
|
B: Bundle
|
||||||
{
|
{
|
||||||
// TODO: If the archetype has a single entity, add a component column for the new
|
|
||||||
// component instead of moving the entity to a brand new archetype.
|
|
||||||
// TODO: If the entity already has the components in `bundle`, update the values of the
|
// TODO: If the entity already has the components in `bundle`, update the values of the
|
||||||
// components with the bundle.
|
// components with the bundle.
|
||||||
|
|
||||||
|
@ -142,6 +140,7 @@ impl World {
|
||||||
|
|
||||||
let record = self.entities.entity_record(entity).unwrap();
|
let record = self.entities.entity_record(entity).unwrap();
|
||||||
let current_arch = self.archetypes.get(&record.id).unwrap();
|
let current_arch = self.archetypes.get(&record.id).unwrap();
|
||||||
|
let current_arch_len = current_arch.len();
|
||||||
|
|
||||||
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
let mut col_types: Vec<DynTypeId> = current_arch.columns.iter().map(|c| c.info.type_id()).collect();
|
||||||
let orig_col = col_types.clone();
|
let orig_col = col_types.clone();
|
||||||
|
@ -150,7 +149,9 @@ impl World {
|
||||||
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
let mut col_infos: Vec<ComponentInfo> = current_arch.columns.iter().map(|c| c.info).collect();
|
||||||
col_infos.extend(bundle.info());
|
col_infos.extend(bundle.info());
|
||||||
|
|
||||||
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter().map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) }).collect();
|
let col_ptrs: Vec<(NonNull<u8>, ComponentInfo)> = current_arch.columns.iter()
|
||||||
|
.map(|c| unsafe { (NonNull::new_unchecked(c.borrow_ptr().as_ptr()), c.info) })
|
||||||
|
.collect();
|
||||||
|
|
||||||
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
if let Some(arch) = self.archetypes.values_mut().find(|a| a.is_archetype_for(&col_types)) {
|
||||||
let res_index = arch.reserve_one(entity);
|
let res_index = arch.reserve_one(entity);
|
||||||
|
@ -158,19 +159,21 @@ impl World {
|
||||||
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
let ptr = NonNull::new_unchecked(col_ptr.as_ptr()
|
||||||
.add(res_index.0 as usize * col_info.layout().size()));
|
.add(record.index.0 as usize * col_info.layout().size()));
|
||||||
let col = arch.get_column_mut(col_type).unwrap();
|
let col = arch.get_column_mut(col_type).unwrap();
|
||||||
|
// set_at is used since the entity was reserved
|
||||||
col.set_at(res_index.0 as _, ptr, tick);
|
col.set_at(res_index.0 as _, ptr, tick);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bundle.take(|data, type_id, _size| {
|
bundle.take(|data, type_id, _size| {
|
||||||
let col = arch.get_column_mut(type_id).unwrap();
|
let col = arch.get_column_mut(type_id).unwrap();
|
||||||
|
// set_at is used since the entity was reserved
|
||||||
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
unsafe { col.set_at(res_index.0 as _, data, tick); }
|
||||||
col.len += 1;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
arch.entity_ids.insert(entity, res_index);
|
arch.entity_ids.insert(entity, res_index);
|
||||||
|
arch.ensure_synced();
|
||||||
|
|
||||||
let new_record = Record {
|
let new_record = Record {
|
||||||
id: arch.id(),
|
id: arch.id(),
|
||||||
|
@ -178,10 +181,30 @@ impl World {
|
||||||
};
|
};
|
||||||
self.entities.insert_entity_record(entity, new_record);
|
self.entities.insert_entity_record(entity, new_record);
|
||||||
} else {
|
} else {
|
||||||
|
if current_arch_len == 1 {
|
||||||
|
// if this entity is the only entity for this archetype, add more columns to it
|
||||||
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||||
|
current_arch.extend(&tick, vec![bundle]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let new_arch_id = self.next_archetype_id.increment();
|
let new_arch_id = self.next_archetype_id.increment();
|
||||||
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos);
|
||||||
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
let entity_arch_id = archetype.add_entity(entity, bundle, &tick);
|
||||||
|
|
||||||
|
// move the old components into the new archetype
|
||||||
|
for (column_ptr, column_info) in col_ptrs.into_iter() {
|
||||||
|
unsafe {
|
||||||
|
// ptr of component for the entity
|
||||||
|
let comp_ptr = NonNull::new_unchecked(column_ptr.as_ptr()
|
||||||
|
.add(record.index.0 as usize * column_info.layout().size()));
|
||||||
|
|
||||||
|
let col = archetype.get_column_mut(column_info.type_id()).unwrap();
|
||||||
|
col.insert_entity(entity_arch_id.0 as _, comp_ptr, tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.archetypes.insert(new_arch_id, archetype);
|
self.archetypes.insert(new_arch_id, archetype);
|
||||||
|
|
||||||
// Create entity record and store it
|
// Create entity record and store it
|
||||||
|
@ -194,7 +217,13 @@ impl World {
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
let current_arch = self.archetypes.get_mut(&record.id).unwrap();
|
||||||
|
if current_arch.len() > 1 {
|
||||||
current_arch.remove_entity(entity, &tick);
|
current_arch.remove_entity(entity, &tick);
|
||||||
|
} else if current_arch.len() == 1 {
|
||||||
|
// The old archetype will only be removed if there was another archetype that would
|
||||||
|
// work for the entity's components, and the old archetype only had a single entity.
|
||||||
|
self.archetypes.remove(&record.id).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> {
|
||||||
|
@ -225,6 +254,13 @@ impl World {
|
||||||
v.into_iter()
|
v.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// View into the world for a set of entities that satisfy the queries.
|
||||||
|
pub fn filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> {
|
||||||
|
let archetypes = self.archetypes.values().collect();
|
||||||
|
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes);
|
||||||
|
v.into_iter()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn dynamic_view(&self) -> DynamicView {
|
pub fn dynamic_view(&self) -> DynamicView {
|
||||||
DynamicView::new(self)
|
DynamicView::new(self)
|
||||||
}
|
}
|
||||||
|
@ -330,7 +366,7 @@ impl World {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{tests::{Vec2, Vec3}, query::TickOf};
|
use crate::{query::TickOf, tests::{Vec2, Vec3}, Entity};
|
||||||
|
|
||||||
use super::World;
|
use super::World;
|
||||||
|
|
||||||
|
@ -453,6 +489,46 @@ mod tests {
|
||||||
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
assert!(world.view_one::<&Vec3>(e).get().is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn insert_multiple_times() {
|
||||||
|
let v2s = &[Vec2::rand(), Vec2::rand(), Vec2::rand()];
|
||||||
|
let v3s = &[Vec3::rand(), Vec3::rand(), Vec3::rand()];
|
||||||
|
|
||||||
|
let mut world = World::new();
|
||||||
|
let e1 = world.spawn(v2s[0]);
|
||||||
|
let e2 = world.spawn(v2s[1]);
|
||||||
|
let e3 = world.spawn(v2s[2]);
|
||||||
|
println!("Spawned entities");
|
||||||
|
|
||||||
|
let ev2 = world.view_one::<&Vec2>(e2).get()
|
||||||
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||||
|
assert_eq!(*ev2, v2s[1]);
|
||||||
|
drop(ev2);
|
||||||
|
|
||||||
|
let insert_and_assert = |world: &mut World, e: Entity, v2: Vec2, v3: Vec3| {
|
||||||
|
println!("inserting entity");
|
||||||
|
world.insert(e, (v3,));
|
||||||
|
println!("inserted entity");
|
||||||
|
|
||||||
|
let (ev2, ev3) = world.view_one::<(&Vec2, &Vec3)>(e).get()
|
||||||
|
.expect("Failed to find Vec2 and Vec3 on inserted entity!");
|
||||||
|
assert_eq!(*ev2, v2);
|
||||||
|
assert_eq!(*ev3, v3);
|
||||||
|
};
|
||||||
|
|
||||||
|
insert_and_assert(&mut world, e2, v2s[1], v3s[1]);
|
||||||
|
println!("Entity 2 is good");
|
||||||
|
insert_and_assert(&mut world, e3, v2s[2], v3s[2]);
|
||||||
|
println!("Entity 3 is good");
|
||||||
|
assert_eq!(world.archetypes.len(), 2);
|
||||||
|
println!("No extra archetypes were created");
|
||||||
|
|
||||||
|
insert_and_assert(&mut world, e1, v2s[0], v3s[0]);
|
||||||
|
println!("Entity 1 is good");
|
||||||
|
assert_eq!(world.archetypes.len(), 1);
|
||||||
|
println!("Empty archetype was removed");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn view_one() {
|
fn view_one() {
|
||||||
let v = Vec2::rand();
|
let v = Vec2::rand();
|
||||||
|
@ -468,11 +544,6 @@ mod tests {
|
||||||
fn view_change_tracking() {
|
fn view_change_tracking() {
|
||||||
let mut world = World::new();
|
let mut world = World::new();
|
||||||
|
|
||||||
/* let v = Vec2::rand();
|
|
||||||
world.spawn((v,));
|
|
||||||
let v = Vec2::rand();
|
|
||||||
world.spawn((v,)); */
|
|
||||||
|
|
||||||
println!("spawning");
|
println!("spawning");
|
||||||
world.spawn((Vec2::new(10.0, 10.0),));
|
world.spawn((Vec2::new(10.0, 10.0),));
|
||||||
world.spawn((Vec2::new(5.0, 5.0),));
|
world.spawn((Vec2::new(5.0, 5.0),));
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use lyra_resource::{ResHandle, Texture};
|
||||||
|
|
||||||
use super::texture::RenderTexture;
|
use super::texture::RenderTexture;
|
||||||
|
|
||||||
pub struct MaterialSpecular {
|
pub struct MaterialSpecular {
|
||||||
|
@ -9,13 +11,18 @@ pub struct MaterialSpecular {
|
||||||
pub color_texture: Option<RenderTexture>,
|
pub color_texture: Option<RenderTexture>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaterialSpecular {
|
fn texture_to_render(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: &Arc<wgpu::BindGroupLayout>, i: &Option<ResHandle<Texture>>) -> Option<RenderTexture> {
|
||||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Specular) -> Self {
|
if let Some(tex) = i {
|
||||||
let tex = value.texture.as_ref().map(|t| t.data_ref())
|
RenderTexture::from_resource(device, queue, bg_layout.clone(), tex, None).ok()
|
||||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap());
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let color_tex = value.color_texture.as_ref().map(|t| t.data_ref())
|
impl MaterialSpecular {
|
||||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout, &i.image, None).unwrap());
|
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Specular) -> Self {
|
||||||
|
let tex = texture_to_render(device, queue, &bg_layout, &value.texture);
|
||||||
|
let color_tex = texture_to_render(device, queue, &bg_layout, &value.color_texture);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
factor: value.factor,
|
factor: value.factor,
|
||||||
|
@ -38,9 +45,8 @@ pub struct Material {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Material {
|
impl Material {
|
||||||
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::Material) -> Self {
|
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, value: &lyra_resource::gltf::Material) -> Self {
|
||||||
let diffuse_texture = value.base_color_texture.as_ref().map(|t| t.data_ref())
|
let diffuse_texture = texture_to_render(device, queue, &bg_layout, &value.base_color_texture);
|
||||||
.map(|i| RenderTexture::from_image(device, queue, bg_layout.clone(), &i.image, None).unwrap());
|
|
||||||
|
|
||||||
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
|
let specular = value.specular.as_ref().map(|s| MaterialSpecular::from_resource(device, queue, bg_layout.clone(), s));
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
use lyra_ecs::Entity;
|
use lyra_ecs::Entity;
|
||||||
|
|
||||||
use crate::math::Transform;
|
|
||||||
|
|
||||||
pub struct RenderJob {
|
pub struct RenderJob {
|
||||||
pub entity: Entity,
|
pub entity: Entity,
|
||||||
pub shader_id: u64,
|
pub shader_id: u64,
|
||||||
pub mesh_buffer_id: uuid::Uuid,
|
pub mesh_uuid: uuid::Uuid,
|
||||||
pub transform: Transform,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderJob {
|
impl RenderJob {
|
||||||
pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform) -> Self {
|
pub fn new(entity: Entity, shader_id: u64, mesh_buffer_id: uuid::Uuid) -> Self {
|
||||||
Self {
|
Self {
|
||||||
entity,
|
entity,
|
||||||
shader_id,
|
shader_id,
|
||||||
mesh_buffer_id,
|
mesh_uuid: mesh_buffer_id,
|
||||||
transform,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -5,10 +5,13 @@ use std::borrow::Cow;
|
||||||
use glam::Vec3;
|
use glam::Vec3;
|
||||||
use instant::Instant;
|
use instant::Instant;
|
||||||
use itertools::izip;
|
use itertools::izip;
|
||||||
use lyra_ecs::Entity;
|
use lyra_ecs::query::filter::{Has, Or};
|
||||||
|
use lyra_ecs::{Entity, Tick};
|
||||||
use lyra_ecs::query::{Entities, TickOf};
|
use lyra_ecs::query::{Entities, TickOf};
|
||||||
use lyra_ecs::World;
|
use lyra_ecs::World;
|
||||||
|
use lyra_resource::gltf::GltfScene;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
use uuid::Uuid;
|
||||||
use wgpu::{BindGroupLayout, Limits};
|
use wgpu::{BindGroupLayout, Limits};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use winit::window::Window;
|
use winit::window::Window;
|
||||||
|
@ -16,7 +19,7 @@ use winit::window::Window;
|
||||||
use crate::math::Transform;
|
use crate::math::Transform;
|
||||||
use crate::render::material::MaterialUniform;
|
use crate::render::material::MaterialUniform;
|
||||||
use crate::render::render_buffer::BufferWrapperBuilder;
|
use crate::render::render_buffer::BufferWrapperBuilder;
|
||||||
use crate::scene::{ModelComponent, CameraComponent};
|
use crate::scene::CameraComponent;
|
||||||
|
|
||||||
use super::camera::{RenderCamera, CameraUniform};
|
use super::camera::{RenderCamera, CameraUniform};
|
||||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||||
|
@ -28,7 +31,10 @@ use super::transform_buffer_storage::TransformBuffers;
|
||||||
use super::vertex::Vertex;
|
use super::vertex::Vertex;
|
||||||
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
|
||||||
|
|
||||||
use lyra_resource::Mesh;
|
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||||
|
|
||||||
|
type MeshHandle = ResHandle<Mesh>;
|
||||||
|
type SceneHandle = ResHandle<GltfScene>;
|
||||||
|
|
||||||
pub trait Renderer {
|
pub trait Renderer {
|
||||||
fn prepare(&mut self, main_world: &mut World);
|
fn prepare(&mut self, main_world: &mut World);
|
||||||
|
@ -265,16 +271,19 @@ impl BasicRenderer {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_mesh_buffers(&mut self, _entity: Entity, mesh: &Mesh) {
|
/// Checks if the mesh buffers in the GPU need to be updated.
|
||||||
if let Some(buffers) = self.mesh_buffers.get_mut(&mesh.uuid) {
|
fn check_mesh_buffers(&mut self, _entity: Entity, meshh: &ResHandle<Mesh>) {
|
||||||
|
let mesh_uuid = meshh.uuid();
|
||||||
|
|
||||||
|
if let (Some(mesh), Some(buffers)) = (meshh.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) {
|
||||||
// check if the buffer sizes dont match. If they dont, completely remake the buffers
|
// check if the buffer sizes dont match. If they dont, completely remake the buffers
|
||||||
let vertices = mesh.position().unwrap();
|
let vertices = mesh.position().unwrap();
|
||||||
if buffers.buffer_vertex.count() != vertices.len() {
|
if buffers.buffer_vertex.count() != vertices.len() {
|
||||||
debug!("Recreating buffers for mesh {}", mesh.uuid.to_string());
|
debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
|
||||||
let (vert, idx) = self.create_vertex_index_buffers(mesh);
|
let (vert, idx) = self.create_vertex_index_buffers(&mesh);
|
||||||
|
|
||||||
// have to re-get buffers because of borrow checker
|
// have to re-get buffers because of borrow checker
|
||||||
let buffers = self.mesh_buffers.get_mut(&mesh.uuid).unwrap();
|
let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap();
|
||||||
buffers.buffer_indices = idx;
|
buffers.buffer_indices = idx;
|
||||||
buffers.buffer_vertex = vert;
|
buffers.buffer_vertex = vert;
|
||||||
|
|
||||||
|
@ -292,8 +301,8 @@ impl BasicRenderer {
|
||||||
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
|
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
|
||||||
let aligned_indices = match mesh.indices.as_ref().unwrap() {
|
let aligned_indices = match mesh.indices.as_ref().unwrap() {
|
||||||
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
|
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
|
||||||
lyra_resource::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
|
lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
|
||||||
lyra_resource::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
|
lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let index_buffer = index_buffer.1.buffer();
|
let index_buffer = index_buffer.1.buffer();
|
||||||
|
@ -327,8 +336,8 @@ impl BasicRenderer {
|
||||||
let indices = match mesh.indices.as_ref() {
|
let indices = match mesh.indices.as_ref() {
|
||||||
Some(indices) => {
|
Some(indices) => {
|
||||||
let (idx_type, len, contents) = match indices {
|
let (idx_type, len, contents) = match indices {
|
||||||
lyra_resource::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
|
lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
|
||||||
lyra_resource::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
|
lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let index_buffer = self.device.create_buffer_init(
|
let index_buffer = self.device.create_buffer_init(
|
||||||
|
@ -354,7 +363,11 @@ impl BasicRenderer {
|
||||||
fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage {
|
fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage {
|
||||||
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
|
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
|
||||||
|
|
||||||
let material = Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &mesh.material());
|
let material = mesh.material.as_ref()
|
||||||
|
.expect("Material resource not loaded yet")
|
||||||
|
.data_ref()
|
||||||
|
.unwrap();
|
||||||
|
let material = Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &material);
|
||||||
let uni = MaterialUniform::from(&material);
|
let uni = MaterialUniform::from(&material);
|
||||||
self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni));
|
self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni));
|
||||||
debug!("Wrote material to buffer");
|
debug!("Wrote material to buffer");
|
||||||
|
@ -367,24 +380,57 @@ impl BasicRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
|
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
|
||||||
fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh) -> bool {
|
fn process_mesh(&mut self, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
|
||||||
if self.transform_buffers.should_expand() {
|
if self.transform_buffers.should_expand() {
|
||||||
self.transform_buffers.expand_buffers(&self.device);
|
self.transform_buffers.expand_buffers(&self.device);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
|
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
|
||||||
entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) ));
|
mesh_uuid, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) ));
|
||||||
|
|
||||||
#[allow(clippy::map_entry)]
|
#[allow(clippy::map_entry)]
|
||||||
if !self.mesh_buffers.contains_key(&mesh.uuid) {
|
if !self.mesh_buffers.contains_key(&mesh_uuid) {
|
||||||
// create the mesh's buffers
|
// create the mesh's buffers
|
||||||
let buffers = self.create_mesh_buffers(mesh);
|
let buffers = self.create_mesh_buffers(mesh);
|
||||||
self.mesh_buffers.insert(mesh.uuid, buffers);
|
self.mesh_buffers.insert(mesh_uuid, buffers);
|
||||||
self.entity_meshes.insert(entity, mesh.uuid);
|
self.entity_meshes.insert(entity, mesh_uuid);
|
||||||
|
|
||||||
true
|
true
|
||||||
} else { false }
|
} else { false }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn interpolate_transforms(&mut self, now: Instant, last_epoch: Tick, entity: Entity, transform: &Transform, transform_epoch: Tick) -> Transform {
|
||||||
|
let cached = match self.entity_last_transforms.get_mut(&entity) {
|
||||||
|
Some(last) if transform_epoch == last_epoch => {
|
||||||
|
last.from_transform = last.to_transform;
|
||||||
|
last.to_transform = *transform;
|
||||||
|
last.last_updated_at = Some(last.cached_at);
|
||||||
|
last.cached_at = now;
|
||||||
|
|
||||||
|
last.clone()
|
||||||
|
},
|
||||||
|
Some(last) => last.clone(),
|
||||||
|
None => {
|
||||||
|
let cached = CachedTransform {
|
||||||
|
last_updated_at: None,
|
||||||
|
cached_at: now,
|
||||||
|
from_transform: *transform,
|
||||||
|
to_transform: *transform,
|
||||||
|
};
|
||||||
|
self.entity_last_transforms.insert(entity, cached.clone());
|
||||||
|
cached
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let fixed_time = match cached.last_updated_at {
|
||||||
|
Some(last_updated_at) => cached.cached_at - last_updated_at,
|
||||||
|
None => now - cached.cached_at
|
||||||
|
}.as_secs_f32();
|
||||||
|
let accumulator = (now - cached.cached_at).as_secs_f32();
|
||||||
|
let alpha = accumulator / fixed_time;
|
||||||
|
|
||||||
|
cached.from_transform.lerp(cached.to_transform, alpha)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderer for BasicRenderer {
|
impl Renderer for BasicRenderer {
|
||||||
|
@ -395,56 +441,61 @@ impl Renderer for BasicRenderer {
|
||||||
|
|
||||||
let now_inst = Instant::now();
|
let now_inst = Instant::now();
|
||||||
|
|
||||||
for (entity, model, model_epoch, transform, transform_epoch) in main_world.view_iter::<(Entities, &ModelComponent, TickOf<ModelComponent>, &Transform, TickOf<Transform>)>() {
|
let view = main_world.filtered_view_iter::<(Entities, &Transform, TickOf<Transform>), Or<Has<MeshHandle>, Has<SceneHandle>>>();
|
||||||
|
for (entity, transform, transform_epoch) in view {
|
||||||
alive_entities.insert(entity);
|
alive_entities.insert(entity);
|
||||||
|
|
||||||
let cached = match self.entity_last_transforms.get_mut(&entity) {
|
let mesh_view = main_world.view_one::<(&MeshHandle, TickOf<MeshHandle>)>(entity);
|
||||||
Some(last) if transform_epoch == last_epoch => {
|
if let Some((mesh_han, mesh_epoch)) = mesh_view.get() {
|
||||||
last.from_transform = last.to_transform;
|
let interop_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch);
|
||||||
last.to_transform = *transform;
|
|
||||||
last.last_updated_at = Some(last.cached_at);
|
|
||||||
last.cached_at = now_inst;
|
|
||||||
|
|
||||||
last.clone()
|
if let Some(mesh) = mesh_han.data_ref() {
|
||||||
},
|
// if process mesh did not just create a new mesh, and the epoch
|
||||||
Some(last) => last.clone(),
|
// shows that the scene has changed, verify that the mesh buffers
|
||||||
None => {
|
// dont need to be resent to the gpu.
|
||||||
let cached = CachedTransform {
|
if !self.process_mesh(entity, interop_pos, &*mesh, mesh_han.uuid())
|
||||||
last_updated_at: None,
|
&& mesh_epoch == last_epoch {
|
||||||
cached_at: now_inst,
|
self.check_mesh_buffers(entity, &mesh_han);
|
||||||
from_transform: *transform,
|
|
||||||
to_transform: *transform,
|
|
||||||
};
|
|
||||||
self.entity_last_transforms.insert(entity, cached.clone());
|
|
||||||
cached
|
|
||||||
}
|
|
||||||
};
|
|
||||||
//debug!("Transform: {:?}, comp: {:?}", cached.to_transform.translation, transform.transform.translation);
|
|
||||||
|
|
||||||
let fixed_time = match cached.last_updated_at {
|
|
||||||
Some(last_updated_at) => cached.cached_at - last_updated_at,
|
|
||||||
None => now_inst - cached.cached_at
|
|
||||||
}.as_secs_f32();
|
|
||||||
let accumulator = (now_inst - cached.cached_at).as_secs_f32();
|
|
||||||
let alpha = accumulator / fixed_time;
|
|
||||||
|
|
||||||
let transform_val = cached.from_transform.lerp(cached.to_transform, alpha);
|
|
||||||
|
|
||||||
let model = model.data_ref();
|
|
||||||
for mesh in model.meshes.iter() {
|
|
||||||
if !self.process_mesh(entity, transform_val, mesh) && model_epoch == last_epoch {
|
|
||||||
self.update_mesh_buffers(entity, mesh);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let shader = mesh.material().shader_uuid.unwrap_or(0);
|
let material = mesh.material.as_ref().unwrap()
|
||||||
let job = RenderJob::new(entity, shader, mesh.uuid, transform_val);
|
.data_ref().unwrap();
|
||||||
|
let shader = material.shader_uuid.unwrap_or(0);
|
||||||
|
let job = RenderJob::new(entity, shader, mesh_han.uuid());
|
||||||
self.render_jobs.push_back(job);
|
self.render_jobs.push_back(job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* for (entity, mesh, mesh_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf<MeshComponent>, &TransformComponent)>().iter() {
|
let scene_view = main_world.view_one::<(&SceneHandle, TickOf<SceneHandle>)>(entity);
|
||||||
debug!("TODO: Process MeshComponents"); // TODO: Process MeshComponents
|
if let Some((scene_han, scene_epoch)) = scene_view.get() {
|
||||||
} */
|
// TODO: Rendering scenes
|
||||||
|
|
||||||
|
if let Some(scene) = scene_han.data_ref() {
|
||||||
|
let interpo_pos = self.interpolate_transforms(now_inst, last_epoch, entity, &transform, transform_epoch);
|
||||||
|
let meshes = scene.collect_world_meshes();
|
||||||
|
|
||||||
|
for (mesh_han, mesh_pos) in meshes.into_iter() {
|
||||||
|
if let Some(mesh) = mesh_han.data_ref() {
|
||||||
|
let mesh_interpo = interpo_pos + mesh_pos;
|
||||||
|
|
||||||
|
// if process mesh did not just create a new mesh, and the epoch
|
||||||
|
// shows that the scene has changed, verify that the mesh buffers
|
||||||
|
// dont need to be resent to the gpu.
|
||||||
|
if !self.process_mesh(entity, mesh_interpo, &*mesh, mesh_han.uuid())
|
||||||
|
&& scene_epoch == last_epoch {
|
||||||
|
self.check_mesh_buffers(entity, &mesh_han);
|
||||||
|
}
|
||||||
|
|
||||||
|
let material = mesh.material.as_ref().unwrap()
|
||||||
|
.data_ref().unwrap();
|
||||||
|
let shader = material.shader_uuid.unwrap_or(0);
|
||||||
|
let job = RenderJob::new(entity, shader, mesh_han.uuid());
|
||||||
|
self.render_jobs.push_back(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// collect dead entities
|
// collect dead entities
|
||||||
self.transform_buffers.tick();
|
self.transform_buffers.tick();
|
||||||
|
@ -513,7 +564,7 @@ impl Renderer for BasicRenderer {
|
||||||
render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
|
render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
|
||||||
|
|
||||||
// get the mesh (containing vertices) and the buffers from storage
|
// get the mesh (containing vertices) and the buffers from storage
|
||||||
let buffers = self.mesh_buffers.get(&job.mesh_buffer_id).unwrap();
|
let buffers = self.mesh_buffers.get(&job.mesh_uuid).unwrap();
|
||||||
|
|
||||||
// Bind the optional texture
|
// Bind the optional texture
|
||||||
if let Some(tex) = buffers.material.as_ref()
|
if let Some(tex) = buffers.material.as_ref()
|
||||||
|
@ -532,7 +583,7 @@ impl Renderer for BasicRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the bindgroup for job's transform and bind to it using an offset.
|
// Get the bindgroup for job's transform and bind to it using an offset.
|
||||||
let transform_indices = *self.transform_buffers.entity_indices(job.entity).unwrap();
|
let transform_indices = *self.transform_buffers.transform_indices(job.mesh_uuid).unwrap();
|
||||||
let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap();
|
let bindgroup = self.transform_buffers.bind_group(transform_indices).unwrap();
|
||||||
let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32;
|
let offset = TransformBuffers::index_offset(&self.render_limits, transform_indices) as u32;
|
||||||
render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);
|
render_pass.set_bind_group(1, bindgroup, &[ offset, offset, ]);
|
||||||
|
|
|
@ -1,29 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use image::GenericImageView;
|
use image::GenericImageView;
|
||||||
use lyra_resource::{ResHandle, Texture};
|
use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
|
||||||
|
|
||||||
use super::render_buffer::BindGroupPair;
|
use super::render_buffer::BindGroupPair;
|
||||||
|
|
||||||
/* #[derive(Clone)]
|
|
||||||
pub struct Texture {
|
|
||||||
texture_id: u32,
|
|
||||||
pub img: image::DynamicImage,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Texture {
|
|
||||||
pub fn from_bytes(bytes: &[u8]) -> anyhow::Result<Self> {
|
|
||||||
let img = image::load_from_memory(bytes)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
texture_id: 0,
|
|
||||||
img,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct RenderTexture {
|
pub struct RenderTexture {
|
||||||
pub inner_texture: wgpu::Texture,
|
pub inner_texture: wgpu::Texture,
|
||||||
|
@ -153,8 +134,101 @@ impl RenderTexture {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &Arc<ResHandle<Texture>>) {
|
pub fn from_resource(device: &wgpu::Device, queue: &wgpu::Queue, bg_layout: Arc<wgpu::BindGroupLayout>, texture_res: &ResHandle<Texture>, label: Option<&str>) -> anyhow::Result<Self> {
|
||||||
let texture = &texture.data_ref().image;
|
let texture_ref = texture_res.data_ref().unwrap();
|
||||||
|
let img = texture_ref.image.data_ref().unwrap();
|
||||||
|
|
||||||
|
let rgba = img.to_rgba8();
|
||||||
|
let dimensions = img.dimensions();
|
||||||
|
|
||||||
|
let size = wgpu::Extent3d {
|
||||||
|
width: dimensions.0,
|
||||||
|
height: dimensions.1,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
let texture = device.create_texture(
|
||||||
|
&wgpu::TextureDescriptor {
|
||||||
|
label,
|
||||||
|
size,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||||
|
view_formats: &[],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
queue.write_texture(
|
||||||
|
wgpu::ImageCopyTexture {
|
||||||
|
aspect: wgpu::TextureAspect::All,
|
||||||
|
texture: &texture,
|
||||||
|
mip_level: 0,
|
||||||
|
origin: wgpu::Origin3d::ZERO,
|
||||||
|
},
|
||||||
|
&rgba,
|
||||||
|
wgpu::ImageDataLayout {
|
||||||
|
offset: 0,
|
||||||
|
bytes_per_row: std::num::NonZeroU32::new(4 * dimensions.0),
|
||||||
|
rows_per_image: std::num::NonZeroU32::new(dimensions.1),
|
||||||
|
},
|
||||||
|
size,
|
||||||
|
);
|
||||||
|
|
||||||
|
// convert resource sampler into wgpu sampler
|
||||||
|
let sampler_desc = match &texture_ref.sampler {
|
||||||
|
Some(sampler) => {
|
||||||
|
let magf = res_filter_to_wgpu(sampler.mag_filter.unwrap_or(FilterMode::Linear));
|
||||||
|
let minf = res_filter_to_wgpu(sampler.min_filter.unwrap_or(FilterMode::Nearest));
|
||||||
|
let mipf = res_filter_to_wgpu(sampler.mipmap_filter.unwrap_or(FilterMode::Nearest));
|
||||||
|
|
||||||
|
let wrap_u = res_wrap_to_wgpu(sampler.wrap_u);
|
||||||
|
let wrap_v = res_wrap_to_wgpu(sampler.wrap_v);
|
||||||
|
let wrap_w = res_wrap_to_wgpu(sampler.wrap_w);
|
||||||
|
|
||||||
|
wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wrap_u,
|
||||||
|
address_mode_v: wrap_v,
|
||||||
|
address_mode_w: wrap_w,
|
||||||
|
mag_filter: magf,
|
||||||
|
min_filter: minf,
|
||||||
|
mipmap_filter: mipf,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let sampler = device.create_sampler(
|
||||||
|
&sampler_desc
|
||||||
|
);
|
||||||
|
|
||||||
|
let bgp = Self::create_bind_group_pair(device, bg_layout, &view, &sampler);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner_texture: texture,
|
||||||
|
view,
|
||||||
|
sampler,
|
||||||
|
bindgroup_pair: Some(bgp),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the texture on the gpu with the provided texture.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// Panics if `texture` is not loaded
|
||||||
|
pub fn update_texture(&mut self, _device: &wgpu::Device, queue: &wgpu::Queue, texture: &ResHandle<Texture>) {
|
||||||
|
let texture = &texture.data_ref().unwrap().image;
|
||||||
|
let texture = &texture.data_ref().unwrap();
|
||||||
let rgba = texture.to_rgba8();
|
let rgba = texture.to_rgba8();
|
||||||
let dimensions = texture.dimensions();
|
let dimensions = texture.dimensions();
|
||||||
let size = wgpu::Extent3d {
|
let size = wgpu::Extent3d {
|
||||||
|
@ -231,3 +305,22 @@ impl RenderTexture {
|
||||||
&self.bindgroup_pair.as_ref().unwrap().bindgroup
|
&self.bindgroup_pair.as_ref().unwrap().bindgroup
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert [`lyra_resource::WrappingMode`] to [`wgpu::AddressMode`]
|
||||||
|
#[inline(always)]
|
||||||
|
fn res_wrap_to_wgpu(wmode: WrappingMode) -> wgpu::AddressMode {
|
||||||
|
match wmode {
|
||||||
|
WrappingMode::ClampToEdge => wgpu::AddressMode::ClampToEdge,
|
||||||
|
WrappingMode::MirroredRepeat => wgpu::AddressMode::MirrorRepeat,
|
||||||
|
WrappingMode::Repeat => wgpu::AddressMode::Repeat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert [`lyra_resource::FilterMode`] to [`wgpu::FilterMode`]
|
||||||
|
#[inline(always)]
|
||||||
|
fn res_filter_to_wgpu(fmode: FilterMode) -> wgpu::FilterMode {
|
||||||
|
match fmode {
|
||||||
|
FilterMode::Nearest => wgpu::FilterMode::Nearest,
|
||||||
|
FilterMode::Linear => wgpu::FilterMode::Linear,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{collections::{VecDeque, HashMap}, num::NonZeroU64};
|
use std::{collections::{VecDeque, HashMap}, num::NonZeroU64};
|
||||||
|
|
||||||
use lyra_ecs::Entity;
|
use uuid::Uuid;
|
||||||
use wgpu::Limits;
|
use wgpu::Limits;
|
||||||
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -19,11 +19,20 @@ pub(crate) struct TransformBufferEntry {
|
||||||
pub bindgroup: wgpu::BindGroup,
|
pub bindgroup: wgpu::BindGroup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A helper struct for managing the Transform buffers for meshes.
|
||||||
|
///
|
||||||
|
/// This struct manages a "chain" of uniform buffers that store Transform for meshes. When
|
||||||
|
/// first created it only has a single "chain-link" with a buffer that is the maximum length
|
||||||
|
/// the GPU supports. When the first buffer fills up, a new one should be created which will also
|
||||||
|
/// be the maximum length the GPU supports. When the new buffer fills up, a new one will be
|
||||||
|
/// created once again, and so on.
|
||||||
|
///
|
||||||
|
/// `Uuid`'s are used to represent entries (usually Meshes) in the buffer. The Uuid's can be used
|
||||||
|
/// to insert, update, and retrieve the transforms.
|
||||||
pub(crate) struct TransformBuffers {
|
pub(crate) struct TransformBuffers {
|
||||||
pub bindgroup_layout: wgpu::BindGroupLayout,
|
pub bindgroup_layout: wgpu::BindGroupLayout,
|
||||||
/// A vector storing the EntityId and
|
pub just_updated: HashMap<Uuid, TransformBufferIndices>,
|
||||||
pub just_updated: HashMap<Entity, TransformBufferIndices>,
|
pub not_updated: HashMap<Uuid, TransformBufferIndices>,
|
||||||
pub not_updated: HashMap<Entity, TransformBufferIndices>,
|
|
||||||
pub dead_indices: VecDeque<TransformBufferIndices>,
|
pub dead_indices: VecDeque<TransformBufferIndices>,
|
||||||
pub next_indices: TransformBufferIndices,
|
pub next_indices: TransformBufferIndices,
|
||||||
/// (transform count, buffer, bindgroup)
|
/// (transform count, buffer, bindgroup)
|
||||||
|
@ -78,12 +87,15 @@ impl TransformBuffers {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update an entity's buffer with the new transform. Will panic if the entity isn't stored
|
/// Update an transform in the buffer.
|
||||||
pub fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
///
|
||||||
let indices = self.not_updated.remove(&entity)
|
/// # Panics
|
||||||
.or_else(|| self.just_updated.remove(&entity))
|
/// Panics if the entity isn't stored, you can check if it is before with [`TransformBuffers:contains`].
|
||||||
|
pub fn update_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
||||||
|
let indices = self.not_updated.remove(&uuid)
|
||||||
|
.or_else(|| self.just_updated.remove(&uuid))
|
||||||
.expect("Use 'insert_entity' for new entities");
|
.expect("Use 'insert_entity' for new entities");
|
||||||
self.just_updated.insert(entity, indices);
|
self.just_updated.insert(uuid, indices);
|
||||||
|
|
||||||
let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
|
let normal_matrix = glam::Mat4::from_mat3(normal_matrix);
|
||||||
|
|
||||||
|
@ -94,8 +106,8 @@ impl TransformBuffers {
|
||||||
indices
|
indices
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new entity into the buffer, returns where it was stored.
|
/// Insert a new transform into the buffer, returns where in the buffer it was stored.
|
||||||
pub fn insert_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
pub fn insert_transform(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform: glam::Mat4, normal_matrix: glam::Mat3) -> TransformBufferIndices {
|
||||||
let indices = match self.dead_indices.pop_front() {
|
let indices = match self.dead_indices.pop_front() {
|
||||||
Some(indices) => indices,
|
Some(indices) => indices,
|
||||||
None => {
|
None => {
|
||||||
|
@ -113,28 +125,28 @@ impl TransformBuffers {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.just_updated.insert(entity, indices);
|
self.just_updated.insert(uuid, indices);
|
||||||
self.update_entity(queue, limits, entity, transform, normal_matrix)
|
self.update_transform(queue, limits, uuid, transform, normal_matrix)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update or insert an entities transform
|
/// Update or insert a transform
|
||||||
pub fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: Entity, transform_fn: TFn) -> TransformBufferIndices
|
pub fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, uuid: Uuid, transform_fn: TFn) -> TransformBufferIndices
|
||||||
where TFn: Fn() -> (glam::Mat4, glam::Mat3)
|
where TFn: Fn() -> (glam::Mat4, glam::Mat3)
|
||||||
{
|
{
|
||||||
let (tran, norm) = transform_fn();
|
let (tran, norm) = transform_fn();
|
||||||
if self.contains(entity) {
|
if self.contains(uuid) {
|
||||||
self.update_entity(queue, limits, entity, tran, norm)
|
self.update_transform(queue, limits, uuid, tran, norm)
|
||||||
} else {
|
} else {
|
||||||
self.insert_entity(queue, limits, entity, tran, norm)
|
self.insert_transform(queue, limits, uuid, tran, norm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the entity's transform is stored (does not mean its up-to-date).
|
/// Returns true if the transform related to the `uuid` is stored (does not mean its up-to-date).
|
||||||
pub fn contains(&self, entity: Entity) -> bool {
|
pub fn contains(&self, uuid: Uuid) -> bool {
|
||||||
self.not_updated.contains_key(&entity) || self.just_updated.contains_key(&entity)
|
self.not_updated.contains_key(&uuid) || self.just_updated.contains_key(&uuid)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect the dead entities, mark entities and not updated for next updates.
|
/// Collect the dead transforms and prepare self to check next time.
|
||||||
pub fn tick(&mut self) {
|
pub fn tick(&mut self) {
|
||||||
// take the dead entities, these were ones that were not updated this tick
|
// take the dead entities, these were ones that were not updated this tick
|
||||||
let dead: VecDeque<TransformBufferIndices> = self.not_updated.values().copied().collect();
|
let dead: VecDeque<TransformBufferIndices> = self.not_updated.values().copied().collect();
|
||||||
|
@ -164,7 +176,10 @@ impl TransformBuffers {
|
||||||
.map(|entry| &entry.bindgroup)
|
.map(|entry| &entry.bindgroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expand the Transform buffers by adding another uniform buffer binding
|
/// Expand the Transform buffers by adding another uniform buffer binding.
|
||||||
|
///
|
||||||
|
/// This object has a chain of uniform buffers, when the buffers are expanded, a new
|
||||||
|
/// "chain-link" is created.
|
||||||
pub fn expand_buffers(&mut self, device: &wgpu::Device) {
|
pub fn expand_buffers(&mut self, device: &wgpu::Device) {
|
||||||
let limits = device.limits();
|
let limits = device.limits();
|
||||||
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
|
let max_buffer_sizes = (limits.max_uniform_buffer_binding_size as u64) / 2;
|
||||||
|
@ -228,7 +243,8 @@ impl TransformBuffers {
|
||||||
self.buffer_bindgroups.push(entry);
|
self.buffer_bindgroups.push(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn entity_indices(&self, entity: Entity) -> Option<&TransformBufferIndices> {
|
/// Returns the indices of the Transform
|
||||||
self.just_updated.get(&entity).or_else(|| self.not_updated.get(&entity))
|
pub fn transform_indices(&self, uuid: Uuid) -> Option<&TransformBufferIndices> {
|
||||||
|
self.just_updated.get(&uuid).or_else(|| self.not_updated.get(&uuid))
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,15 +1,37 @@
|
||||||
use lyra_ecs::Component;
|
use lyra_ecs::Component;
|
||||||
use lyra_resource::Mesh;
|
use lyra_reflect::Reflect;
|
||||||
|
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||||
|
|
||||||
#[derive(Clone, Component)]
|
#[derive(Clone, Component, Reflect)]
|
||||||
pub struct MeshComponent {
|
pub struct MeshComponent {
|
||||||
pub mesh: Mesh,
|
#[reflect(skip)]
|
||||||
|
pub mesh: ResHandle<Mesh>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ResHandle<Mesh>> for MeshComponent {
|
||||||
|
fn from(value: ResHandle<Mesh>) -> Self {
|
||||||
|
Self {
|
||||||
|
mesh: value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for MeshComponent {
|
||||||
|
type Target = ResHandle<Mesh>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.mesh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for MeshComponent {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.mesh
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeshComponent {
|
impl MeshComponent {
|
||||||
pub fn new(mesh: Mesh) -> Self {
|
pub fn new(mesh: ResHandle<Mesh>) -> Self {
|
||||||
Self {
|
Self::from(mesh)
|
||||||
mesh,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub use mesh::*;
|
pub use mesh::*;
|
||||||
|
|
||||||
pub mod model;
|
|
||||||
pub use model::*;
|
|
||||||
|
|
||||||
pub mod camera;
|
pub mod camera;
|
||||||
pub use camera::*;
|
pub use camera::*;
|
||||||
|
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
use lyra_ecs::Component;
|
|
||||||
use lyra_reflect::Reflect;
|
|
||||||
use lyra_resource::ResHandle;
|
|
||||||
|
|
||||||
use crate::assets::Model;
|
|
||||||
|
|
||||||
#[derive(Clone, Component, Reflect)]
|
|
||||||
pub struct ModelComponent(#[reflect(skip)] pub ResHandle<Model>);
|
|
||||||
|
|
||||||
impl From<ResHandle<Model>> for ModelComponent {
|
|
||||||
fn from(value: ResHandle<Model>) -> Self {
|
|
||||||
ModelComponent(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* impl From<ResHandle<Model> for ModelComponent {
|
|
||||||
|
|
||||||
} */
|
|
||||||
|
|
||||||
impl std::ops::Deref for ModelComponent {
|
|
||||||
type Target = ResHandle<Model>;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::ops::DerefMut for ModelComponent {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* impl ModelComponent {
|
|
||||||
pub fn new(model, material: Material) -> Self {
|
|
||||||
Self {
|
|
||||||
mesh,
|
|
||||||
material
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} */
|
|
|
@ -93,6 +93,13 @@ impl Transform {
|
||||||
trans.z += z;
|
trans.z += z;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Combines a transform with another one.
|
||||||
|
///
|
||||||
|
/// The translations are added while the rotations and scales are multiplied.
|
||||||
|
pub fn combine(self, rhs: Transform) -> Transform {
|
||||||
|
self + rhs
|
||||||
|
}
|
||||||
|
|
||||||
/// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`.
|
/// Performs a linear interpolation between `self` and `rhs` based on the value `alpha`.
|
||||||
///
|
///
|
||||||
/// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result
|
/// When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`, the result
|
||||||
|
|
|
@ -6,7 +6,8 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
lyra-ecs = { path = "../lyra-ecs" }
|
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||||
|
lyra-math = { path = "../lyra-math" }
|
||||||
anyhow = "1.0.75"
|
anyhow = "1.0.75"
|
||||||
base64 = "0.21.4"
|
base64 = "0.21.4"
|
||||||
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
|
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
|
||||||
|
@ -23,3 +24,4 @@ percent-encoding = "2.3.0"
|
||||||
thiserror = "1.0.48"
|
thiserror = "1.0.48"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
uuid = { version = "1.4.1", features = ["v4"] }
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
instant = "0.1"
|
|
@ -1,8 +1,12 @@
|
||||||
use std::{sync::Arc, path::PathBuf};
|
use std::{sync::Arc, path::PathBuf};
|
||||||
|
|
||||||
|
use glam::{Quat, Vec3};
|
||||||
|
use instant::Instant;
|
||||||
|
use lyra_math::Transform;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, ResHandle, Material, MeshIndices, ResourceManager, util};
|
use crate::{gltf::GltfScene, util, LoaderError, ResHandle, ResourceLoader, ResourceManager};
|
||||||
|
use super::{GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
|
||||||
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -60,16 +64,23 @@ impl ModelLoader {
|
||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
fn process_node(buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
|
fn process_node(ctx: &mut GltfLoadContext, materials: &Vec<ResHandle<Material>>, gnode: gltf::Node<'_>) -> GltfNode {
|
||||||
let mut meshes = vec![];
|
let mut node = GltfNode::default();
|
||||||
//node.transform()
|
|
||||||
|
|
||||||
if let Some(mesh) = node.mesh() {
|
node.transform = {
|
||||||
for prim in mesh.primitives() {
|
let gt = gnode.transform();
|
||||||
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice()));
|
let (pos, rot, scale) = gt.decomposed();
|
||||||
|
|
||||||
|
Transform::new(Vec3::from(pos), Quat::from_array(rot), Vec3::from(scale))
|
||||||
|
};
|
||||||
|
node.name = gnode.name().map(str::to_string);
|
||||||
|
|
||||||
|
if let Some(mesh) = gnode.mesh() {
|
||||||
let mut new_mesh = Mesh::default();
|
let mut new_mesh = Mesh::default();
|
||||||
|
|
||||||
|
for prim in mesh.primitives() {
|
||||||
|
let reader = prim.reader(|buf| Some(ctx.buffers[buf.index()].as_slice()));
|
||||||
|
|
||||||
// read the positions
|
// read the positions
|
||||||
if let Some(pos) = reader.read_positions() {
|
if let Some(pos) = reader.read_positions() {
|
||||||
if prim.mode() != gltf::mesh::Mode::Triangles {
|
if prim.mode() != gltf::mesh::Mode::Triangles {
|
||||||
|
@ -111,18 +122,20 @@ impl ModelLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mat = materials.get(prim.material().index().unwrap()).unwrap();
|
let mat = materials.get(prim.material().index().unwrap()).unwrap();
|
||||||
new_mesh.set_material(mat.clone());
|
new_mesh.material = Some(mat.clone());
|
||||||
|
|
||||||
meshes.push(new_mesh);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in node.children() {
|
let handle = ResHandle::with_data("", new_mesh);
|
||||||
let mut child_meshes = ModelLoader::process_node(buffers, materials, child);
|
ctx.resource_manager.store_uuid(handle.clone());
|
||||||
meshes.append(&mut child_meshes);
|
node.mesh = Some(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
meshes
|
for child in gnode.children() {
|
||||||
|
let cmesh = ModelLoader::process_node(ctx, materials, child);
|
||||||
|
node.children.push(cmesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,18 +160,6 @@ impl ResourceLoader for ModelLoader {
|
||||||
parent_path.pop();
|
parent_path.pop();
|
||||||
let parent_path = parent_path.display().to_string();
|
let parent_path = parent_path.display().to_string();
|
||||||
|
|
||||||
/* let (document, buffers, images) = gltf::import(&path)?;
|
|
||||||
let buffers: Vec<Vec<u8>> = buffers.into_iter().map(|b| b.0).collect();
|
|
||||||
|
|
||||||
let scene = document.scenes().next().unwrap();
|
|
||||||
|
|
||||||
let materials: Vec<Material> = document.materials()
|
|
||||||
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
|
|
||||||
|
|
||||||
let meshes: Vec<Mesh> = scene.nodes()
|
|
||||||
.map(|node| self.process_node(&buffers, &materials, node))
|
|
||||||
.flatten().collect(); */
|
|
||||||
|
|
||||||
let gltf = gltf::Gltf::open(path)?;
|
let gltf = gltf::Gltf::open(path)?;
|
||||||
|
|
||||||
let mut use_bin = false;
|
let mut use_bin = false;
|
||||||
|
@ -172,8 +173,7 @@ impl ResourceLoader for ModelLoader {
|
||||||
.map_err(ModelLoaderError::UriDecodingError),
|
.map_err(ModelLoaderError::UriDecodingError),
|
||||||
}).collect();
|
}).collect();
|
||||||
|
|
||||||
// TODO: Read in multiple scenes
|
let mut gltf_out = super::Gltf::default();
|
||||||
let scene = gltf.scenes().next().unwrap();
|
|
||||||
|
|
||||||
let mut context = GltfLoadContext {
|
let mut context = GltfLoadContext {
|
||||||
resource_manager,
|
resource_manager,
|
||||||
|
@ -183,15 +183,38 @@ impl ResourceLoader for ModelLoader {
|
||||||
buffers: &buffers,
|
buffers: &buffers,
|
||||||
};
|
};
|
||||||
|
|
||||||
let materials: Vec<Material> = gltf.materials()
|
let start_inst = Instant::now();
|
||||||
.map(|mat| Material::from_gltf(&mut context, mat)).collect();
|
let materials: Vec<ResHandle<Material>> = gltf.materials()
|
||||||
|
.map(|mat| ResHandle::with_data("", Material::from_gltf(&mut context, mat)))
|
||||||
let meshes: Vec<Mesh> = scene.nodes()
|
|
||||||
.flat_map(|node| ModelLoader::process_node(&buffers, &materials, node))
|
|
||||||
.collect();
|
.collect();
|
||||||
debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path);
|
let mat_time = Instant::now() - start_inst;
|
||||||
|
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
|
||||||
|
|
||||||
Ok(Arc::new(ResHandle::with_data(path, Model::new(meshes))))
|
for (idx, scene) in gltf.scenes().enumerate() {
|
||||||
|
let start_inst = Instant::now();
|
||||||
|
let nodes: Vec<GltfNode> = scene.nodes()
|
||||||
|
.map(|node| ModelLoader::process_node(&mut context, &materials, node))
|
||||||
|
.collect();
|
||||||
|
let node_time = Instant::now() - start_inst;
|
||||||
|
|
||||||
|
debug!("Loaded {} nodes in the scene in {}s", nodes.len(), node_time.as_secs_f32());
|
||||||
|
|
||||||
|
for mesh in nodes.iter().map(|n| &n.mesh) {
|
||||||
|
if let Some(mesh) = mesh {
|
||||||
|
gltf_out.meshes.push(mesh.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let scene = GltfScene {
|
||||||
|
nodes,
|
||||||
|
};
|
||||||
|
let scene = ResHandle::with_data(&format!("{}:Scene{}", path, idx), scene);
|
||||||
|
gltf_out.scenes.push(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
gltf_out.materials = materials;
|
||||||
|
|
||||||
|
Ok(Arc::new(ResHandle::with_data(path, gltf_out)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
|
@ -202,7 +225,7 @@ impl ResourceLoader for ModelLoader {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::ResourceLoader;
|
use crate::{gltf::Gltf, ResourceLoader};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn test_file_path(path: &str) -> String {
|
fn test_file_path(path: &str) -> String {
|
||||||
|
@ -217,16 +240,27 @@ mod tests {
|
||||||
|
|
||||||
let mut manager = ResourceManager::new();
|
let mut manager = ResourceManager::new();
|
||||||
let loader = ModelLoader::default();
|
let loader = ModelLoader::default();
|
||||||
let model = loader.load(&mut manager, &path).unwrap();
|
let gltf = loader.load(&mut manager, &path).unwrap();
|
||||||
let model = Arc::downcast::<ResHandle<Model>>(model.as_arc_any()).unwrap();
|
let gltf = Arc::downcast::<ResHandle<Gltf>>(gltf.as_arc_any()).unwrap();
|
||||||
let model = model.data_ref();
|
let gltf = gltf.data_ref().unwrap();
|
||||||
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
|
|
||||||
let mesh = &model.meshes[0];
|
assert_eq!(gltf.scenes.len(), 1);
|
||||||
|
let scene = &gltf.scenes[0]
|
||||||
|
.data_ref().unwrap();
|
||||||
|
|
||||||
|
assert_eq!(scene.nodes.len(), 1);
|
||||||
|
let mnode = &scene.nodes[0];
|
||||||
|
|
||||||
|
assert!(mnode.mesh.is_some());
|
||||||
|
assert_eq!(mnode.transform, Transform::from_xyz(0.0, 0.0, 0.0));
|
||||||
|
assert_eq!(mnode.children.len(), 0);
|
||||||
|
|
||||||
|
let mesh = mnode.mesh.as_ref().unwrap();
|
||||||
|
let mesh = mesh.data_ref().unwrap();
|
||||||
assert!(mesh.position().unwrap().len() > 0);
|
assert!(mesh.position().unwrap().len() > 0);
|
||||||
assert!(mesh.normals().unwrap().len() > 0);
|
assert!(mesh.normals().unwrap().len() > 0);
|
||||||
assert!(mesh.tex_coords().unwrap().len() > 0);
|
assert!(mesh.tex_coords().unwrap().len() > 0);
|
||||||
assert!(mesh.indices.clone().unwrap().len() > 0);
|
assert!(mesh.indices.clone().unwrap().len() > 0);
|
||||||
assert!(mesh.material().base_color_texture.is_some());
|
assert!(mesh.material.as_ref().unwrap().data_ref().unwrap().base_color_texture.is_some());
|
||||||
let _mesh_mat = mesh.material(); // inner panic if material was not loaded
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,9 @@
|
||||||
use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
|
use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
|
||||||
|
|
||||||
use crate::{Texture, ResHandle, util, loader::model::GltfLoadContext};
|
use gltf::texture::{MagFilter, MinFilter};
|
||||||
|
|
||||||
|
use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode};
|
||||||
|
use super::loader::GltfLoadContext;
|
||||||
|
|
||||||
/// PBR metallic roughness
|
/// PBR metallic roughness
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
|
@ -247,15 +250,33 @@ impl Material {
|
||||||
fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
|
fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
|
||||||
// TODO: texture_info.tex_coord()
|
// TODO: texture_info.tex_coord()
|
||||||
let tex = texture_info.texture();
|
let tex = texture_info.texture();
|
||||||
|
// TODO: tex.sampler()
|
||||||
let img = tex.source();
|
let img = tex.source();
|
||||||
let src = img.source();
|
let src = img.source();
|
||||||
|
|
||||||
let buf = Material::read_source(context, src).unwrap();
|
let buf = Material::read_source(context, src).unwrap();
|
||||||
let buflen = buf.len();
|
let buflen = buf.len();
|
||||||
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type();
|
let mime_type = infer::get(&buf).expect("Failed to get image type").mime_type();
|
||||||
|
|
||||||
context.resource_manager.load_bytes::<Texture>(&uuid::Uuid::new_v4().to_string(), mime_type,
|
let tex_img = context.resource_manager.load_bytes::<Image>(&uuid::Uuid::new_v4().to_string(), mime_type,
|
||||||
buf, 0, buflen).unwrap()
|
buf, 0, buflen).unwrap();
|
||||||
|
|
||||||
|
let samp = tex.sampler();
|
||||||
|
let samp = TextureSampler {
|
||||||
|
mag_filter: samp.mag_filter().map(FilterMode::from_mag_filter),
|
||||||
|
min_filter: samp.min_filter().map(FilterMode::from_min_filter),
|
||||||
|
mipmap_filter: samp.min_filter().and_then(FilterMode::mipmap_filter),
|
||||||
|
wrap_u: samp.wrap_s().into(),
|
||||||
|
wrap_v: samp.wrap_t().into(),
|
||||||
|
wrap_w: WrappingMode::ClampToEdge,
|
||||||
|
};
|
||||||
|
|
||||||
|
let handler = ResHandle::with_data("", Texture {
|
||||||
|
image: tex_img,
|
||||||
|
sampler: Some(samp),
|
||||||
|
});
|
||||||
|
context.resource_manager.store_uuid(handler.clone());
|
||||||
|
handler
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load the Material from a gltf::Material.
|
/// Load the Material from a gltf::Material.
|
||||||
|
@ -296,3 +317,59 @@ impl Material {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<gltf::texture::MagFilter> for FilterMode {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: gltf::texture::MagFilter) -> Self {
|
||||||
|
match value {
|
||||||
|
gltf::texture::MagFilter::Nearest => Self::Nearest,
|
||||||
|
gltf::texture::MagFilter::Linear => Self::Linear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FilterMode {
|
||||||
|
/// Get the MinFilter mode and the mipmap filter mode from gltf MinFilter
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_min_filter(minf: MinFilter) -> FilterMode {
|
||||||
|
match minf {
|
||||||
|
MinFilter::Nearest => FilterMode::Nearest,
|
||||||
|
MinFilter::Linear => FilterMode::Linear,
|
||||||
|
MinFilter::NearestMipmapNearest => FilterMode::Nearest,
|
||||||
|
MinFilter::LinearMipmapNearest => FilterMode::Linear,
|
||||||
|
MinFilter::NearestMipmapLinear => FilterMode::Nearest,
|
||||||
|
MinFilter::LinearMipmapLinear => FilterMode::Linear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn from_mag_filter(magf: MagFilter) -> FilterMode {
|
||||||
|
match magf {
|
||||||
|
MagFilter::Nearest => FilterMode::Nearest,
|
||||||
|
MagFilter::Linear => FilterMode::Linear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn mipmap_filter(minf: MinFilter) -> Option<FilterMode> {
|
||||||
|
match minf {
|
||||||
|
MinFilter::Nearest => None,
|
||||||
|
MinFilter::Linear => None,
|
||||||
|
MinFilter::NearestMipmapNearest => Some(FilterMode::Nearest),
|
||||||
|
MinFilter::LinearMipmapNearest => Some(FilterMode::Nearest),
|
||||||
|
MinFilter::NearestMipmapLinear => Some(FilterMode::Linear),
|
||||||
|
MinFilter::LinearMipmapLinear => Some(FilterMode::Linear),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<gltf::texture::WrappingMode> for WrappingMode {
|
||||||
|
#[inline(always)]
|
||||||
|
fn from(value: gltf::texture::WrappingMode) -> Self {
|
||||||
|
match value {
|
||||||
|
gltf::texture::WrappingMode::ClampToEdge => Self::ClampToEdge,
|
||||||
|
gltf::texture::WrappingMode::MirroredRepeat => Self::MirroredRepeat,
|
||||||
|
gltf::texture::WrappingMode::Repeat => Self::Repeat,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::Material;
|
use crate::{lyra_engine, ResHandle};
|
||||||
use crate::lyra_engine;
|
|
||||||
|
use super::Material;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -82,23 +83,11 @@ pub enum MeshVertexAttribute {
|
||||||
Other(String),
|
Other(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, lyra_ecs::Component)]
|
#[derive(Clone, Default, lyra_ecs::Component)]
|
||||||
pub struct Mesh {
|
pub struct Mesh {
|
||||||
pub uuid: uuid::Uuid,
|
|
||||||
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,
|
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,
|
||||||
pub indices: Option<MeshIndices>,
|
pub indices: Option<MeshIndices>,
|
||||||
material: Option<Material>,
|
pub material: Option<ResHandle<Material>>,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Mesh {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
uuid: uuid::Uuid::new_v4(),
|
|
||||||
attributes: Default::default(),
|
|
||||||
indices: Default::default(),
|
|
||||||
material: Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mesh {
|
impl Mesh {
|
||||||
|
@ -127,26 +116,4 @@ impl Mesh {
|
||||||
self.attributes.get(&MeshVertexAttribute::TexCoords)
|
self.attributes.get(&MeshVertexAttribute::TexCoords)
|
||||||
.map(|p| p.as_vec2())
|
.map(|p| p.as_vec2())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn material(&self) -> Material {
|
|
||||||
self.material.clone().expect("This mesh is missing a material!")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_material(&mut self, val: Material) {
|
|
||||||
self.material = Some(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
|
||||||
pub struct Model {
|
|
||||||
pub meshes: Vec<Mesh>,
|
|
||||||
//pub material
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Model {
|
|
||||||
pub fn new(meshes: Vec<Mesh>) -> Self {
|
|
||||||
Self {
|
|
||||||
meshes,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
pub mod loader;
|
||||||
|
pub use loader::*;
|
||||||
|
|
||||||
|
pub mod material;
|
||||||
|
use lyra_math::Transform;
|
||||||
|
pub use material::*;
|
||||||
|
|
||||||
|
pub mod mesh;
|
||||||
|
pub use mesh::*;
|
||||||
|
|
||||||
|
pub mod scene;
|
||||||
|
pub use scene::*;
|
||||||
|
|
||||||
|
use crate::ResHandle;
|
||||||
|
|
||||||
|
/// A loaded Gltf file
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct Gltf {
|
||||||
|
pub scenes: Vec<ResHandle<GltfScene>>,
|
||||||
|
pub materials: Vec<ResHandle<Material>>,
|
||||||
|
pub meshes: Vec<ResHandle<Mesh>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Gltf {
|
||||||
|
/// Collects all Gltf meshes and gets their world Transform.
|
||||||
|
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
for scene in self.scenes.iter() {
|
||||||
|
let mut tmp = scene.data_ref()
|
||||||
|
.unwrap().collect_world_meshes();
|
||||||
|
v.append(&mut tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
use lyra_math::Transform;
|
||||||
|
|
||||||
|
use super::Mesh;
|
||||||
|
use crate::ResHandle;
|
||||||
|
|
||||||
|
/// A Node in the Gltf file
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
pub struct GltfNode {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub mesh: Option<ResHandle<Mesh>>,
|
||||||
|
pub transform: Transform,
|
||||||
|
pub children: Vec<GltfNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Scene in a Gltf file
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GltfScene {
|
||||||
|
pub nodes: Vec<GltfNode>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GltfScene {
|
||||||
|
fn collect_node(&self, parent_node: &GltfNode, node: &GltfNode) -> Vec<(ResHandle<Mesh>, Transform)> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
if let Some(mesh) = &node.mesh {
|
||||||
|
v.push((mesh.clone(), parent_node.transform + node.transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in node.children.iter() {
|
||||||
|
let mut tmp = self.collect_node(node, child);
|
||||||
|
v.append(&mut tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collects all Gltf meshes and gets their world Transform.
|
||||||
|
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {
|
||||||
|
let mut v = vec![];
|
||||||
|
|
||||||
|
// process the root nodes in the scene
|
||||||
|
for parent_node in self.nodes.iter() {
|
||||||
|
if let Some(mesh) = &parent_node.mesh {
|
||||||
|
v.push((mesh.clone(), parent_node.transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
for child in parent_node.children.iter() {
|
||||||
|
let mut tmp = self.collect_node(parent_node, child);
|
||||||
|
v.append(&mut tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,11 +10,7 @@ pub use texture::*;
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
pub use loader::*;
|
pub use loader::*;
|
||||||
|
|
||||||
pub mod model;
|
pub mod gltf;
|
||||||
pub use model::*;
|
|
||||||
|
|
||||||
pub mod material;
|
|
||||||
pub use material::*;
|
|
||||||
|
|
||||||
pub mod world_ext;
|
pub mod world_ext;
|
||||||
pub use world_ext::*;
|
pub use world_ext::*;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use std::{fs::File, sync::Arc, io::Read};
|
use std::{fs::File, sync::Arc, io::Read};
|
||||||
|
|
||||||
use image::ImageError;
|
use image::ImageError;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::ResHandle, ResourceManager};
|
use crate::{resource::ResHandle, resource_manager::ResourceStorage, Image, ResourceManager};
|
||||||
|
|
||||||
use super::{LoaderError, ResourceLoader};
|
use super::{LoaderError, ResourceLoader};
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ impl From<ImageError> for LoaderError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct that implements the `ResourceLoader` trait used for loading textures.
|
/// A struct that implements the `ResourceLoader` trait used for loading images.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ImageLoader;
|
pub struct ImageLoader;
|
||||||
|
|
||||||
|
@ -59,24 +60,24 @@ impl ResourceLoader for ImageLoader {
|
||||||
ImageError::IoError(e) => LoaderError::IoError(e),
|
ImageError::IoError(e) => LoaderError::IoError(e),
|
||||||
_ => LoaderError::DecodingError(e.into()),
|
_ => LoaderError::DecodingError(e.into()),
|
||||||
})?;
|
})?;
|
||||||
let texture = Texture {
|
let image = Image::from(image);
|
||||||
image,
|
let res = ResHandle::with_data(path, image);
|
||||||
};
|
|
||||||
let res = ResHandle::with_data(path, texture);
|
|
||||||
|
|
||||||
Ok(Arc::new(res))
|
Ok(Arc::new(res))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
||||||
|
trace!("Loading {} bytes as an image", length);
|
||||||
|
|
||||||
let image = image::load_from_memory(&bytes[offset..(length-offset)])
|
let image = image::load_from_memory(&bytes[offset..(length-offset)])
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
ImageError::IoError(e) => LoaderError::IoError(e),
|
ImageError::IoError(e) => LoaderError::IoError(e),
|
||||||
_ => LoaderError::DecodingError(e.into()),
|
_ => LoaderError::DecodingError(e.into()),
|
||||||
})?;
|
})?;
|
||||||
let texture = Texture {
|
let image = Image::from(image);
|
||||||
image,
|
let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), image);
|
||||||
};
|
|
||||||
let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), texture);
|
debug!("Finished loading image of {} bytes", length);
|
||||||
|
|
||||||
Ok(Arc::new(res))
|
Ok(Arc::new(res))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod image;
|
pub mod image;
|
||||||
pub mod model;
|
|
||||||
|
|
||||||
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{any::Any, sync::{Arc, RwLock}};
|
use std::{any::Any, sync::{Arc, RwLock}};
|
||||||
|
|
||||||
|
use lyra_ecs::Component;
|
||||||
|
use crate::lyra_engine;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::ResourceStorage;
|
use crate::ResourceStorage;
|
||||||
|
@ -38,7 +40,8 @@ pub struct Resource<T> {
|
||||||
/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking.
|
/// This struct has an inner [`RwLock`] to the resource data, so most methods may be blocking.
|
||||||
/// However, the only times it will be blocking is if another thread is reloading the resource
|
/// However, the only times it will be blocking is if another thread is reloading the resource
|
||||||
/// and has a write lock on the data. This means that most of the time, it is not blocking.
|
/// and has a write lock on the data. This means that most of the time, it is not blocking.
|
||||||
pub struct ResHandle<T> {
|
#[derive(Component)]
|
||||||
|
pub struct ResHandle<T: 'static> {
|
||||||
pub(crate) data: Arc<RwLock<Resource<T>>>,
|
pub(crate) data: Arc<RwLock<Resource<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,18 +106,7 @@ impl<T> ResHandle<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a reference to the data in the resource
|
/// Get a reference to the data in the resource
|
||||||
///
|
pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
|
||||||
/// # Panics
|
|
||||||
/// Panics if the resource was not loaded yet.
|
|
||||||
pub fn data_ref<'a>(&'a self) -> ResourceDataRef<'a, T> {
|
|
||||||
let d = self.data.read().expect("Resource mutex was poisoned!");
|
|
||||||
ResourceDataRef {
|
|
||||||
guard: d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Attempt to get a borrow to the resource data. Returns `None` if the resource is not loaded.
|
|
||||||
pub fn try_data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
|
|
||||||
if self.is_loaded() {
|
if self.is_loaded() {
|
||||||
let d = self.data.read().expect("Resource mutex was poisoned!");
|
let d = self.data.read().expect("Resource mutex was poisoned!");
|
||||||
Some(ResourceDataRef {
|
Some(ResourceDataRef {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
|
use crate::{gltf::ModelLoader, loader::{image::ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
|
||||||
|
|
||||||
/// A trait for type erased storage of a resource.
|
/// A trait for type erased storage of a resource.
|
||||||
/// Implemented for [`ResHandle<T>`]
|
/// Implemented for [`ResHandle<T>`]
|
||||||
|
@ -56,6 +56,7 @@ pub struct ResourceWatcher {
|
||||||
|
|
||||||
pub struct ResourceManager {
|
pub struct ResourceManager {
|
||||||
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
||||||
|
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
|
||||||
loaders: Vec<Arc<dyn ResourceLoader>>,
|
loaders: Vec<Arc<dyn ResourceLoader>>,
|
||||||
watchers: HashMap<String, ResourceWatcher>,
|
watchers: HashMap<String, ResourceWatcher>,
|
||||||
}
|
}
|
||||||
|
@ -70,6 +71,7 @@ impl ResourceManager {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
resources: HashMap::new(),
|
resources: HashMap::new(),
|
||||||
|
uuid_resources: HashMap::new(),
|
||||||
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
|
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
|
||||||
watchers: HashMap::new(),
|
watchers: HashMap::new(),
|
||||||
}
|
}
|
||||||
|
@ -148,6 +150,29 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Store a resource using its uuid.
|
||||||
|
///
|
||||||
|
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
|
||||||
|
/// retrieved with [`ResourceManager::request_uuid`].
|
||||||
|
pub fn store_uuid<T: Send + Sync + 'static>(&mut self, res: ResHandle<T>) {
|
||||||
|
self.uuid_resources.insert(res.uuid(), Arc::new(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request a resource via its uuid.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the resource was not found. The resource must of had been
|
||||||
|
/// stored with [`ResourceManager::request`] to return `Some`.
|
||||||
|
pub fn request_uuid<T: Send + Sync + 'static>(&mut self, uuid: &Uuid) -> Option<ResHandle<T>> {
|
||||||
|
match self.uuid_resources.get(uuid) {
|
||||||
|
Some(res) => {
|
||||||
|
let res = res.clone().as_arc_any();
|
||||||
|
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
|
||||||
|
Some(ResHandle::<T>::clone(&res))
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
|
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
|
||||||
///
|
///
|
||||||
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
|
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
|
||||||
|
@ -304,7 +329,7 @@ mod tests {
|
||||||
let mut man = ResourceManager::new();
|
let mut man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
||||||
assert_eq!(res.state(), ResourceState::Ready);
|
assert_eq!(res.state(), ResourceState::Ready);
|
||||||
let img = res.try_data_ref();
|
let img = res.data_ref();
|
||||||
img.unwrap();
|
img.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,7 +365,7 @@ mod tests {
|
||||||
let mut man = ResourceManager::new();
|
let mut man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
||||||
assert_eq!(res.state(), ResourceState::Ready);
|
assert_eq!(res.state(), ResourceState::Ready);
|
||||||
let img = res.try_data_ref();
|
let img = res.data_ref();
|
||||||
img.unwrap();
|
img.unwrap();
|
||||||
|
|
||||||
println!("Path = {}", res.path());
|
println!("Path = {}", res.path());
|
||||||
|
@ -360,7 +385,7 @@ mod tests {
|
||||||
let mut man = ResourceManager::new();
|
let mut man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&image_path).unwrap();
|
let res = man.request::<Texture>(&image_path).unwrap();
|
||||||
assert_eq!(res.state(), ResourceState::Ready);
|
assert_eq!(res.state(), ResourceState::Ready);
|
||||||
let img = res.try_data_ref();
|
let img = res.data_ref();
|
||||||
img.unwrap();
|
img.unwrap();
|
||||||
|
|
||||||
let recv = man.watch(&image_path, false).unwrap();
|
let recv = man.watch(&image_path, false).unwrap();
|
||||||
|
|
|
@ -1,10 +1,73 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
//pub use gltf::texture::{MagFilter, MinFilter, WrappingMode};
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
|
|
||||||
|
use crate::ResHandle;
|
||||||
|
|
||||||
|
/// The filter mode of the sampler.
|
||||||
|
///
|
||||||
|
/// This is used for minification, magnification, and mipmap filters
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum FilterMode {
|
||||||
|
Nearest,
|
||||||
|
Linear,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The wrapping mode of the Texture coordinates
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum WrappingMode {
|
||||||
|
ClampToEdge,
|
||||||
|
MirroredRepeat,
|
||||||
|
Repeat,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The descriptor of the sampler for a Texture.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct TextureSampler {
|
||||||
|
pub mag_filter: Option<FilterMode>,
|
||||||
|
pub min_filter: Option<FilterMode>,
|
||||||
|
pub mipmap_filter: Option<FilterMode>,
|
||||||
|
pub wrap_u: WrappingMode,
|
||||||
|
pub wrap_v: WrappingMode,
|
||||||
|
pub wrap_w: WrappingMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Image(DynamicImage);
|
||||||
|
|
||||||
|
impl Deref for Image {
|
||||||
|
type Target = DynamicImage;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for Image {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DynamicImage> for Image {
|
||||||
|
fn from(value: DynamicImage) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Texture {
|
pub struct Texture {
|
||||||
pub image: DynamicImage,
|
pub image: ResHandle<Image>,
|
||||||
|
pub sampler: Option<TextureSampler>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Texture {
|
impl Texture {
|
||||||
|
/// Create a texture from an image.
|
||||||
|
pub fn from_image(image: ResHandle<Image>) -> Self {
|
||||||
|
Self {
|
||||||
|
image,
|
||||||
|
sampler: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
[package]
|
||||||
|
name = "lyra-scene"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
|
||||||
|
lyra-math = { path = "../lyra-math" }
|
|
@ -0,0 +1,316 @@
|
||||||
|
use std::{collections::VecDeque, ops::{Deref, DerefMut}};
|
||||||
|
|
||||||
|
use lyra_ecs::{query::Entities, relation::ChildOf, Bundle, Component, Entity, World};
|
||||||
|
|
||||||
|
// So we can use lyra_ecs::Component derive macro
|
||||||
|
pub(crate) mod lyra_engine {
|
||||||
|
pub(crate) mod ecs {
|
||||||
|
pub use lyra_ecs::*;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod node;
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
pub use node::*;
|
||||||
|
|
||||||
|
/// A flag spawned on all scene node entities
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SceneNodeFlag;
|
||||||
|
|
||||||
|
/// A flag spawned on only the scene root node
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct SceneNodeRoot;
|
||||||
|
|
||||||
|
enum MutCow<'a, T> {
|
||||||
|
Mut(&'a mut T),
|
||||||
|
Owned(T),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Deref for MutCow<'a, T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Mut(t) => t,
|
||||||
|
MutCow::Owned(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> DerefMut for MutCow<'a, T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
match self {
|
||||||
|
MutCow::Mut(t) => t,
|
||||||
|
MutCow::Owned(t) => t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A SceneGraph is a Graph of nodes that represents the hierarchy of a scene.
|
||||||
|
///
|
||||||
|
/// This SceneGraph is special in the sense that it is literally just an ECS world with methods
|
||||||
|
/// implemented for it that make it easier to use for a SceneGraph.
|
||||||
|
//#[derive(Default)]
|
||||||
|
pub struct SceneGraph<'a> {
|
||||||
|
pub(crate) world: MutCow<'a, World>,
|
||||||
|
root_node: SceneNode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SceneGraph<'a> {
|
||||||
|
/// Create a new SceneGraph with its own ECS World.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
let e = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot));
|
||||||
|
let root = SceneNode::new(None, e);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world: MutCow::Owned(world),
|
||||||
|
root_node: root
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a SceneGraph from an ECS World.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the `root_entity` was not created from a `SceneGraph` that was later
|
||||||
|
/// inserted into another world with [`SceneGraph::into_world`].
|
||||||
|
pub fn from_world(world: &'a mut World, root_entity: Entity) -> Option<Self> {
|
||||||
|
if world.view_one::<(&SceneNodeRoot, &Transform)>(root_entity).get().is_none() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(Self {
|
||||||
|
world: MutCow::Mut(world),
|
||||||
|
root_node: SceneNode::new(None, root_entity),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new SceneGraph inside an existing ECS World.
|
||||||
|
pub fn new_from_world(world: &'a mut World) -> Self {
|
||||||
|
let root_en = world.spawn((Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), SceneNodeRoot));
|
||||||
|
let root = SceneNode::new(None, root_en);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
world: MutCow::Mut(world),
|
||||||
|
root_node: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the SceneGraph, inserting its internal world into the provided world.
|
||||||
|
///
|
||||||
|
/// The `into` World will contain all nodes of the SceneGraph. You could iterate through the
|
||||||
|
/// SceneGraph manually if you'd like. The root entity has a [`SceneNodeRoot`] flag component,
|
||||||
|
/// and a Transform component. The children nodes have a [`SceneNodeFlag`] component and a
|
||||||
|
/// Transform component. The transforms of the children will be local, you must find its world
|
||||||
|
/// transform by traversing up the hierarchy of the scene manually with the `ChildOf` relation.
|
||||||
|
///
|
||||||
|
/// Returns: the Entity of the root node in the provided world.
|
||||||
|
pub fn into_world(mut self, into: &mut World) -> Entity {
|
||||||
|
// first insert the root entity into the World.
|
||||||
|
let v = self.world.view_one::<&Transform>(self.root_node.entity());
|
||||||
|
let pos = *v.get().unwrap();
|
||||||
|
let new_root = into.spawn((pos, SceneNodeRoot));
|
||||||
|
|
||||||
|
// now process the children of the root node.
|
||||||
|
Self::traverse_inserting_into(&mut self.world, self.root_node.entity(), new_root, into);
|
||||||
|
new_root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively traverse the `from` world, starting at `node_en`, inserting the
|
||||||
|
/// children entity into the world.
|
||||||
|
///
|
||||||
|
/// Parameters:
|
||||||
|
/// * `scene_world` - The world that the SceneGraph exists in
|
||||||
|
/// * `scene_en` - The entity of the parent entity inside of the `scene_world`.
|
||||||
|
/// * `parent_en` - The entity of the parent entity inside of the `into` world.
|
||||||
|
/// * `into` - The world to insert the SceneGraph world into.
|
||||||
|
fn traverse_inserting_into(scene_world: &mut World, scene_en: Entity, parent_en: Entity, into: &mut World) {
|
||||||
|
let v = scene_world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(scene_en);
|
||||||
|
|
||||||
|
// unfortunately, the borrow checker exists, and wasn't happy that `scene_world` was
|
||||||
|
// immutably borrowed with the view (v), and being mutably borrowed in the
|
||||||
|
// recursive call.
|
||||||
|
let mut child_entities = VecDeque::new();
|
||||||
|
|
||||||
|
for ((child_scene_en, pos), _rel) in v.iter() {
|
||||||
|
let into_en = into.spawn((*pos, SceneNodeFlag));
|
||||||
|
into.add_relation(into_en, ChildOf, parent_en);
|
||||||
|
|
||||||
|
child_entities.push_back((child_scene_en, into_en));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((child_scene_en, into_en)) = child_entities.pop_front() {
|
||||||
|
Self::traverse_inserting_into(scene_world, child_scene_en, into_en, into);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a node to the root node of the SceneGraph.
|
||||||
|
///
|
||||||
|
/// The spawned entity will have a `ChildOf` relation targeting the root node, the
|
||||||
|
/// `SceneNodeFlag` component is also added to the entity.
|
||||||
|
pub fn add_node<B: Bundle>(&mut self, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
let node = self.root_node.clone();
|
||||||
|
self.add_node_under(&node, local_transform, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a node under a parent node.
|
||||||
|
///
|
||||||
|
/// The spawned entity will have a `ChildOf` relation targeting the provided parent node,
|
||||||
|
/// the `SceneNodeFlag` component is also added to the entity.
|
||||||
|
pub fn add_node_under<B: Bundle>(&mut self, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
world_add_child_node(&mut self.world, parent, local_transform, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_empty_node_under(&mut self, parent: &SceneNode, local_transform: Transform) -> SceneNode {
|
||||||
|
let e = self.world.spawn((SceneNodeFlag, local_transform));
|
||||||
|
self.world.add_relation(e, ChildOf, parent.entity());
|
||||||
|
|
||||||
|
SceneNode::new(Some(parent.entity()), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses down the SceneGraph, calling `callback` with each SceneNode and its world transform.
|
||||||
|
///
|
||||||
|
/// The traversal does not include the root scene node.
|
||||||
|
pub fn traverse_down<F>(&self, mut callback: F)
|
||||||
|
where
|
||||||
|
F: FnMut(&SceneNode, Transform),
|
||||||
|
{
|
||||||
|
self.traverse_down_from(self.root_node.clone(), &mut callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursively Traverses down the SceneGraph from a starting node, calling `callback` with each
|
||||||
|
/// SceneNode and its world transform.
|
||||||
|
fn traverse_down_from<F>(&self, start: SceneNode, callback: &mut F)
|
||||||
|
where
|
||||||
|
F: FnMut(&SceneNode, Transform),
|
||||||
|
{
|
||||||
|
let v = self.world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(start.entity());
|
||||||
|
|
||||||
|
for ((e, _), _rel) in v.iter() {
|
||||||
|
let node = SceneNode::new(Some(start.entity()), e);
|
||||||
|
let world_pos = node.world_transform(self);
|
||||||
|
callback(&node, world_pos);
|
||||||
|
|
||||||
|
self.traverse_down_from(node, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root_node(&self) -> SceneNode {
|
||||||
|
self.root_node.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a node under a parent node.
|
||||||
|
///
|
||||||
|
/// The spawned entity will have a `ChildOf` relation targeting the provided parent node,
|
||||||
|
/// the `SceneNodeFlag` component is also added to the entity.
|
||||||
|
pub(crate) fn world_add_child_node<B: Bundle>(world: &mut World, parent: &SceneNode, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
let e = world.spawn(bundle);
|
||||||
|
world.insert(e, (SceneNodeFlag, local_transform));
|
||||||
|
world.add_relation(e, ChildOf, parent.entity());
|
||||||
|
|
||||||
|
SceneNode::new(Some(parent.entity()), e)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use lyra_ecs::{query::Entities, relation::ChildOf, Component, World};
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
|
||||||
|
use crate::{lyra_engine, SceneGraph, SceneNodeRoot};
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub struct FakeMesh;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_node_hierarchy() {
|
||||||
|
let mut scene = SceneGraph::new();
|
||||||
|
|
||||||
|
let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn double_node_hierarchy() {
|
||||||
|
let mut scene = SceneGraph::new();
|
||||||
|
|
||||||
|
let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
|
||||||
|
let b = a.add_node(&mut scene, Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh);
|
||||||
|
assert!(b.parent(&scene).unwrap() == a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn traverse_down() {
|
||||||
|
let v2s = vec![Vec3::new(10.0, 10.0, 10.0), Vec3::new(50.0, 50.0, 50.0)];
|
||||||
|
|
||||||
|
let mut scene = SceneGraph::new();
|
||||||
|
|
||||||
|
let a = scene.add_node(Transform::from_translation(v2s[0]), FakeMesh);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
let b = a.add_node(&mut scene, Transform::from_translation(v2s[1]), FakeMesh);
|
||||||
|
assert!(b.parent(&scene).unwrap() == a);
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
scene.traverse_down(|_e, pos| {
|
||||||
|
if idx == 0 {
|
||||||
|
assert_eq!(pos, Transform::from_translation(v2s[idx]));
|
||||||
|
} else if idx == 1 {
|
||||||
|
let t = v2s.iter().sum();
|
||||||
|
assert_eq!(pos, Transform::from_translation(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* #[test]
|
||||||
|
fn inserting_and_from_world() {
|
||||||
|
let v2s = vec![Vec3::new(10.0, 10.0, 10.0), Vec3::new(50.0, 50.0, 50.0)];
|
||||||
|
|
||||||
|
let mut scene = SceneGraph::new();
|
||||||
|
|
||||||
|
let a = scene.add_node(Transform::from_translation(v2s[0]), FakeMesh);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
let b = a.add_node(&mut scene, Transform::from_translation(v2s[1]), FakeMesh);
|
||||||
|
assert!(b.parent(&scene).unwrap() == a);
|
||||||
|
|
||||||
|
let mut other_world = World::new();
|
||||||
|
let root = scene.into_world(&mut other_world);
|
||||||
|
|
||||||
|
// check all of the entities inside of the World
|
||||||
|
let (root_pos, _) = other_world.view_one::<(&Transform, &SceneNodeRoot)>(root).get().unwrap();
|
||||||
|
assert_eq!(*root_pos, Transform::from_xyz(0.0, 0.0, 0.0));
|
||||||
|
|
||||||
|
let ((child_en, child_pos), _) = other_world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(root).iter().next().unwrap();
|
||||||
|
assert_eq!(*child_pos, Transform::from_translation(v2s[0]));
|
||||||
|
|
||||||
|
let ((_, childchild_pos), _) = other_world.view::<(Entities, &Transform)>()
|
||||||
|
.relates_to::<ChildOf>(child_en).iter().next().unwrap();
|
||||||
|
assert_eq!(*childchild_pos, Transform::from_translation(v2s[1]));
|
||||||
|
|
||||||
|
drop(root_pos);
|
||||||
|
drop(child_pos);
|
||||||
|
drop(childchild_pos);
|
||||||
|
|
||||||
|
// Now get the SceneGraph inside the World and use the nice utility tools to traverse it.
|
||||||
|
let scene = SceneGraph::from_world(&mut other_world, root)
|
||||||
|
.expect("Failed to get SceneGraph from World!");
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
scene.traverse_down(|_e, pos| {
|
||||||
|
if idx == 0 {
|
||||||
|
assert_eq!(pos, Transform::from_translation(v2s[idx]));
|
||||||
|
} else if idx == 1 {
|
||||||
|
let t = v2s.iter().sum();
|
||||||
|
assert_eq!(pos, Transform::from_translation(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
});
|
||||||
|
} */
|
||||||
|
}
|
|
@ -0,0 +1,101 @@
|
||||||
|
use lyra_ecs::{query::Entities, relation::{ChildOf, RelationOriginComponent}, Bundle, Entity};
|
||||||
|
use lyra_math::Transform;
|
||||||
|
|
||||||
|
use crate::SceneGraph;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq)]
|
||||||
|
pub struct SceneNode {
|
||||||
|
//world: WorldLock,
|
||||||
|
parent: Option<Entity>,
|
||||||
|
entity: Entity
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SceneNode {
|
||||||
|
pub fn new(parent: Option<Entity>, entity: Entity) -> Self {
|
||||||
|
Self {
|
||||||
|
parent,
|
||||||
|
entity,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this node has a parent, retrieves the parent node from the SceneGraph.
|
||||||
|
pub fn parent(&self, graph: &SceneGraph) -> Option<SceneNode> {
|
||||||
|
if let Some(parent) = self.parent {
|
||||||
|
let v = graph.world.view_one::<&RelationOriginComponent<ChildOf>>(parent);
|
||||||
|
|
||||||
|
let p_parent = if let Some(pp) = v.get() {
|
||||||
|
Some(pp.target())
|
||||||
|
} else { None };
|
||||||
|
|
||||||
|
Some(SceneNode::new(p_parent, parent))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parent_entity(&self) -> Option<Entity> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn children(&self, graph: &SceneGraph) -> Vec<SceneNode> {
|
||||||
|
let v = graph.world.view::<Entities>()
|
||||||
|
.relates_to::<ChildOf>(self.entity);
|
||||||
|
|
||||||
|
v.into_iter().map(|(e, _)| SceneNode::new(Some(self.entity), e)).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entity(&self) -> Entity {
|
||||||
|
self.entity
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a child node to this node.
|
||||||
|
///
|
||||||
|
/// The spawned entity backing the scene node will have a `ChildOf` relation targeting
|
||||||
|
/// the provided parent node, the `SceneNodeFlag` component is also added to the entity.
|
||||||
|
pub fn add_node<B: Bundle>(&self, graph: &mut SceneGraph, local_transform: Transform, bundle: B) -> SceneNode {
|
||||||
|
graph.add_node_under(self, local_transform, bundle)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_transform(&self, graph: &SceneGraph) -> Transform {
|
||||||
|
let v = graph.world.view_one::<&Transform>(self.entity);
|
||||||
|
|
||||||
|
let t = v.get().expect("Somehow the SceneNode is missing its Transform!");
|
||||||
|
t.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the world transform of the Node.
|
||||||
|
///
|
||||||
|
/// This traverses up the SceneGraph from this node.
|
||||||
|
pub fn world_transform(&self, graph: &SceneGraph) -> Transform {
|
||||||
|
match self.parent(graph) {
|
||||||
|
Some(parent) => {
|
||||||
|
let pt = parent.world_transform(graph);
|
||||||
|
pt + self.local_transform(graph)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
self.local_transform(graph)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use lyra_math::{Transform, Vec3};
|
||||||
|
|
||||||
|
use crate::{tests::FakeMesh, SceneGraph};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn node_world_transform() {
|
||||||
|
let mut scene = SceneGraph::new();
|
||||||
|
|
||||||
|
let a = scene.add_node(Transform::from_translation(Vec3::new(10.0, 10.0, 10.0)), FakeMesh);
|
||||||
|
assert!(a.parent(&scene).unwrap() == scene.root_node);
|
||||||
|
|
||||||
|
let b = a.add_node(&mut scene, Transform::from_translation(Vec3::new(50.0, 50.0, 50.0)), FakeMesh);
|
||||||
|
assert!(b.parent(&scene).unwrap() == a);
|
||||||
|
|
||||||
|
let wrld_tran = b.world_transform(&scene);
|
||||||
|
assert_eq!(wrld_tran, Transform::from_translation(Vec3::new(60.0, 60.0, 60.0)));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue