mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-29 09:08:58 +00:00
Fix wireframe bug and scene element tag
This commit is contained in:
+1
-1
@@ -32,7 +32,7 @@ web-sys = { version = "0.3.65", optional = true, features = [
|
|||||||
"HtmlElement",
|
"HtmlElement",
|
||||||
"Window",
|
"Window",
|
||||||
] }
|
] }
|
||||||
space_editor = {git = "https://github.com/rewin123/space_editor", rev = "0c3a8eaf1acea181407c43b7226799b2d08b4c89"}
|
space_editor = {git = "https://github.com/rewin123/space_editor", rev = "ce83f86dc902787ab16213f3209276a784654df6"}
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
wasm = [
|
wasm = [
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ impl Plugin for DebugPlugin {
|
|||||||
.disable::<EditorDefaultCameraPlugin>()
|
.disable::<EditorDefaultCameraPlugin>()
|
||||||
.disable::<BottomMenuPlugin>()
|
.disable::<BottomMenuPlugin>()
|
||||||
.disable::<GizmoToolPlugin>()
|
.disable::<GizmoToolPlugin>()
|
||||||
|
.set(space_editor::prelude::SelectedPlugin {
|
||||||
|
show_selected_wireframe: false
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
app.add_systems(Update, sync_editor_camera);
|
app.add_systems(Update, sync_editor_camera);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ impl Plugin for SelectionPlugin {
|
|||||||
.after(SceneSpawnerSystem),
|
.after(SceneSpawnerSystem),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.add_systems(Update, tag_scene_elements);
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
|
|||||||
+140
-164
@@ -13,7 +13,7 @@ use bevy::{
|
|||||||
scene::InstanceId,
|
scene::InstanceId,
|
||||||
};
|
};
|
||||||
|
|
||||||
use space_editor::prelude::{space_undo::OneFrameUndoIgnore, PrefabMarker};
|
use space_editor::{prelude::{space_undo::OneFrameUndoIgnore, PrefabMarker}, space_prefab::component::{SceneAutoChild, SceneAutoRoot}};
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
use wasm_bindgen::prelude::wasm_bindgen;
|
use wasm_bindgen::prelude::wasm_bindgen;
|
||||||
|
|
||||||
@@ -63,6 +63,8 @@ impl Plugin for ScenePlugin {
|
|||||||
.in_set(SceneSpawnerSystem),
|
.in_set(SceneSpawnerSystem),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.add_systems(Update, tag_scene_elements);
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
app.add_systems(
|
app.add_systems(
|
||||||
Update,
|
Update,
|
||||||
@@ -156,22 +158,8 @@ fn init_scene_loading(
|
|||||||
mut st_next: ResMut<NextState<SceneState>>,
|
mut st_next: ResMut<NextState<SceneState>>,
|
||||||
) {
|
) {
|
||||||
if let Some(id) = er.read().last() {
|
if let Some(id) = er.read().last() {
|
||||||
// let name = &**id;
|
|
||||||
// let path = format!("gltf/{name}.gltf");
|
|
||||||
|
|
||||||
// // Avoid trying to re-load this scene if it's already active
|
|
||||||
// if let Some(active_scene_path) =
|
|
||||||
// r_active_scene.and_then(|scene| scene.source.path().cloned())
|
|
||||||
// {
|
|
||||||
// if active_scene_path == AssetPath::parse(&path) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// cmd.insert_resource(PendingScene(assets.load(path)));
|
|
||||||
// st_next.set(SceneState::Loading);
|
|
||||||
|
|
||||||
info!("Loading scene: {}", id.0);
|
info!("Loading scene: {}", id.0);
|
||||||
|
st_next.set(SceneState::Active);
|
||||||
|
|
||||||
cmd.spawn((
|
cmd.spawn((
|
||||||
SpatialBundle::default(),
|
SpatialBundle::default(),
|
||||||
@@ -180,7 +168,7 @@ fn init_scene_loading(
|
|||||||
scene: "Scene0".to_string(),
|
scene: "Scene0".to_string(),
|
||||||
},
|
},
|
||||||
PrefabMarker,
|
PrefabMarker,
|
||||||
SceneElement
|
SceneRoot
|
||||||
));
|
));
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -256,9 +244,6 @@ fn tick_scene_spawning(
|
|||||||
|
|
||||||
fn tag_scene_elements(
|
fn tag_scene_elements(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
r_scene_spawner: Res<SceneSpawner>,
|
|
||||||
r_active_scene: Res<ActiveScene>,
|
|
||||||
ra_gltfs: Res<Assets<Gltf>>,
|
|
||||||
// A "glTF mesh" is actually a container for one or more "glTF primitives",
|
// A "glTF mesh" is actually a container for one or more "glTF primitives",
|
||||||
// which is basically a bundle of vertices paired with the material used to
|
// which is basically a bundle of vertices paired with the material used to
|
||||||
// shade them. It's those "sub-meshes" that carry the actual mesh handles,
|
// shade them. It's those "sub-meshes" that carry the actual mesh handles,
|
||||||
@@ -266,26 +251,17 @@ fn tag_scene_elements(
|
|||||||
// because they often consist of stuff like a single eyeball, or just the
|
// because they often consist of stuff like a single eyeball, or just the
|
||||||
// leafy parts of a plant. Here we only want to tag the elements that a user
|
// leafy parts of a plant. Here we only want to tag the elements that a user
|
||||||
// might want to individually address when arranging or animating a scene.
|
// might want to individually address when arranging or animating a scene.
|
||||||
q_named_non_primitives: Query<&Name, Without<Handle<Mesh>>>,
|
q_named_non_primitives: Query<Entity, (With<SceneAutoChild>, With<Name>, Without<Handle<Mesh>>, Without<SceneElement>)>,
|
||||||
) {
|
) {
|
||||||
let gltf = ra_gltfs.get(&r_active_scene.source).unwrap();
|
for loaded_part in q_named_non_primitives.iter() {
|
||||||
|
cmd.entity(loaded_part).insert(SceneElement);
|
||||||
for ent in r_scene_spawner.iter_instance_entities(**r_active_scene) {
|
|
||||||
cmd.entity(ent).insert(PrefabMarker);
|
|
||||||
let Ok(name) = q_named_non_primitives.get(ent) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if gltf.named_nodes.contains_key(&**name) {
|
|
||||||
cmd.entity(ent).insert(SceneElement);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_main_scene_light(
|
fn spawn_main_scene_light(
|
||||||
mut cmd: Commands,
|
mut cmd: Commands,
|
||||||
r_scene_spawner: Res<SceneSpawner>,
|
r_scene_spawner: Res<SceneSpawner>,
|
||||||
r_active_scene: Res<ActiveScene>,
|
// r_active_scene: Res<ActiveScene>,
|
||||||
q_aabbs: Query<(&Aabb, &GlobalTransform)>,
|
q_aabbs: Query<(&Aabb, &GlobalTransform)>,
|
||||||
q_scene_root: Query<Entity, With<SceneRoot>>,
|
q_scene_root: Query<Entity, With<SceneRoot>>,
|
||||||
) {
|
) {
|
||||||
@@ -293,155 +269,155 @@ fn spawn_main_scene_light(
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(aabb) = Aabb::combine(
|
// let Some(aabb) = Aabb::combine(
|
||||||
r_scene_spawner
|
// r_scene_spawner
|
||||||
.iter_instance_entities(**r_active_scene)
|
// .iter_instance_entities(**r_active_scene)
|
||||||
.filter_map(|ent| q_aabbs.get(ent).ok()),
|
// .filter_map(|ent| q_aabbs.get(ent).ok()),
|
||||||
) else {
|
// ) else {
|
||||||
return;
|
// return;
|
||||||
};
|
// };
|
||||||
|
|
||||||
let light_offset_base = vec3(0.5, 1., 1.).normalize();
|
// let light_offset_base = vec3(0.5, 1., 1.).normalize();
|
||||||
let size = f32::max(
|
// let size = f32::max(
|
||||||
f32::max(aabb.half_extents.x, aabb.half_extents.y),
|
// f32::max(aabb.half_extents.x, aabb.half_extents.y),
|
||||||
aabb.half_extents.z,
|
// aabb.half_extents.z,
|
||||||
) * 2.;
|
// ) * 2.;
|
||||||
let light_pos = Vec3::from(aabb.center) + light_offset_base * size * 2.;
|
// let light_pos = Vec3::from(aabb.center) + light_offset_base * size * 2.;
|
||||||
|
|
||||||
cmd.spawn(DebugPoint {
|
// cmd.spawn(DebugPoint {
|
||||||
point: light_pos,
|
// point: light_pos,
|
||||||
color: Color::WHITE,
|
// color: Color::WHITE,
|
||||||
});
|
// });
|
||||||
cmd.entity(scene_root).with_children(|cmd| {
|
// cmd.entity(scene_root).with_children(|cmd| {
|
||||||
cmd.spawn((
|
// cmd.spawn((
|
||||||
Name::new("Light"),
|
// Name::new("Light"),
|
||||||
SceneElement,
|
// SceneElement,
|
||||||
SpotLightBundle {
|
// SpotLightBundle {
|
||||||
spot_light: SpotLight {
|
// spot_light: SpotLight {
|
||||||
color: Color::WHITE,
|
// color: Color::WHITE,
|
||||||
// TODO: This doesn't scale super well to very large objects
|
// // TODO: This doesn't scale super well to very large objects
|
||||||
intensity: 200. * size,
|
// intensity: 200. * size,
|
||||||
radius: 0.1,
|
// radius: 0.1,
|
||||||
shadows_enabled: true,
|
// shadows_enabled: true,
|
||||||
shadow_normal_bias: 0.2 * size,
|
// shadow_normal_bias: 0.2 * size,
|
||||||
outer_angle: f32::to_radians(150.),
|
// outer_angle: f32::to_radians(150.),
|
||||||
inner_angle: f32::to_radians(45.),
|
// inner_angle: f32::to_radians(45.),
|
||||||
..default()
|
// ..default()
|
||||||
},
|
// },
|
||||||
transform: Transform::from_translation(light_pos)
|
// transform: Transform::from_translation(light_pos)
|
||||||
.looking_at(aabb.center.into(), Vec3::Y),
|
// .looking_at(aabb.center.into(), Vec3::Y),
|
||||||
..default()
|
// ..default()
|
||||||
},
|
// },
|
||||||
));
|
// ));
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_scene_in_view(
|
fn frame_scene_in_view(
|
||||||
mut cmd: Commands,
|
// mut cmd: Commands,
|
||||||
r_scene_spawner: Res<SceneSpawner>,
|
// r_scene_spawner: Res<SceneSpawner>,
|
||||||
r_active_scene: Res<ActiveScene>,
|
// r_active_scene: Res<ActiveScene>,
|
||||||
q_aabbs: Query<(&Aabb, &GlobalTransform)>,
|
// q_aabbs: Query<(&Aabb, &GlobalTransform)>,
|
||||||
mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>,
|
// mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>,
|
||||||
) {
|
) {
|
||||||
let Ok((mut cam, mut cam_xform, cam_proj)) = q_camera.get_single_mut() else {
|
// let Ok((mut cam, mut cam_xform, cam_proj)) = q_camera.get_single_mut() else {
|
||||||
return;
|
// return;
|
||||||
};
|
// };
|
||||||
|
|
||||||
let Some(aabb) = Aabb::combine(
|
// let Some(aabb) = Aabb::combine(
|
||||||
r_scene_spawner
|
// r_scene_spawner
|
||||||
.iter_instance_entities(**r_active_scene)
|
// .iter_instance_entities(**r_active_scene)
|
||||||
.filter_map(|ent| q_aabbs.get(ent).ok()),
|
// .filter_map(|ent| q_aabbs.get(ent).ok()),
|
||||||
) else {
|
// ) else {
|
||||||
return;
|
// return;
|
||||||
};
|
// };
|
||||||
|
|
||||||
let world2cam = cam_xform.compute_matrix().inverse();
|
// let world2cam = cam_xform.compute_matrix().inverse();
|
||||||
let plane_origin = Vec3::from(aabb.center);
|
// let plane_origin = Vec3::from(aabb.center);
|
||||||
let plane_normal = cam_xform.forward();
|
// let plane_normal = cam_xform.forward();
|
||||||
|
|
||||||
let Some((_depth, bounding_rect)) = aabb
|
// let Some((_depth, bounding_rect)) = aabb
|
||||||
.extrema()
|
// .extrema()
|
||||||
.iter()
|
// .iter()
|
||||||
.copied()
|
// .copied()
|
||||||
.map(|mut p| {
|
// .map(|mut p| {
|
||||||
cmd.spawn(DebugPoint {
|
// cmd.spawn(DebugPoint {
|
||||||
point: p,
|
// point: p,
|
||||||
color: Color::CYAN,
|
// color: Color::CYAN,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Inflate the combined AABB to give the model some breathing room
|
// // Inflate the combined AABB to give the model some breathing room
|
||||||
p *= 1.5;
|
// p *= 1.5;
|
||||||
|
|
||||||
// Project each corner onto the camera plane
|
// // Project each corner onto the camera plane
|
||||||
let v = p - plane_origin;
|
// let v = p - plane_origin;
|
||||||
let proj = p - v.project_onto_normalized(plane_normal);
|
// let proj = p - v.project_onto_normalized(plane_normal);
|
||||||
|
|
||||||
cmd.spawn(DebugPoint {
|
// cmd.spawn(DebugPoint {
|
||||||
point: proj,
|
// point: proj,
|
||||||
color: Color::FUCHSIA,
|
// color: Color::FUCHSIA,
|
||||||
});
|
// });
|
||||||
|
|
||||||
// Transform into the camera's local space
|
// // Transform into the camera's local space
|
||||||
world2cam.transform_point(proj)
|
// world2cam.transform_point(proj)
|
||||||
})
|
// })
|
||||||
// Fold the camera-space plane-projected points into a bounding rect and view depth
|
// // Fold the camera-space plane-projected points into a bounding rect and view depth
|
||||||
.fold(None, |accum: Option<(f32, Rect)>, p: Vec3| match accum {
|
// .fold(None, |accum: Option<(f32, Rect)>, p: Vec3| match accum {
|
||||||
Some((depth, rect)) => Some((depth, rect.union_point(vec2(p.x, p.y)))),
|
// Some((depth, rect)) => Some((depth, rect.union_point(vec2(p.x, p.y)))),
|
||||||
None => {
|
// None => {
|
||||||
let depth = p.z.abs();
|
// let depth = p.z.abs();
|
||||||
let p = vec2(p.x, p.y);
|
// let p = vec2(p.x, p.y);
|
||||||
Some((depth, Rect::from_corners(p, p)))
|
// Some((depth, Rect::from_corners(p, p)))
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
else {
|
// else {
|
||||||
return;
|
// return;
|
||||||
};
|
// };
|
||||||
|
|
||||||
let pproj = match cam_proj {
|
// let pproj = match cam_proj {
|
||||||
Projection::Perspective(p) => p,
|
// Projection::Perspective(p) => p,
|
||||||
_ => panic!("Expected a perspective projection"),
|
// _ => panic!("Expected a perspective projection"),
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Formula for the target camera distance
|
// // Formula for the target camera distance
|
||||||
// (c/o https://stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene):
|
// // (c/o https://stackoverflow.com/questions/2866350/move-camera-to-fit-3d-scene):
|
||||||
//
|
// //
|
||||||
// d = (s/2) / tan(a/2)
|
// // d = (s/2) / tan(a/2)
|
||||||
// where:
|
// // where:
|
||||||
// d = distance
|
// // d = distance
|
||||||
// s = size of the largest camera-aligned axis in world coords
|
// // s = size of the largest camera-aligned axis in world coords
|
||||||
// a = field-of-view angle for the same axis
|
// // a = field-of-view angle for the same axis
|
||||||
let Vec2 {
|
// let Vec2 {
|
||||||
x: width,
|
// x: width,
|
||||||
y: height,
|
// y: height,
|
||||||
} = bounding_rect.size();
|
// } = bounding_rect.size();
|
||||||
|
|
||||||
let s = f32::max(width, height);
|
// let s = f32::max(width, height);
|
||||||
let a = if s == height {
|
// let a = if s == height {
|
||||||
pproj.fov
|
// pproj.fov
|
||||||
} else {
|
// } else {
|
||||||
f32::atan(pproj.aspect_ratio * f32::tan(pproj.fov / 2.))
|
// f32::atan(pproj.aspect_ratio * f32::tan(pproj.fov / 2.))
|
||||||
};
|
// };
|
||||||
|
|
||||||
// TODO:
|
// // TODO:
|
||||||
// According to "I eat babies" at the Ars Technica forums
|
// // According to "I eat babies" at the Ars Technica forums
|
||||||
// (https://arstechnica.com/civis/threads/if-you-have-aspect-ratio-and-vertical-fov-can-you-calc-horizontal-fov.37447/):
|
// // (https://arstechnica.com/civis/threads/if-you-have-aspect-ratio-and-vertical-fov-can-you-calc-horizontal-fov.37447/):
|
||||||
//
|
// //
|
||||||
// tan(fov_v / 2) = y / z
|
// // tan(fov_v / 2) = y / z
|
||||||
// tan(fov_h / 2) = x / z
|
// // tan(fov_h / 2) = x / z
|
||||||
//
|
// //
|
||||||
// Theoretically, this would let us skip some costly tan/atan
|
// // Theoretically, this would let us skip some costly tan/atan
|
||||||
// computations, but I must be missing something, because just
|
// // computations, but I must be missing something, because just
|
||||||
// substituting (s / depth) for the divisor below would make the
|
// // substituting (s / depth) for the divisor below would make the
|
||||||
// whole equation independent of the FOV, which is obviously
|
// // whole equation independent of the FOV, which is obviously
|
||||||
// wrong.
|
// // wrong.
|
||||||
//
|
// //
|
||||||
// Unfortunately I'm embarrassingly bad at algebra, but there
|
// // Unfortunately I'm embarrassingly bad at algebra, but there
|
||||||
// must be some room for simplification here.
|
// // must be some room for simplification here.
|
||||||
let radius = (s / 2.) / f32::tan(a / 2.);
|
// let radius = (s / 2.) / f32::tan(a / 2.);
|
||||||
|
|
||||||
cam.focus = aabb.center.into();
|
// cam.focus = aabb.center.into();
|
||||||
cam.radius = radius;
|
// cam.radius = radius;
|
||||||
cam_xform.translation = cam.focus + cam_xform.back() * radius;
|
// cam_xform.translation = cam.focus + cam_xform.back() * radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "wasm")]
|
#[cfg(feature = "wasm")]
|
||||||
|
|||||||
Reference in New Issue
Block a user