Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

11 changes: 8 additions & 3 deletions studio-web/src/lib/studio.element.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { inject } from "@storyteller/framework";
import { inject, on } from "@storyteller/framework";
import * as studio from "@storyteller/studio";
import {
type InitOptions,
Expand Down Expand Up @@ -32,14 +32,18 @@ export class StudioElement extends LitElement {
override connectedCallback(): void {
super.connectedCallback();

this.#resizeObserver = new ResizeObserver(this.#onResize.bind(this));
this.#resizeObserver = new ResizeObserver(this._onResize.bind(this));
this.#resizeObserver.observe(this);

if (!this._canvasProvider)
throw new Error(`No provider found for token: ${CANVAS_PROVIDER}`);

if ((this.#canvas = this._canvasProvider.takeCanvas()))
this.appendChild(this.#canvas);

requestAnimationFrame(() => {
this._onResize();
});
}

override disconnectedCallback(): void {
Expand All @@ -61,7 +65,8 @@ export class StudioElement extends LitElement {
super.updated(changes);
}

#onResize(): void {
@on("window:scene-state")
_onResize(): void {
const { width, height } = this.getBoundingClientRect();

studio.resize(new ViewportSize(width, height));
Expand Down
3 changes: 1 addition & 2 deletions studio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ path = "src/main-headless.rs"
[dependencies]
bevy-inspector-egui = "0.23.0"
bevy_mod_raycast = "0.17.0"
bevy_rapier3d = "0.25.0"
bevy_rapier3d = { version = "0.25.0", features = ["simd-stable", "debug-render-3d"] }
bevy_mod_outline = "0.7.0"
bevy_web_asset = "0.8.0"
bitflags = "2.4.0"
Expand Down Expand Up @@ -95,5 +95,4 @@ features = [
"backend_raycast",
"backend_sprite",
"debug",
"selection",
]
162 changes: 160 additions & 2 deletions studio/src/anim/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ use bevy::{
math::Affine3A,
prelude::*,
render::{mesh::skinning::SkinnedMesh, view::NoFrustumCulling},
utils::HashMap,
};
use space_editor::space_prefab::editor_registry::EditorRegistryExt;
use bevy_rapier3d::geometry::Collider;
use space_editor::{prelude::PrefabMarker, space_prefab::editor_registry::EditorRegistryExt};

use crate::{hierarchy, scene::SceneElement};
use crate::{
hierarchy,
scene::{SceneElement, SceneState},
};
pub use retargeting::*;

mod retargeting;
Expand Down Expand Up @@ -34,6 +39,69 @@ impl Plugin for AnimPlugin {
retargeting::RetargetingPlugin,
timeline_animation::TimelinePlugin,
));

{
// TODO
app.register_type::<TempCharacterCollidersProcessed>();
app.editor_registry::<TempCharacterCollidersProcessed>();

use ColliderShape::*;

app.insert_resource(TempCharacterCollidersConfig(
[
(
"DEF-spine",
vec![TempCharacterColliderConfig {
shape: Capsule {
radius: 0.108191,
length: 0.135549,
alignment: BoneAlignment::X,
},
transform: Transform::from_xyz(0., 0.117, -0.001),
}],
),
(
"DEF-spine.001",
vec![TempCharacterColliderConfig {
shape: Capsule {
radius: 0.091,
length: 0.095,
alignment: BoneAlignment::X,
},
transform: Transform::from_xyz(0., 0.068, 0.),
}],
),
(
"DEF-spine.002",
vec![
TempCharacterColliderConfig {
shape: Capsule {
radius: 0.082,
length: 0.062,
alignment: BoneAlignment::X,
},
transform: Transform::from_xyz(0., 0.006, 0.),
},
TempCharacterColliderConfig {
shape: Capsule {
radius: 0.0823,
length: 0.03661,
alignment: BoneAlignment::X,
},
transform: Transform::from_xyz(0., 0.088, 0.010),
},
],
),
]
.into_iter()
.collect(),
));

app.add_systems(
Update,
temp_attach_colliders_to_character.run_if(in_state(SceneState::Active)),
);
}
}
}

Expand Down Expand Up @@ -144,3 +212,93 @@ fn process_skinned_meshes(
}
}
}

#[derive(Resource, Clone, Default, Deref)]
struct TempCharacterCollidersConfig(HashMap<&'static str, Vec<TempCharacterColliderConfig>>);

#[derive(Debug, Clone, Copy)]
enum BoneAlignment {
X,
Y,
Z,
}

#[derive(Debug, Clone, Copy)]
enum ColliderShape {
Capsule {
radius: f32,
length: f32,
alignment: BoneAlignment,
},
Box {
x: f32,
y: f32,
z: f32,
},
}

#[derive(Debug, Clone, Copy)]
struct TempCharacterColliderConfig {
shape: ColliderShape,
transform: Transform,
}

#[derive(Component, Reflect, Default, Clone, Copy)]
#[reflect(Component)]
struct TempCharacterCollidersProcessed;

fn temp_attach_colliders_to_character(
mut cmd: Commands,
r_colliders_map: Res<TempCharacterCollidersConfig>,
q_named_prefabs: Query<
(Entity, &Name),
(With<PrefabMarker>, Without<TempCharacterCollidersProcessed>),
>,
q_named_joints: Query<(Entity, &Name), With<SkeletalJoint>>,
q_children: Query<&Children>,
) {
for target in q_named_prefabs.iter().filter_map(|(ent, name)| {
if name.as_str() == "base-human-female.glb" {
Some(ent)
} else {
None
}
}) {
for (joint, name) in q_children
.iter_descendants(target)
.filter_map(|ent| q_named_joints.get(ent).ok())
{
if let Some(colliders) = r_colliders_map.get(&name.as_str()) {
info!("Processing colliders for bone: '{name}'");

for TempCharacterColliderConfig { shape, transform } in colliders.iter().copied() {
cmd.entity(joint).with_children(|cmd| {
cmd.spawn((
TransformBundle::from_transform(transform),
match shape {
ColliderShape::Capsule {
radius,
length,
alignment,
} => match alignment {
BoneAlignment::X => Collider::capsule_x(length / 2., radius),
BoneAlignment::Y => Collider::capsule_x(length / 2., radius),
BoneAlignment::Z => Collider::capsule_x(length / 2., radius),
},
ColliderShape::Box { x, y, z } => {
Collider::cuboid(x / 2., y / 2., z / 2.)
}
},
// SceneElement::Collider,
bevy_mod_picking::PickableBundle::default(),
));
});
}
} else {
warn!("No collider config found for bone: '{name}'");
}
}

cmd.entity(target).insert(TempCharacterCollidersProcessed);
}
}
62 changes: 30 additions & 32 deletions studio/src/camera_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use bevy::{
};

use crate::{
input::{self, Action, CameraMotion, InputAction, Lifecycle},
input::{self, CameraAction, CameraMotion, Lifecycle, SelectionAction},
interaction::Selection,
math::{
Easing, InterpPosition, InterpVelocity, Interpolation, InterpolationSystem, NearlyEq,
Expand Down Expand Up @@ -96,41 +96,39 @@ impl CameraControllerBundle {

fn camera_control_kinematic(
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
mut input: EventReader<InputAction>,
mut er_camera: EventReader<CameraAction>,
mut q_camera: Query<(&mut CameraController, &mut Transform, &Projection)>,
) {
if input.is_empty() {
if er_camera.is_empty() {
return;
}

let mut any_ongoing = false;
let mut any_started = false;
let mut any_stopped = false;

for action in input.read().filter(|action| {
!matches!(
action,
InputAction(_, Action::Camera(CameraMotion::Translate(_)))
)
}) {
if let InputAction(lifecycle, Action::Camera(motion)) = action {
let (mut controller, mut xform, proj) = q_camera.single_mut();
let window = q_window.single();

match lifecycle {
Lifecycle::Start => any_started = true,
Lifecycle::Ongoing => any_ongoing = true,
Lifecycle::Stop => any_stopped = true,
}
for action in er_camera
.read()
.filter(|action| !matches!(action, CameraAction(_, CameraMotion::Translate(_))))
{
let CameraAction(lifecycle, motion) = action;
let (mut controller, mut xform, proj) = q_camera.single_mut();
let window = q_window.single();

match lifecycle {
Lifecycle::Start => any_started = true,
Lifecycle::Ongoing => any_ongoing = true,
Lifecycle::Stop => any_stopped = true,
_ => {}
}

use CameraMotion::*;
match motion {
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
Zoom(delta) => do_zoom(window, *delta, &mut xform, &mut controller),
Orbit(delta) => do_orbit(window, *delta, *lifecycle, &mut xform, &mut controller),
Rotate(delta) => do_rotate(window, *delta, &mut xform, &mut controller),
_ => {}
}
use CameraMotion::*;
match motion {
Pan(delta) => do_pan(proj, window, *delta, &mut xform, &mut controller),
Zoom(delta) => do_zoom(window, *delta, &mut xform, &mut controller),
Orbit(delta) => do_orbit(window, *delta, *lifecycle, &mut xform, &mut controller),
Rotate(delta) => do_rotate(window, *delta, &mut xform, &mut controller),
_ => {}
}
}

Expand All @@ -143,12 +141,12 @@ fn camera_control_kinematic(

fn process_physical_inputs(
mut cmd: Commands,
mut input: EventReader<InputAction>,
mut er_camera: EventReader<CameraAction>,
q_camera: Query<(Entity, &Velocity), With<CameraController>>,
mut q_interp: Query<&mut InterpVelocity>,
) {
for action in input.read() {
let &InputAction(_, Action::Camera(CameraMotion::Translate(delta))) = action else {
for action in er_camera.read() {
let &CameraAction(_, CameraMotion::Translate(delta)) = action else {
continue;
};

Expand Down Expand Up @@ -181,7 +179,7 @@ fn process_physical_inputs(
fn process_focus_inputs(
mut cmd: Commands,
r_selection: Option<Res<Selection>>,
mut input: EventReader<InputAction>,
mut er_selection: EventReader<SelectionAction>,
mut q_camera: Query<(Entity, &CameraController, &mut Velocity)>,
q_interp_vel: Query<&InterpVelocity>,
mut q_interp_pos: Query<&mut InterpPosition>,
Expand All @@ -191,10 +189,10 @@ fn process_focus_inputs(
return;
};

if input.read().any(|action| {
if er_selection.read().any(|action| {
matches!(
action,
InputAction(Lifecycle::Stop, Action::Selection(input::Selection::Focus))
SelectionAction(Lifecycle::Stop, input::Selection::Focus)
)
}) {
let Ok(target) = q_global_xforms
Expand Down
16 changes: 9 additions & 7 deletions studio/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::StudioMode;
use crate::{
anim::SkeletalJoint,
camera_controller::{CameraController, CameraMotionSystem},
input::{Action, InputAction, Lifecycle},
input::{self, EditorAction, Lifecycle},
interaction::Selectable,
is_editor,
scene::SceneElement,
Expand Down Expand Up @@ -109,11 +109,13 @@ impl Plugin for DebugPlugin {
}
}

fn toggle_debug_mode(mut input: EventReader<InputAction>, mut r_debug: ResMut<DebugMode>) {
if input
.read()
.any(|action| matches!(action, InputAction(Lifecycle::Stop, Action::ToggleDebug)))
{
fn toggle_debug_mode(mut er_editor: EventReader<EditorAction>, mut r_debug: ResMut<DebugMode>) {
if er_editor.read().any(|action| {
matches!(
action,
EditorAction(Lifecycle::Stop, input::Editor::ToggleDebug)
)
}) {
**r_debug = !**r_debug;
}
}
Expand Down Expand Up @@ -252,7 +254,7 @@ mod wasm {
debugModeActive: boolean
}
}

declare global {
export interface GlobalEventHandlersEventMap {
"debug-mode-toggled": DebugModeToggled
Expand Down
Loading