use std::{borrow::Cow, collections::HashSet, fmt, marker::PhantomData};
use crate::grid::{
cartesian::coordinates::{Cartesian2D, Cartesian3D},
coordinate_system::CoordinateSystem,
direction::{Direction, DirectionTrait},
};
#[cfg(feature = "debug-traces")]
use tracing::warn;
#[cfg(feature = "bevy")]
use bevy::ecs::component::Component;
#[cfg(feature = "reflect")]
use bevy::{ecs::reflect::ReflectComponent, reflect::Reflect};
use super::{
rules::CARTESIAN_2D_ROTATION_AXIS,
socket::{Socket, SocketId, SocketsCartesian2D, SocketsCartesian3D},
};
pub type ModelIndex = usize;
pub type ModelVariantIndex = usize;
pub const DEFAULT_MODEL_WEIGHT: f32 = 1.0;
#[derive(Clone, Debug)]
pub struct ModelTemplate<C> {
sockets: Vec<Vec<Socket>>,
weight: f32,
allowed_rotations: HashSet<ModelRotation>,
typestate: PhantomData<C>,
}
impl ModelTemplate<Cartesian3D> {
pub(crate) fn new(sockets: SocketsCartesian3D) -> ModelTemplate<Cartesian3D> {
Self {
sockets: sockets.into(),
allowed_rotations: HashSet::from([ModelRotation::Rot0]),
weight: DEFAULT_MODEL_WEIGHT,
typestate: PhantomData,
}
}
pub fn rotated(&self, rotation: ModelRotation, axis: Direction) -> Self {
Self {
sockets: self.rotated_sockets(rotation, axis),
weight: self.weight,
allowed_rotations: self.allowed_rotations.clone(),
typestate: PhantomData,
}
}
}
impl ModelTemplate<Cartesian2D> {
pub(crate) fn new(sockets: SocketsCartesian2D) -> ModelTemplate<Cartesian2D> {
Self {
sockets: sockets.into(),
allowed_rotations: HashSet::from([ModelRotation::Rot0]),
weight: DEFAULT_MODEL_WEIGHT,
typestate: PhantomData,
}
}
pub fn rotated(&self, rotation: ModelRotation) -> Self {
Self {
sockets: self.rotated_sockets(rotation, CARTESIAN_2D_ROTATION_AXIS),
weight: self.weight,
allowed_rotations: self.allowed_rotations.clone(),
typestate: PhantomData,
}
}
}
impl<C: CoordinateSystem> ModelTemplate<C> {
pub fn with_rotation(mut self, rotation: ModelRotation) -> Self {
self.allowed_rotations = HashSet::from([rotation]);
self
}
pub fn with_additional_rotation(mut self, rotation: ModelRotation) -> Self {
self.allowed_rotations.insert(rotation);
self
}
pub fn with_rotations<R: Into<HashSet<ModelRotation>>>(mut self, rotations: R) -> Self {
self.allowed_rotations = rotations.into();
self
}
pub fn with_additional_rotations<R: IntoIterator<Item = ModelRotation>>(
mut self,
rotations: R,
) -> Self {
self.allowed_rotations.extend(rotations.into_iter());
self
}
pub fn with_all_rotations(mut self) -> Self {
self.allowed_rotations = ALL_MODEL_ROTATIONS.iter().cloned().collect();
self
}
pub fn with_weight<W: Into<f32>>(mut self, weight: W) -> Self {
let mut checked_weight = weight.into();
if checked_weight <= 0. {
#[cfg(feature = "debug-traces")]
warn!(
"Template had an invalid weight {} <= 0., weight overriden to f32::MIN: {}",
checked_weight,
f32::MIN_POSITIVE
);
checked_weight = f32::MIN_POSITIVE
};
self.weight = checked_weight;
self
}
fn rotated_sockets(&self, rotation: ModelRotation, rot_axis: C::Direction) -> Vec<Vec<Socket>> {
let mut rotated_sockets = vec![Vec::new(); self.sockets.len()];
if self.sockets.len() > rot_axis.into() {
for fixed_axis in [rot_axis, rot_axis.opposite()] {
rotated_sockets[fixed_axis.into()].extend(self.sockets[fixed_axis.into()].clone());
for socket in &mut rotated_sockets[fixed_axis.into()] {
socket.rotate(rotation);
}
}
}
let basis = rot_axis.rotation_basis();
let mut rotated_basis = basis.to_vec();
rotated_basis.rotate_right(rotation.index() as usize);
for i in 0..basis.len() {
rotated_sockets[basis[i].into()].extend(self.sockets[rotated_basis[i].into()].clone());
}
rotated_sockets
}
}
#[derive(Clone)]
pub struct ModelCollection<C: CoordinateSystem> {
models: Vec<Model<C>>,
}
impl<C: CoordinateSystem> ModelCollection<C> {
pub fn new() -> Self {
Self { models: Vec::new() }
}
pub fn create<T: Into<ModelTemplate<C>>>(&mut self, template: T) -> &mut Model<C> {
let model = Model::<C>::from_template(template.into(), self.models.len());
self.models.push(model);
self.models.last_mut().unwrap()
}
pub fn models_count(&self) -> usize {
self.models.len()
}
pub fn models(&self) -> std::slice::Iter<'_, Model<C>> {
self.models.iter()
}
pub fn models_mut(&mut self) -> std::slice::IterMut<'_, Model<C>> {
self.models.iter_mut()
}
pub fn last(&self) -> Option<&Model<C>> {
self.models.last()
}
pub fn last_mut(&mut self) -> Option<&mut Model<C>> {
self.models.last_mut()
}
pub(crate) fn create_variations(&self, rotation_axis: C::Direction) -> Vec<ModelVariation> {
let mut model_variations = Vec::new();
for model in self.models.iter() {
for rotation in ALL_MODEL_ROTATIONS {
if model.template.allowed_rotations.contains(&rotation) {
let rotated_sockets = model.template.rotated_sockets(*rotation, rotation_axis);
model_variations.push(ModelVariation {
sockets: rotated_sockets
.iter()
.map(|dir| dir.iter().map(|s| s.id()).collect())
.collect(),
weight: model.template.weight,
original_index: model.index,
rotation: *rotation,
#[cfg(feature = "models-names")]
name: model.name.clone(),
});
}
}
}
model_variations
}
}
#[derive(Clone, Debug)]
pub struct Model<C: CoordinateSystem> {
index: ModelIndex,
template: ModelTemplate<C>,
#[cfg(feature = "models-names")]
name: Option<Cow<'static, str>>,
}
impl<C: CoordinateSystem> Model<C> {
pub(crate) fn from_template(template: ModelTemplate<C>, index: ModelIndex) -> Model<C> {
Self {
index,
template,
#[cfg(feature = "models-names")]
name: None,
}
}
pub fn index(&self) -> ModelIndex {
self.index
}
pub fn with_rotation(&mut self, rotation: ModelRotation) -> &mut Self {
self.template.allowed_rotations = HashSet::from([rotation]);
self
}
pub fn with_additional_rotation(&mut self, rotation: ModelRotation) -> &mut Self {
self.template.allowed_rotations.insert(rotation);
self
}
pub fn with_rotations<R: Into<HashSet<ModelRotation>>>(&mut self, rotations: R) -> &mut Self {
self.template.allowed_rotations = rotations.into();
self
}
pub fn with_additional_rotations<R: IntoIterator<Item = ModelRotation>>(
&mut self,
rotations: R,
) -> &mut Self {
self.template
.allowed_rotations
.extend(rotations.into_iter());
self
}
pub fn with_all_rotations(&mut self) -> &mut Self {
self.template.allowed_rotations = ALL_MODEL_ROTATIONS.iter().cloned().collect();
self
}
pub fn with_weight<W: Into<f32>>(&mut self, weight: W) -> &mut Self {
let mut checked_weight = weight.into();
if checked_weight <= 0. {
#[cfg(feature = "debug-traces")]
warn!(
"Model with index {}, name {:?}, had an invalid weight {} <= 0., weight overriden to f32::MIN: {}",
self.index, self.name, checked_weight, f32::MIN_POSITIVE
);
checked_weight = f32::MIN_POSITIVE
};
self.template.weight = checked_weight;
self
}
#[allow(unused_mut)]
pub fn with_name(&mut self, _name: impl Into<Cow<'static, str>>) -> &mut Self {
#[cfg(feature = "models-names")]
{
self.name = Some(_name.into());
}
self
}
pub(crate) fn first_rot(&self) -> ModelRotation {
for rot in ALL_MODEL_ROTATIONS {
if self.template.allowed_rotations.contains(rot) {
return *rot;
}
}
ModelRotation::Rot0
}
pub fn instance(&self) -> ModelInstance {
ModelInstance {
model_index: self.index,
rotation: self.first_rot(),
}
}
}
impl<C: CoordinateSystem> Into<ModelTemplate<C>> for Model<C> {
fn into(self) -> ModelTemplate<C> {
self.template.clone()
}
}
#[derive(Debug)]
pub struct ModelVariation {
sockets: Vec<Vec<SocketId>>,
weight: f32,
original_index: ModelIndex,
rotation: ModelRotation,
#[cfg(feature = "models-names")]
pub name: Option<Cow<'static, str>>,
}
impl ModelVariation {
pub fn sockets(&self) -> &Vec<Vec<SocketId>> {
&self.sockets
}
pub fn weight(&self) -> f32 {
self.weight
}
pub fn original_index(&self) -> ModelIndex {
self.original_index
}
pub fn rotation(&self) -> ModelRotation {
self.rotation
}
pub(crate) fn to_instance(&self) -> ModelInstance {
ModelInstance {
model_index: self.original_index,
rotation: self.rotation,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "bevy", derive(Component, Default))]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Component))]
pub struct ModelInstance {
pub model_index: ModelIndex,
pub rotation: ModelRotation,
}
impl fmt::Display for ModelInstance {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "id: {}, rot: {}", self.model_index, self.rotation)
}
}
#[derive(Default, Clone, Copy, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "bevy", derive(Component))]
#[cfg_attr(feature = "reflect", derive(Reflect), reflect(Component))]
pub enum ModelRotation {
#[default]
Rot0,
Rot90,
Rot180,
Rot270,
}
impl fmt::Display for ModelRotation {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.value())
}
}
impl ModelRotation {
pub fn value(&self) -> u32 {
match *self {
ModelRotation::Rot0 => 0,
ModelRotation::Rot90 => 90,
ModelRotation::Rot180 => 180,
ModelRotation::Rot270 => 270,
}
}
pub fn rad(&self) -> f32 {
f32::to_radians(self.value() as f32)
}
pub fn index(&self) -> u8 {
match *self {
ModelRotation::Rot0 => 0,
ModelRotation::Rot90 => 1,
ModelRotation::Rot180 => 2,
ModelRotation::Rot270 => 3,
}
}
#[inline]
pub fn rotated(&self, rotation: ModelRotation) -> ModelRotation {
ALL_MODEL_ROTATIONS
[(self.index() as usize + rotation.index() as usize) % ALL_MODEL_ROTATIONS.len()]
}
#[inline]
pub fn rotate(&mut self, rotation: ModelRotation) {
*self = self.rotated(rotation);
}
#[inline]
pub fn next(&self) -> ModelRotation {
self.rotated(ModelRotation::Rot90)
}
}
pub const ALL_MODEL_ROTATIONS: &'static [ModelRotation] = &[
ModelRotation::Rot0,
ModelRotation::Rot90,
ModelRotation::Rot180,
ModelRotation::Rot270,
];