use alloc::{string::String, vec::Vec};
use scenix_core::NodeId;
use scenix_math::Vec3;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LayerMask(u32);
impl LayerMask {
pub const NONE: Self = Self(0);
pub const ALL: Self = Self(u32::MAX);
#[inline]
pub const fn from_bits(bits: u32) -> Self {
Self(bits)
}
#[inline]
pub const fn bits(self) -> u32 {
self.0
}
#[inline]
pub const fn intersects(self, other: Self) -> bool {
self.0 & other.0 != 0
}
#[inline]
pub const fn matches_node(self, node_layers: u32) -> bool {
self.0 & node_layers != 0
}
#[inline]
pub fn insert(&mut self, other: Self) {
self.0 |= other.0;
}
#[inline]
pub fn remove(&mut self, other: Self) {
self.0 &= !other.0;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct LayerPolicy {
pub selectable: LayerMask,
pub draggable: LayerMask,
pub transformable: LayerMask,
}
impl Default for LayerPolicy {
fn default() -> Self {
Self {
selectable: LayerMask::ALL,
draggable: LayerMask::ALL,
transformable: LayerMask::ALL,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct NodeEditorMetadata {
pub selectable: bool,
pub draggable: bool,
pub transformable: bool,
pub locked: bool,
pub visible_in_inspector: bool,
pub label: Option<String>,
pub tags: Vec<String>,
}
impl Default for NodeEditorMetadata {
fn default() -> Self {
Self {
selectable: true,
draggable: true,
transformable: true,
locked: false,
visible_in_inspector: true,
label: None,
tags: Vec::new(),
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum SelectionMode {
#[default]
Replace,
Add,
Toggle,
Remove,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelectionState {
pub hovered: Option<NodeId>,
pub active: Option<NodeId>,
selected: Vec<NodeId>,
}
impl SelectionState {
#[inline]
pub fn selected(&self) -> &[NodeId] {
&self.selected
}
#[inline]
pub fn contains(&self, id: NodeId) -> bool {
self.selected.binary_search(&id).is_ok()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SelectionChange {
pub added: Vec<NodeId>,
pub removed: Vec<NodeId>,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransformMode {
#[default]
Translate,
Rotate,
Scale,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransformSpace {
#[default]
World,
Local,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum TransformConstraint {
#[default]
Free,
X,
Y,
Z,
XY,
XZ,
YZ,
}
impl TransformConstraint {
pub const fn component_mask(self) -> Vec3 {
match self {
Self::Free => Vec3::ONE,
Self::X => Vec3::X,
Self::Y => Vec3::Y,
Self::Z => Vec3::Z,
Self::XY => Vec3::new(1.0, 1.0, 0.0),
Self::XZ => Vec3::new(1.0, 0.0, 1.0),
Self::YZ => Vec3::new(0.0, 1.0, 1.0),
}
}
pub const fn axis(self) -> Option<Vec3> {
match self {
Self::X => Some(Vec3::X),
Self::Y => Some(Vec3::Y),
Self::Z => Some(Vec3::Z),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct SnapSettings {
pub translation: Vec3,
pub rotation_radians: f32,
pub scale: Vec3,
}
impl SnapSettings {
pub fn snap_translation(self, delta: Vec3) -> Vec3 {
snap_vec3(delta, self.translation)
}
pub fn snap_rotation(self, radians: f32) -> f32 {
snap_scalar(radians, self.rotation_radians)
}
pub fn snap_scale(self, delta: Vec3) -> Vec3 {
snap_vec3(delta, self.scale)
}
}
fn snap_scalar(value: f32, increment: f32) -> f32 {
if increment.is_finite() && increment.abs() > 1.0e-6 {
let increment = increment.abs();
let scaled = value / increment;
if !scaled.is_finite() {
return value;
}
let rounded = if scaled >= 0.0 {
(scaled + 0.5) as i64
} else {
(scaled - 0.5) as i64
};
rounded as f32 * increment
} else {
value
}
}
fn snap_vec3(value: Vec3, increments: Vec3) -> Vec3 {
Vec3::new(
snap_scalar(value.x, increments.x),
snap_scalar(value.y, increments.y),
snap_scalar(value.z, increments.z),
)
}
pub(crate) fn apply_selection(
state: &mut SelectionState,
id: NodeId,
mode: SelectionMode,
) -> SelectionChange {
let previous = state.selected.clone();
match mode {
SelectionMode::Replace => {
state.selected.clear();
state.selected.push(id);
}
SelectionMode::Add => {
if let Err(index) = state.selected.binary_search(&id) {
state.selected.insert(index, id);
}
}
SelectionMode::Toggle => match state.selected.binary_search(&id) {
Ok(index) => {
state.selected.remove(index);
}
Err(index) => state.selected.insert(index, id),
},
SelectionMode::Remove => {
if let Ok(index) = state.selected.binary_search(&id) {
state.selected.remove(index);
}
}
}
selection_diff(&previous, &state.selected)
}
pub(crate) fn replace_selection(
state: &mut SelectionState,
mut selected: Vec<NodeId>,
) -> SelectionChange {
selected.sort_unstable();
selected.dedup();
let previous = core::mem::replace(&mut state.selected, selected);
selection_diff(&previous, &state.selected)
}
fn selection_diff(previous: &[NodeId], current: &[NodeId]) -> SelectionChange {
SelectionChange {
added: current
.iter()
.copied()
.filter(|id| previous.binary_search(id).is_err())
.collect(),
removed: previous
.iter()
.copied()
.filter(|id| current.binary_search(id).is_err())
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn snap_settings_quantize_relative_deltas() {
let snap = SnapSettings {
translation: Vec3::new(0.5, 1.0, 0.0),
rotation_radians: 0.25,
scale: Vec3::new(0.1, 0.0, 0.5),
};
assert_eq!(
snap.snap_translation(Vec3::new(0.74, 1.6, 0.3)),
Vec3::new(0.5, 2.0, 0.3)
);
assert_eq!(snap.snap_rotation(0.38), 0.5);
}
}