mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
WIP refactor
This commit is contained in:
@@ -12,7 +12,7 @@ import styles from "./app.element.scss?inline";
|
||||
import { match } from "@storyteller/utility";
|
||||
|
||||
// FIXME: Figure out how to generate the correct code from `wasm_bindgen`
|
||||
enum InteractionTransform {
|
||||
enum TransformType {
|
||||
TRANSLATE = 0b0001,
|
||||
ROTATE = 0b0010,
|
||||
}
|
||||
@@ -92,9 +92,9 @@ export class AppElement extends LitElement {
|
||||
});
|
||||
|
||||
let transform = 0;
|
||||
if (this.translateGizmoEnabled) transform |= InteractionTransform.TRANSLATE;
|
||||
if (this.rotateGizmoEnabled) transform |= InteractionTransform.ROTATE;
|
||||
// TODO: Scale
|
||||
if (this.translateGizmoEnabled) transform |= TransformType.TRANSLATE;
|
||||
if (this.rotateGizmoEnabled) transform |= TransformType.ROTATE;
|
||||
// TODO: Scale?
|
||||
|
||||
studio.setInteractionTransform(transform);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
use std::mem;
|
||||
|
||||
use bevy::{prelude::Color, utils::default};
|
||||
|
||||
use crate::{design_system, interaction::InteractionMode};
|
||||
|
||||
use super::{
|
||||
model::{Axes, Control, Gizmo, GizmoColor, GizmoElement, Shape},
|
||||
TransformType,
|
||||
};
|
||||
|
||||
impl Gizmo {
|
||||
pub(super) fn update_composition(&mut self, mode: &InteractionMode) {
|
||||
let indices = Indices::collect_from(self);
|
||||
self.remove_extras(mode.transform, &indices);
|
||||
self.add_missing(mode.transform, &indices);
|
||||
}
|
||||
|
||||
fn remove_extras(&mut self, ttype: TransformType, indices: &Indices) {
|
||||
let mut to_remove =
|
||||
Vec::<i32>::with_capacity(mem::size_of::<Indices>() / mem::size_of::<i32>());
|
||||
|
||||
match ttype {
|
||||
TransformType::TRANSLATE_ROTATE => {
|
||||
to_remove.extend_from_slice(&[
|
||||
indices.xlate_x,
|
||||
indices.xlate_y,
|
||||
indices.xlate_z,
|
||||
indices.xlate_xy,
|
||||
indices.xlate_xz,
|
||||
indices.xlate_yz,
|
||||
]);
|
||||
}
|
||||
TransformType::TRANSLATE => {
|
||||
to_remove.extend_from_slice(&[
|
||||
indices.rotate_screen,
|
||||
indices.rotate_x,
|
||||
indices.rotate_y,
|
||||
indices.rotate_z,
|
||||
]);
|
||||
}
|
||||
TransformType::ROTATE => {
|
||||
to_remove.extend_from_slice(&[
|
||||
indices.xlate_screen,
|
||||
indices.xlate_x,
|
||||
indices.xlate_x_cone,
|
||||
indices.xlate_y,
|
||||
indices.xlate_y_cone,
|
||||
indices.xlate_z,
|
||||
indices.xlate_z_cone,
|
||||
indices.xlate_xy,
|
||||
indices.xlate_xz,
|
||||
indices.xlate_yz,
|
||||
]);
|
||||
}
|
||||
_ => {
|
||||
self.0.clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
to_remove.sort();
|
||||
|
||||
for idx in to_remove.into_iter().rev().filter_map(|idx| {
|
||||
if idx == -1 {
|
||||
None
|
||||
} else {
|
||||
Some(idx as usize)
|
||||
}
|
||||
}) {
|
||||
self.0.swap_remove(idx);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_missing(&mut self, ttype: TransformType, indices: &Indices) {
|
||||
if ttype.contains(TransformType::ROTATE) {
|
||||
if indices.rotate_screen == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Ring(default()),
|
||||
color: Color::WHITE.into(),
|
||||
control: Control::Rotation(Axes::SCREEN_Z),
|
||||
});
|
||||
}
|
||||
if indices.rotate_x == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Ring(default()),
|
||||
color: design_system::Color::RED.into(),
|
||||
control: Control::Rotation(Axes::X),
|
||||
});
|
||||
}
|
||||
if indices.rotate_y == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Ring(default()),
|
||||
color: design_system::Color::GREEN.into(),
|
||||
control: Control::Rotation(Axes::Y),
|
||||
});
|
||||
}
|
||||
if indices.rotate_z == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Ring(default()),
|
||||
color: design_system::Color::BLUE.into(),
|
||||
control: Control::Rotation(Axes::Z),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ttype.contains(TransformType::TRANSLATE) {
|
||||
if indices.xlate_screen == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Ring(default()),
|
||||
color: Color::WHITE.into(),
|
||||
control: Control::Translation(Axes::SCREEN_XY),
|
||||
});
|
||||
}
|
||||
if indices.xlate_x_cone == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Cone(default()),
|
||||
color: design_system::Color::RED.into(),
|
||||
control: Control::Translation(Axes::X),
|
||||
});
|
||||
}
|
||||
if indices.xlate_y_cone == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Cone(default()),
|
||||
color: design_system::Color::GREEN.into(),
|
||||
control: Control::Translation(Axes::Y),
|
||||
});
|
||||
}
|
||||
if indices.xlate_z_cone == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Cone(default()),
|
||||
color: design_system::Color::BLUE.into(),
|
||||
control: Control::Translation(Axes::Z),
|
||||
});
|
||||
}
|
||||
|
||||
if !ttype.contains(TransformType::ROTATE) {
|
||||
if indices.xlate_x == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: design_system::Color::RED.into(),
|
||||
control: Control::Translation(Axes::X),
|
||||
});
|
||||
}
|
||||
if indices.xlate_y == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: design_system::Color::GREEN.into(),
|
||||
control: Control::Translation(Axes::Y),
|
||||
});
|
||||
}
|
||||
if indices.xlate_z == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: design_system::Color::BLUE.into(),
|
||||
control: Control::Translation(Axes::Z),
|
||||
});
|
||||
}
|
||||
if indices.xlate_xy == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: GizmoColor::Gradient(
|
||||
design_system::Color::RED,
|
||||
design_system::Color::GREEN,
|
||||
),
|
||||
control: Control::Translation(Axes::XY),
|
||||
});
|
||||
}
|
||||
if indices.xlate_xz == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: GizmoColor::Gradient(
|
||||
design_system::Color::RED,
|
||||
design_system::Color::BLUE,
|
||||
),
|
||||
control: Control::Translation(Axes::XZ),
|
||||
});
|
||||
}
|
||||
if indices.xlate_yz == -1 {
|
||||
self.0.push(GizmoElement {
|
||||
shape: Shape::Line(default()),
|
||||
color: GizmoColor::Gradient(
|
||||
design_system::Color::GREEN,
|
||||
design_system::Color::BLUE,
|
||||
),
|
||||
control: Control::Translation(Axes::YZ),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Indices {
|
||||
xlate_screen: i32,
|
||||
xlate_x: i32,
|
||||
xlate_x_cone: i32,
|
||||
xlate_y: i32,
|
||||
xlate_y_cone: i32,
|
||||
xlate_z: i32,
|
||||
xlate_z_cone: i32,
|
||||
xlate_xy: i32,
|
||||
xlate_xz: i32,
|
||||
xlate_yz: i32,
|
||||
rotate_screen: i32,
|
||||
rotate_x: i32,
|
||||
rotate_y: i32,
|
||||
rotate_z: i32,
|
||||
}
|
||||
|
||||
impl Default for Indices {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
xlate_screen: -1,
|
||||
xlate_x: -1,
|
||||
xlate_x_cone: -1,
|
||||
xlate_y: -1,
|
||||
xlate_y_cone: -1,
|
||||
xlate_z: -1,
|
||||
xlate_z_cone: -1,
|
||||
xlate_xy: -1,
|
||||
xlate_xz: -1,
|
||||
xlate_yz: -1,
|
||||
rotate_screen: -1,
|
||||
rotate_x: -1,
|
||||
rotate_y: -1,
|
||||
rotate_z: -1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Indices {
|
||||
fn collect_from(gizmo: &Gizmo) -> Self {
|
||||
let mut result = Indices::default();
|
||||
|
||||
for (idx, GizmoElement { control, shape, .. }) in gizmo.0.iter().enumerate() {
|
||||
match control {
|
||||
Control::Translation(axes) => match *axes {
|
||||
Axes::SCREEN_XY => result.xlate_screen = idx as i32,
|
||||
Axes::XY => result.xlate_xy = idx as i32,
|
||||
Axes::XZ => result.xlate_xz = idx as i32,
|
||||
Axes::YZ => result.xlate_yz = idx as i32,
|
||||
axis => match shape {
|
||||
Shape::Line { .. } => match axis {
|
||||
Axes::X => result.xlate_x = idx as i32,
|
||||
Axes::Y => result.xlate_y = idx as i32,
|
||||
Axes::Z => result.xlate_z = idx as i32,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Shape::Cone { .. } => match axis {
|
||||
Axes::X => result.xlate_x_cone = idx as i32,
|
||||
Axes::Y => result.xlate_y_cone = idx as i32,
|
||||
Axes::Z => result.xlate_z_cone = idx as i32,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
},
|
||||
Control::Rotation(axes) => match *axes {
|
||||
Axes::SCREEN_Z => result.rotate_screen = idx as i32,
|
||||
Axes::X => result.rotate_x = idx as i32,
|
||||
Axes::Y => result.rotate_y = idx as i32,
|
||||
Axes::Z => result.rotate_z = idx as i32,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,17 @@
|
||||
mod composition;
|
||||
mod model;
|
||||
mod view;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use super::{InteractionMode, InteractionTransform, Selected, TransformSpace};
|
||||
use super::{InteractionMode, Selected, TransformSpace, TransformType};
|
||||
|
||||
pub(super) struct StudioGizmosPlugin;
|
||||
|
||||
impl Plugin for StudioGizmosPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Update, view::draw);
|
||||
app.add_systems(PreUpdate, (model::update_spawns, view::configure))
|
||||
.add_systems(Update, model::update)
|
||||
.add_systems(PostUpdate, view::draw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,315 @@
|
||||
use bevy::prelude::*;
|
||||
use bitflags::bitflags;
|
||||
|
||||
use super::{InteractionMode, Selected, TransformSpace, TransformType};
|
||||
|
||||
pub(super) fn update_spawns(
|
||||
mut cmd: Commands,
|
||||
q_needs_spawn: Query<Entity, (With<Selected>, Without<Gizmo>)>,
|
||||
q_needs_despawn: Query<Entity, (Without<Selected>, With<Gizmo>)>,
|
||||
) {
|
||||
// TODO: Is there a way to take a component from one entity and move it onto
|
||||
// another entity without recreating the component from scratch? Would
|
||||
// save us some unnecessary reallocations since Gizmo holds a vector
|
||||
for ent in q_needs_despawn.iter() {
|
||||
cmd.entity(ent).remove::<Gizmo>();
|
||||
}
|
||||
|
||||
for ent in q_needs_spawn.iter() {
|
||||
cmd.entity(ent).insert(Gizmo::default());
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn update(
|
||||
mut q_gizmo: Query<(&mut Gizmo, &GlobalTransform)>,
|
||||
q_camera_xform: Query<&Transform, With<Camera>>,
|
||||
mode: Res<InteractionMode>,
|
||||
) {
|
||||
let Ok((mut gizmo, world_xform)) = q_gizmo.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
let Ok(cam_xform) = q_camera_xform.get_single() else {
|
||||
return;
|
||||
};
|
||||
|
||||
gizmo.update_composition(&mode);
|
||||
|
||||
let xform = world_xform.compute_transform();
|
||||
let basis = match mode.space {
|
||||
TransformSpace::Local => xform,
|
||||
TransformSpace::World => Transform::IDENTITY,
|
||||
};
|
||||
|
||||
for el in gizmo.0.iter_mut() {
|
||||
el.update(mode.transform, &xform, &basis, cam_xform);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Default, Debug)]
|
||||
pub(super) struct Gizmo(pub Vec<GizmoElement>);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) struct GizmoElement {
|
||||
pub shape: Shape,
|
||||
pub color: GizmoColor,
|
||||
pub control: Control,
|
||||
}
|
||||
|
||||
impl GizmoElement {
|
||||
#[inline]
|
||||
fn update(
|
||||
&mut self,
|
||||
ttype: TransformType,
|
||||
xform: &Transform,
|
||||
basis: &Transform,
|
||||
cam_xform: &Transform,
|
||||
) {
|
||||
let dist = Vec3::distance(cam_xform.translation, xform.translation);
|
||||
let base_size = dist / 20.;
|
||||
|
||||
match self.control {
|
||||
Control::Translation(axes) => match axes {
|
||||
Axes::SCREEN_XY => {
|
||||
self.update_xlation_ring(xform, cam_xform, base_size);
|
||||
}
|
||||
Axes::X | Axes::Y | Axes::Z => match self.shape {
|
||||
Shape::Line(_) => {
|
||||
self.update_xlation_line(axes, xform, basis, base_size);
|
||||
}
|
||||
Shape::Cone(_) => {
|
||||
self.update_xlation_cone(ttype, axes, xform, basis, base_size);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Axes::XY | Axes::XZ | Axes::YZ => {
|
||||
self.update_xlation_crossbar(axes, xform, basis, base_size);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
Control::Rotation(axes) => match axes {
|
||||
Axes::SCREEN_Z => {
|
||||
self.update_rotation_ring_screenspace(xform, cam_xform, base_size);
|
||||
}
|
||||
Axes::X | Axes::Y | Axes::Z => {
|
||||
self.update_rotation_ring(axes, xform, basis, base_size);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_xlation_ring(&mut self, xform: &Transform, cam_xform: &Transform, base_size: f32) {
|
||||
let Shape::Ring(ring) = &mut self.shape else {
|
||||
panic!("Wrong shape for Screen XY Translation: {:?}", self.shape)
|
||||
};
|
||||
|
||||
ring.origin = xform.translation;
|
||||
ring.normal = cam_xform.forward();
|
||||
ring.radius = base_size * 0.2;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_xlation_line(
|
||||
&mut self,
|
||||
axis: Axes,
|
||||
xform: &Transform,
|
||||
basis: &Transform,
|
||||
base_size: f32,
|
||||
) {
|
||||
let Shape::Line(line) = &mut self.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let direction = match axis {
|
||||
Axes::X => basis.local_x(),
|
||||
Axes::Y => basis.local_y(),
|
||||
Axes::Z => basis.local_z(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
line.origin = xform.translation + direction * base_size * 0.25;
|
||||
line.direction = direction;
|
||||
line.length = base_size * 1.35;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_xlation_cone(
|
||||
&mut self,
|
||||
ttype: TransformType,
|
||||
axis: Axes,
|
||||
xform: &Transform,
|
||||
basis: &Transform,
|
||||
base_size: f32,
|
||||
) {
|
||||
let Shape::Cone(cone) = &mut self.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let offset_factor = if ttype.contains(TransformType::ROTATE) {
|
||||
1.4
|
||||
} else {
|
||||
1.2
|
||||
};
|
||||
|
||||
let direction = match axis {
|
||||
Axes::X => basis.local_x(),
|
||||
Axes::Y => basis.local_y(),
|
||||
Axes::Z => basis.local_z(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
cone.base = xform.translation + direction * base_size * offset_factor;
|
||||
cone.direction = direction;
|
||||
cone.radius = base_size * 0.1;
|
||||
cone.length = base_size * 0.4;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_xlation_crossbar(
|
||||
&mut self,
|
||||
plane: Axes,
|
||||
xform: &Transform,
|
||||
basis: &Transform,
|
||||
base_size: f32,
|
||||
) {
|
||||
let Shape::Line(line) = &mut self.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let (a, b) = match plane {
|
||||
Axes::XY => (basis.local_x(), basis.local_y()),
|
||||
Axes::XZ => (basis.local_x(), basis.local_z()),
|
||||
Axes::YZ => (basis.local_y(), basis.local_z()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let start = xform.translation + a * base_size * 0.925;
|
||||
let end = xform.translation + b * base_size * 0.925;
|
||||
|
||||
let vector = end - start;
|
||||
let length = vector.length();
|
||||
let direction = vector / length;
|
||||
|
||||
line.origin = start;
|
||||
line.direction = direction;
|
||||
line.length = length;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_rotation_ring_screenspace(
|
||||
&mut self,
|
||||
xform: &Transform,
|
||||
cam_xform: &Transform,
|
||||
base_size: f32,
|
||||
) {
|
||||
let Shape::Ring(ring) = &mut self.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
ring.origin = xform.translation;
|
||||
ring.normal = cam_xform.forward();
|
||||
ring.radius = base_size * 1.2;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn update_rotation_ring(
|
||||
&mut self,
|
||||
axis: Axes,
|
||||
xform: &Transform,
|
||||
basis: &Transform,
|
||||
base_size: f32,
|
||||
) {
|
||||
let Shape::Ring(ring) = &mut self.shape else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let normal = match axis {
|
||||
Axes::X => basis.local_x(),
|
||||
Axes::Y => basis.local_y(),
|
||||
Axes::Z => basis.local_z(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
ring.origin = xform.translation;
|
||||
ring.normal = normal;
|
||||
ring.radius = base_size;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) enum GizmoColor {
|
||||
Single(Color),
|
||||
Gradient(Color, Color),
|
||||
}
|
||||
|
||||
impl From<Color> for GizmoColor {
|
||||
fn from(value: Color) -> Self {
|
||||
Self::Single(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(Color, Color)> for GizmoColor {
|
||||
fn from(value: (Color, Color)) -> Self {
|
||||
Self::Gradient(value.0, value.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) enum Shape {
|
||||
Ring(Ring),
|
||||
Line(Line),
|
||||
Cone(Cone),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(super) struct Ring {
|
||||
pub origin: Vec3,
|
||||
pub normal: Vec3,
|
||||
pub radius: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(super) struct Arc {
|
||||
pub center: Vec3,
|
||||
pub axis: Vec3,
|
||||
pub start: Vec3,
|
||||
pub length: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(super) struct Line {
|
||||
pub origin: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub length: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub(super) struct Cone {
|
||||
pub base: Vec3,
|
||||
pub direction: Vec3,
|
||||
pub radius: f32,
|
||||
pub length: f32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub(super) enum Control {
|
||||
Translation(Axes),
|
||||
Rotation(Axes),
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub (super) struct Axes: u8 {
|
||||
const X = 0b0001;
|
||||
const Y = 0b0010;
|
||||
const Z = 0b0100;
|
||||
const SCREEN = 0b1000;
|
||||
const XY = 0b0011;
|
||||
const XZ = 0b0101;
|
||||
const YZ = 0b0110;
|
||||
const XYZ = 0b0111;
|
||||
const SCREEN_XY = 0b1011;
|
||||
const SCREEN_Z = 0b1100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,240 +2,338 @@ use std::f32::consts;
|
||||
|
||||
use bevy::{prelude::*, window::PrimaryWindow};
|
||||
|
||||
use crate::{design_system, math::Lerp};
|
||||
// use crate::{design_system, math::Lerp};
|
||||
|
||||
use super::{InteractionMode, InteractionTransform, Selected, TransformSpace};
|
||||
#[rustfmt::skip]
|
||||
use super::{
|
||||
model::{Arc, Axes, Control, Gizmo, GizmoColor, Ring, Shape},
|
||||
// InteractionMode, Selected, TransformSpace, TransformType,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum GizmoVariant {
|
||||
Full,
|
||||
Partial,
|
||||
// #[derive(Clone, Copy)]
|
||||
// enum GizmoVariant {
|
||||
// Full,
|
||||
// Partial,
|
||||
// }
|
||||
|
||||
pub(super) fn configure(
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
mut config: ResMut<GizmoConfig>,
|
||||
) {
|
||||
let scale_factor = q_window.single().scale_factor() as f32;
|
||||
config.line_width = 4. * scale_factor;
|
||||
config.line_perspective = false;
|
||||
config.depth_bias = -1.;
|
||||
}
|
||||
|
||||
pub(super) fn draw(
|
||||
q_selection: Query<&GlobalTransform, With<Selected>>,
|
||||
q_gizmo: Query<&Gizmo>,
|
||||
q_camera_xform: Query<&Transform, With<Camera>>,
|
||||
q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
mode: Res<InteractionMode>,
|
||||
mut gizmos: Gizmos,
|
||||
mut config: ResMut<GizmoConfig>,
|
||||
) {
|
||||
if !q_selection.is_empty() {
|
||||
// TODO: Use an app state or something so we only have to set this up once
|
||||
let scale_factor = q_window.single().scale_factor() as f32;
|
||||
config.line_width = 4. * scale_factor;
|
||||
config.line_perspective = false;
|
||||
config.depth_bias = -1.;
|
||||
let cam_xform = q_camera_xform.single();
|
||||
|
||||
let camera_xform = q_camera_xform.single();
|
||||
// TODO (maybe):
|
||||
// 1. Map GizmoElements to a new Vec<(Shape, Color)>
|
||||
// 2. Bisect the rotator rings and push the back sides into the vec
|
||||
// 3. Sort by camera depth
|
||||
// 4. Draw
|
||||
//
|
||||
// (Starting to feel like a really gross hack -- wish I had a better
|
||||
// grasp of Bevy's render pipeline so we didn't have to lean so hard on
|
||||
// the Gizmos API)
|
||||
|
||||
// TODO: We only want to render one gizmo -- this is just for testing
|
||||
for world_xform in q_selection.iter() {
|
||||
let xform = world_xform.compute_transform();
|
||||
// Note: Scaling the gizmo size like this isn't perfect due to
|
||||
// distortion from the perspective projection. It bugged me at
|
||||
// first, but even Blender exhibits this type of gizmo distortion if
|
||||
// you dial the camera's focal length way down, so I'm gonna call it
|
||||
// good enough.
|
||||
let dist = Vec3::distance(camera_xform.translation, xform.translation);
|
||||
let size = dist / 20.;
|
||||
for gizmo in q_gizmo.iter() {
|
||||
for el in gizmo.0.iter() {
|
||||
match el.shape {
|
||||
Shape::Ring(ring) => {
|
||||
let GizmoColor::Single(color) = el.color else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
let variant = if mode
|
||||
.transform
|
||||
.contains(InteractionTransform::TRANSLATE | InteractionTransform::ROTATE)
|
||||
{
|
||||
GizmoVariant::Partial
|
||||
} else {
|
||||
GizmoVariant::Full
|
||||
};
|
||||
if let Control::Rotation(Axes::X | Axes::Y | Axes::Z) = el.control {
|
||||
let (front, _back) = bisect_rotator_ring(&ring, cam_xform);
|
||||
gizmos.arc(front.center, front.axis, front.start, front.length, color);
|
||||
// TODO
|
||||
// gizmos.arc(
|
||||
// back.center,
|
||||
// back.axis,
|
||||
// back.start,
|
||||
// back.length,
|
||||
// color.with_a(0.2),
|
||||
// );
|
||||
} else {
|
||||
gizmos.circle(ring.origin, ring.normal, ring.radius, color);
|
||||
}
|
||||
}
|
||||
Shape::Line(line) => {
|
||||
// TODO: Camera-direction fade
|
||||
let start = line.origin;
|
||||
let end = line.origin + line.direction * line.length;
|
||||
|
||||
if mode.transform.contains(InteractionTransform::TRANSLATE) {
|
||||
draw_translate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
}
|
||||
if mode.transform.contains(InteractionTransform::ROTATE) {
|
||||
draw_rotate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
match el.color {
|
||||
GizmoColor::Single(color) => {
|
||||
gizmos.line(start, end, color);
|
||||
}
|
||||
GizmoColor::Gradient(color_a, color_b) => {
|
||||
gizmos.line_gradient(start, end, color_a, color_b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Shape::Cone(cone) => {
|
||||
// TODO: Camera-direction fade
|
||||
let GizmoColor::Single(color) = el.color else {
|
||||
unreachable!()
|
||||
};
|
||||
gizmos.cone(cone.base, cone.direction, cone.radius, cone.length, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: We might need more control over the Z-ordering / depth bias than the
|
||||
// Bevy gizmos give us. As best as I can tell, the elements are drawn in
|
||||
// reverse invocation order (the methods called last are rendered first,
|
||||
// like the commands are being popped from a LIFO stack), but it's not
|
||||
// 100% consistent and there seems to be lots of flickering / Z-fighting.
|
||||
// Might not be a big deal, so I'm inclined to roll with it for now and
|
||||
// see how users respond.
|
||||
fn bisect_rotator_ring(ring: &Ring, cam_xform: &Transform) -> (Arc, Arc) {
|
||||
let plane_intersection = Vec3::cross(ring.normal, cam_xform.forward()).normalize();
|
||||
let start_a = ring.origin + plane_intersection * ring.radius;
|
||||
let start_b = ring.origin - plane_intersection * ring.radius;
|
||||
|
||||
#[inline]
|
||||
fn draw_translate_gizmo(
|
||||
gizmos: &mut Gizmos,
|
||||
variant: GizmoVariant,
|
||||
xform: &Transform,
|
||||
camera_xform: &Transform,
|
||||
space: TransformSpace,
|
||||
size: f32,
|
||||
) {
|
||||
let origin = xform.translation;
|
||||
let basis = match space {
|
||||
TransformSpace::Local => xform,
|
||||
TransformSpace::World => &Transform::IDENTITY,
|
||||
let arc_a = Arc {
|
||||
center: ring.origin,
|
||||
axis: ring.normal,
|
||||
start: start_a,
|
||||
length: consts::PI,
|
||||
};
|
||||
|
||||
for (direction, mut color) in [
|
||||
(basis.local_x(), design_system::Color::RED),
|
||||
(basis.local_y(), design_system::Color::GREEN),
|
||||
(basis.local_z(), design_system::Color::BLUE),
|
||||
] {
|
||||
// Fade the arrow out when it's pointed directly at the camera.
|
||||
// (Prevents overlapping/obscuring the center screen-space ring, or just
|
||||
// generally getting in the way when it's not very useful to grab onto.)
|
||||
let alpha = (1. - Vec3::dot(camera_xform.forward(), direction).abs())
|
||||
.remap((0., 0.1), (0., 1.))
|
||||
.clamp(0., 1.);
|
||||
let mut arc_b = arc_a;
|
||||
arc_b.start = start_b;
|
||||
|
||||
color = color.with_a(alpha);
|
||||
|
||||
// In "Partial" mode, we only draw the center ring and the arrow tips to
|
||||
// leave room for the rotator rings.
|
||||
if matches!(variant, GizmoVariant::Full) {
|
||||
// Draw a line from just outside the center ring to the tip of the arrow
|
||||
let start = origin + direction * size * 0.25;
|
||||
let end = origin + direction * size * 1.6;
|
||||
gizmos.line(start, end, color);
|
||||
|
||||
// Draw a line from the midpoint of this arrow to the midpoint of
|
||||
// the next one. This "crossbar" can be used to translate the object
|
||||
// on the plane defined by the corresponding axes.
|
||||
let midpoint_self = origin + ((end - origin) / 2.);
|
||||
let (ortho, mut color_ortho) = get_ortho(basis, direction);
|
||||
|
||||
// Fade the crossbar when its plane is edge-on with the camera, just
|
||||
// like we did for the arrow pointing toward/away from the camera.
|
||||
let ortho_alpha = (1. - Vec3::dot(camera_xform.forward(), ortho).abs())
|
||||
.remap((0., 0.1), (0., 1.))
|
||||
.clamp(0., 1.);
|
||||
|
||||
color_ortho = color_ortho.with_a(f32::min(ortho_alpha, alpha));
|
||||
|
||||
let ortho_end = origin + ortho * size * 1.6;
|
||||
let midpoint_ortho = origin + ((ortho_end - origin) / 2.);
|
||||
gizmos.line_gradient(midpoint_self, midpoint_ortho, color, color_ortho);
|
||||
}
|
||||
|
||||
// Draw the arrow tip
|
||||
let factor = match variant {
|
||||
GizmoVariant::Full => 1.2,
|
||||
GizmoVariant::Partial => 1.4,
|
||||
};
|
||||
let cone_base = origin + direction * size * factor;
|
||||
|
||||
let radius = size * 0.1;
|
||||
let length = size * 0.4;
|
||||
gizmos.cone(cone_base, direction, radius, length, color);
|
||||
}
|
||||
|
||||
// Draw the center ring. This can be dragged around to translate the object
|
||||
// in screen-space (i.e. on the camera's XY plane).
|
||||
gizmos.circle(origin, camera_xform.forward(), size * 0.2, Color::WHITE);
|
||||
(arc_a, arc_b)
|
||||
}
|
||||
|
||||
// Dumb helper function for grabbing the normal vector/color for the translation
|
||||
// gizmo's next arrow. We use this to determine where and how to render the
|
||||
// planar translation "crossbars".
|
||||
#[inline]
|
||||
fn get_ortho(xform: &Transform, compare: Vec3) -> (Vec3, Color) {
|
||||
let x = xform.local_x();
|
||||
let y = xform.local_y();
|
||||
let z = xform.local_z();
|
||||
// pub(super) fn draw_old(
|
||||
// q_selection: Query<&GlobalTransform, With<Selected>>,
|
||||
// q_camera_xform: Query<&Transform, With<Camera>>,
|
||||
// q_window: Query<&Window, With<PrimaryWindow>>,
|
||||
// mode: Res<InteractionMode>,
|
||||
// mut gizmos: Gizmos,
|
||||
// mut config: ResMut<GizmoConfig>,
|
||||
// ) {
|
||||
// if !q_selection.is_empty() {
|
||||
// // TODO: Use an app state or something so we only have to set this up once
|
||||
// let scale_factor = q_window.single().scale_factor() as f32;
|
||||
// config.line_width = 4. * scale_factor;
|
||||
// config.line_perspective = false;
|
||||
// config.depth_bias = -1.;
|
||||
|
||||
let diff_x = (compare - x).length_squared();
|
||||
let diff_y = (compare - y).length_squared();
|
||||
let diff_z = (compare - z).length_squared();
|
||||
// let camera_xform = q_camera_xform.single();
|
||||
|
||||
if diff_x < diff_y && diff_x < diff_z {
|
||||
(y, design_system::Color::GREEN)
|
||||
} else if diff_y < diff_x && diff_y < diff_z {
|
||||
(z, design_system::Color::BLUE)
|
||||
} else {
|
||||
(x, design_system::Color::RED)
|
||||
}
|
||||
}
|
||||
// // TODO: We only want to render one gizmo -- this is just for testing
|
||||
// for world_xform in q_selection.iter() {
|
||||
// let xform = world_xform.compute_transform();
|
||||
// // Note: Scaling the gizmo size like this isn't perfect due to
|
||||
// // distortion from the perspective projection. It bugged me at
|
||||
// // first, but even Blender exhibits this type of gizmo distortion if
|
||||
// // you dial the camera's focal length way down, so I'm gonna call it
|
||||
// // good enough.
|
||||
// let dist = Vec3::distance(camera_xform.translation, xform.translation);
|
||||
// let size = dist / 20.;
|
||||
|
||||
#[inline]
|
||||
fn draw_rotate_gizmo(
|
||||
gizmos: &mut Gizmos,
|
||||
variant: GizmoVariant,
|
||||
xform: &Transform,
|
||||
camera_xform: &Transform,
|
||||
space: TransformSpace,
|
||||
radius: f32,
|
||||
) {
|
||||
let origin = xform.translation;
|
||||
let basis = match space {
|
||||
TransformSpace::Local => xform,
|
||||
TransformSpace::World => &Transform::IDENTITY,
|
||||
};
|
||||
let camera_fwd = camera_xform.forward();
|
||||
// let variant = if mode
|
||||
// .transform
|
||||
// .contains(TransformType::TRANSLATE | TransformType::ROTATE)
|
||||
// {
|
||||
// GizmoVariant::Partial
|
||||
// } else {
|
||||
// GizmoVariant::Full
|
||||
// };
|
||||
|
||||
// Draw a ring around the whole gizmo for rotating the object in screen
|
||||
// space (i.e. around the camera's forward vector).
|
||||
gizmos.circle(origin, camera_fwd, radius * 1.2, Color::WHITE);
|
||||
// if mode.transform.contains(TransformType::TRANSLATE) {
|
||||
// draw_translate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
// }
|
||||
// if mode.transform.contains(TransformType::ROTATE) {
|
||||
// draw_rotate_gizmo(&mut gizmos, variant, &xform, camera_xform, mode.space, size);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// Draw semicircles for the main basis vector axes, bisected by the camera's
|
||||
// XY plane. The `RotatorRings` argument determines which side of the plane
|
||||
// to draw on.
|
||||
draw_rotator_axis_rings(
|
||||
gizmos,
|
||||
RotatorRings::Near,
|
||||
origin,
|
||||
camera_fwd,
|
||||
basis,
|
||||
radius,
|
||||
);
|
||||
// // FIXME: We might need more control over the Z-ordering / depth bias than the
|
||||
// // Bevy gizmos give us. As best as I can tell, the elements are drawn in
|
||||
// // reverse invocation order (the methods called last are rendered first,
|
||||
// // like the commands are being popped from a LIFO stack), but it's not
|
||||
// // 100% consistent and there seems to be lots of flickering / Z-fighting.
|
||||
// // Might not be a big deal, so I'm inclined to roll with it for now and
|
||||
// // see how users respond.
|
||||
|
||||
// We skip drawing the far-side of the rings in "Partial" mode to cut down
|
||||
// on visual noise and leave less room for mis-clicks.
|
||||
if matches!(variant, GizmoVariant::Full) {
|
||||
draw_rotator_axis_rings(gizmos, RotatorRings::Far, origin, camera_fwd, basis, radius);
|
||||
}
|
||||
}
|
||||
// #[inline]
|
||||
// fn draw_translate_gizmo(
|
||||
// gizmos: &mut Gizmos,
|
||||
// variant: GizmoVariant,
|
||||
// xform: &Transform,
|
||||
// camera_xform: &Transform,
|
||||
// space: TransformSpace,
|
||||
// size: f32,
|
||||
// ) {
|
||||
// let origin = xform.translation;
|
||||
// let basis = match space {
|
||||
// TransformSpace::Local => xform,
|
||||
// TransformSpace::World => &Transform::IDENTITY,
|
||||
// };
|
||||
|
||||
enum RotatorRings {
|
||||
Near,
|
||||
Far,
|
||||
}
|
||||
// for (direction, mut color) in [
|
||||
// (basis.local_x(), design_system::Color::RED),
|
||||
// (basis.local_y(), design_system::Color::GREEN),
|
||||
// (basis.local_z(), design_system::Color::BLUE),
|
||||
// ] {
|
||||
// // Fade the arrow out when it's pointed directly at the camera.
|
||||
// // (Prevents overlapping/obscuring the center screen-space ring, or just
|
||||
// // generally getting in the way when it's not very useful to grab onto.)
|
||||
// let alpha = (1. - Vec3::dot(camera_xform.forward(), direction).abs())
|
||||
// .remap((0., 0.1), (0., 1.))
|
||||
// .clamp(0., 1.);
|
||||
|
||||
#[inline]
|
||||
fn draw_rotator_axis_rings(
|
||||
gizmos: &mut Gizmos,
|
||||
which: RotatorRings,
|
||||
origin: Vec3,
|
||||
camera_fwd: Vec3,
|
||||
basis: &Transform,
|
||||
radius: f32,
|
||||
) {
|
||||
for (normal, color) in [
|
||||
(basis.local_x(), design_system::Color::RED),
|
||||
(basis.local_y(), design_system::Color::GREEN),
|
||||
(basis.local_z(), design_system::Color::BLUE),
|
||||
] {
|
||||
// The line of intersection between two planes will be mutually
|
||||
// orthogonal to the normal vectors of those planes, so we can find the
|
||||
// points of intersection between the ring and the camera plane by
|
||||
// taking the cross product of the camera direction and the rotation
|
||||
// axis, and scaling it by the ring's radius. The order of the operands
|
||||
// determines which of the intersection points we get.
|
||||
let (color, perp) = match which {
|
||||
RotatorRings::Near => (color, Vec3::cross(normal, camera_fwd)),
|
||||
RotatorRings::Far => (color.with_a(0.2), Vec3::cross(camera_fwd, normal)),
|
||||
};
|
||||
// color = color.with_a(alpha);
|
||||
|
||||
let start = origin + perp.normalize() * radius;
|
||||
// // In "Partial" mode, we only draw the center ring and the arrow tips to
|
||||
// // leave room for the rotator rings.
|
||||
// if matches!(variant, GizmoVariant::Full) {
|
||||
// // Draw a line from just outside the center ring to the tip of the arrow
|
||||
// let start = origin + direction * size * 0.25;
|
||||
// let end = origin + direction * size * 1.6;
|
||||
// gizmos.line(start, end, color);
|
||||
|
||||
gizmos.arc(origin, normal, start, consts::PI, color);
|
||||
}
|
||||
}
|
||||
// // Draw a line from the midpoint of this arrow to the midpoint of
|
||||
// // the next one. This "crossbar" can be used to translate the object
|
||||
// // on the plane defined by the corresponding axes.
|
||||
// let midpoint_self = origin + ((end - origin) / 2.);
|
||||
// let (ortho, mut color_ortho) = get_ortho(basis, direction);
|
||||
|
||||
// // Fade the crossbar when its plane is edge-on with the camera, just
|
||||
// // like we did for the arrow pointing toward/away from the camera.
|
||||
// let ortho_alpha = (1. - Vec3::dot(camera_xform.forward(), ortho).abs())
|
||||
// .remap((0., 0.1), (0., 1.))
|
||||
// .clamp(0., 1.);
|
||||
|
||||
// color_ortho = color_ortho.with_a(f32::min(ortho_alpha, alpha));
|
||||
|
||||
// let ortho_end = origin + ortho * size * 1.6;
|
||||
// let midpoint_ortho = origin + ((ortho_end - origin) / 2.);
|
||||
// gizmos.line_gradient(midpoint_self, midpoint_ortho, color, color_ortho);
|
||||
// }
|
||||
|
||||
// // Draw the arrow tip
|
||||
// let factor = match variant {
|
||||
// GizmoVariant::Full => 1.2,
|
||||
// GizmoVariant::Partial => 1.4,
|
||||
// };
|
||||
// let cone_base = origin + direction * size * factor;
|
||||
|
||||
// let radius = size * 0.1;
|
||||
// let length = size * 0.4;
|
||||
// gizmos.cone(cone_base, direction, radius, length, color);
|
||||
// }
|
||||
|
||||
// // Draw the center ring. This can be dragged around to translate the object
|
||||
// // in screen-space (i.e. on the camera's XY plane).
|
||||
// gizmos.circle(origin, camera_xform.forward(), size * 0.2, Color::WHITE);
|
||||
// }
|
||||
|
||||
// // Dumb helper function for grabbing the normal vector/color for the translation
|
||||
// // gizmo's next arrow. We use this to determine where and how to render the
|
||||
// // planar translation "crossbars".
|
||||
// #[inline]
|
||||
// fn get_ortho(xform: &Transform, compare: Vec3) -> (Vec3, Color) {
|
||||
// let x = xform.local_x();
|
||||
// let y = xform.local_y();
|
||||
// let z = xform.local_z();
|
||||
|
||||
// let diff_x = (compare - x).length_squared();
|
||||
// let diff_y = (compare - y).length_squared();
|
||||
// let diff_z = (compare - z).length_squared();
|
||||
|
||||
// if diff_x < diff_y && diff_x < diff_z {
|
||||
// (y, design_system::Color::GREEN)
|
||||
// } else if diff_y < diff_x && diff_y < diff_z {
|
||||
// (z, design_system::Color::BLUE)
|
||||
// } else {
|
||||
// (x, design_system::Color::RED)
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[inline]
|
||||
// fn draw_rotate_gizmo(
|
||||
// gizmos: &mut Gizmos,
|
||||
// variant: GizmoVariant,
|
||||
// xform: &Transform,
|
||||
// camera_xform: &Transform,
|
||||
// space: TransformSpace,
|
||||
// radius: f32,
|
||||
// ) {
|
||||
// let origin = xform.translation;
|
||||
// let basis = match space {
|
||||
// TransformSpace::Local => xform,
|
||||
// TransformSpace::World => &Transform::IDENTITY,
|
||||
// };
|
||||
// let camera_fwd = camera_xform.forward();
|
||||
|
||||
// // Draw a ring around the whole gizmo for rotating the object in screen
|
||||
// // space (i.e. around the camera's forward vector).
|
||||
// gizmos.circle(origin, camera_fwd, radius * 1.2, Color::WHITE);
|
||||
|
||||
// // Draw semicircles for the main basis vector axes, bisected by the camera's
|
||||
// // XY plane. The `RotatorRings` argument determines which side of the plane
|
||||
// // to draw on.
|
||||
// draw_rotator_axis_rings(
|
||||
// gizmos,
|
||||
// RotatorRings::Near,
|
||||
// origin,
|
||||
// camera_fwd,
|
||||
// basis,
|
||||
// radius,
|
||||
// );
|
||||
|
||||
// // We skip drawing the far-side of the rings in "Partial" mode to cut down
|
||||
// // on visual noise and leave less room for mis-clicks.
|
||||
// if matches!(variant, GizmoVariant::Full) {
|
||||
// draw_rotator_axis_rings(gizmos, RotatorRings::Far, origin, camera_fwd, basis, radius);
|
||||
// }
|
||||
// }
|
||||
|
||||
// enum RotatorRings {
|
||||
// Near,
|
||||
// Far,
|
||||
// }
|
||||
|
||||
// #[inline]
|
||||
// fn draw_rotator_axis_rings(
|
||||
// gizmos: &mut Gizmos,
|
||||
// which: RotatorRings,
|
||||
// origin: Vec3,
|
||||
// camera_fwd: Vec3,
|
||||
// basis: &Transform,
|
||||
// radius: f32,
|
||||
// ) {
|
||||
// for (normal, color) in [
|
||||
// (basis.local_x(), design_system::Color::RED),
|
||||
// (basis.local_y(), design_system::Color::GREEN),
|
||||
// (basis.local_z(), design_system::Color::BLUE),
|
||||
// ] {
|
||||
// // The line of intersection between two planes will be mutually
|
||||
// // orthogonal to the normal vectors of those planes, so we can find the
|
||||
// // points of intersection between the ring and the camera plane by
|
||||
// // taking the cross product of the camera direction and the rotation
|
||||
// // axis, and scaling it by the ring's radius. The order of the operands
|
||||
// // determines which of the intersection points we get.
|
||||
// let (color, perp) = match which {
|
||||
// RotatorRings::Near => (color, Vec3::cross(normal, camera_fwd)),
|
||||
// RotatorRings::Far => (color.with_a(0.2), Vec3::cross(camera_fwd, normal)),
|
||||
// };
|
||||
|
||||
// let start = origin + perp.normalize() * radius;
|
||||
|
||||
// gizmos.arc(origin, normal, start, consts::PI, color);
|
||||
// }
|
||||
// }
|
||||
|
||||
trait GizmoExtras {
|
||||
/// Draw an arc around `center` with angular `length` (in radians),
|
||||
|
||||
@@ -46,28 +46,29 @@ pub struct SelectionMode(SelectionChannel);
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct InteractionMode {
|
||||
pub transform: InteractionTransform,
|
||||
pub transform: TransformType,
|
||||
pub space: TransformSpace,
|
||||
}
|
||||
|
||||
impl Default for InteractionMode {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
transform: InteractionTransform::TRANSLATE | InteractionTransform::ROTATE,
|
||||
transform: TransformType::TRANSLATE | TransformType::ROTATE,
|
||||
space: default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[wasm_bindgen]
|
||||
pub struct InteractionTransform(u8);
|
||||
pub struct TransformType(u8);
|
||||
|
||||
bitflags! {
|
||||
impl InteractionTransform: u8 {
|
||||
const TRANSLATE = 0b0001;
|
||||
const ROTATE = 0b0010;
|
||||
impl TransformType: u8 {
|
||||
const TRANSLATE = 0b0001;
|
||||
const ROTATE = 0b0010;
|
||||
const TRANSLATE_ROTATE = 0b0011;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,13 +92,10 @@ pub mod wasm {
|
||||
|
||||
use crate::wasm::{self, WasmMessageBuffer};
|
||||
|
||||
use super::{
|
||||
InteractionMode, InteractionTransform, SelectionChannel, SelectionMode, TransformSpace,
|
||||
};
|
||||
use super::{InteractionMode, SelectionChannel, SelectionMode, TransformSpace, TransformType};
|
||||
|
||||
static SELECT_MODE_BUF: WasmMessageBuffer<SelectionChannel> = WasmMessageBuffer::new();
|
||||
static INTERACTION_TRANSFORM_BUF: WasmMessageBuffer<InteractionTransform> =
|
||||
WasmMessageBuffer::new();
|
||||
static INTERACTION_TRANSFORM_BUF: WasmMessageBuffer<TransformType> = WasmMessageBuffer::new();
|
||||
static INTERACTION_SPACE_BUF: WasmMessageBuffer<TransformSpace> = WasmMessageBuffer::new();
|
||||
|
||||
#[wasm_bindgen(js_name = setSelectionMode)]
|
||||
@@ -107,7 +105,7 @@ pub mod wasm {
|
||||
|
||||
#[wasm_bindgen(js_name = setInteractionTransform)]
|
||||
pub fn set_interaction_transform(flags: u8) {
|
||||
wasm::write_message!(INTERACTION_TRANSFORM_BUF, InteractionTransform(flags));
|
||||
wasm::write_message!(INTERACTION_TRANSFORM_BUF, TransformType(flags));
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = setTransformSpace)]
|
||||
|
||||
Reference in New Issue
Block a user