use crate::engine_v2::coords::Coords;
use crate::engine_v2::entity::object::ObjectRef;
use crate::engine_v2::position::Position;
use crate::engine_v2::position::XTermPosition;
use crate::engine_v2::position::YTermPosition;
use crate::engine_v2::size::Size;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Direction {
Linear,
Stationary,
Circular,
Relative,
Manual,
None,
}
#[derive(Clone)]
pub struct Movement {
current_coordinate_id: usize,
path: Vec<Coords>,
start: Position,
end: Position,
direction: Direction,
speed: i32,
is_done: bool,
offset: Coords,
ttl: usize, parent_object: Option<ObjectRef>,
started_tick_id: Option<usize>,
radius: usize,
active: bool,
}
impl Movement {
pub fn new_none() -> Self {
Self {
current_coordinate_id: 0,
path: vec![],
start: Position::new(
crate::engine_v2::position::XTermPosition::Coord(0),
crate::engine_v2::position::YTermPosition::Coord(0),
0,
),
end: Position::new(
crate::engine_v2::position::XTermPosition::Coord(0),
crate::engine_v2::position::YTermPosition::Coord(0),
0,
),
direction: Direction::None,
speed: 0,
is_done: false,
offset: Coords::new(0, 0, 0),
ttl: 0,
parent_object: None,
started_tick_id: None,
radius: 0,
active: true,
}
}
pub fn new_linear(start: Position, end: Position, speed: i32) -> Self {
Self {
current_coordinate_id: 0,
path: vec![],
start,
end,
direction: Direction::Linear,
speed,
is_done: false,
offset: Coords::new(0, 0, 0),
ttl: 0,
parent_object: None,
started_tick_id: None,
radius: 0,
active: true,
}
}
pub fn new_circular(start: Position, end: Position, speed: i32, radius: usize) -> Self {
Self {
current_coordinate_id: 0,
path: vec![],
start,
end,
direction: Direction::Circular,
speed,
is_done: false,
offset: Coords::new(0, 0, 0),
ttl: 0,
parent_object: None,
started_tick_id: None,
radius,
active: true,
}
}
pub fn new_stationary(position: Position, ttl: usize) -> Self {
Self {
current_coordinate_id: 0,
path: vec![],
start: position,
end: position,
direction: Direction::Stationary,
speed: 0,
is_done: false,
offset: Coords::new(0, 0, 0),
ttl,
parent_object: None,
started_tick_id: None,
radius: 0,
active: true,
}
}
pub fn new_relative(object: ObjectRef, offset: Coords) -> Self {
Self {
current_coordinate_id: 0,
path: vec![],
start: Position::new(XTermPosition::LeftOut, YTermPosition::BottomOut, 0),
end: Position::new(XTermPosition::LeftOut, YTermPosition::BottomOut, 0),
direction: Direction::Relative,
speed: -1,
is_done: false,
offset,
ttl: 0,
parent_object: Some(object),
started_tick_id: None,
radius: 0,
active: true,
}
}
pub fn direction(&self) -> Direction {
self.direction
}
pub fn speed(&self) -> i32 {
self.speed
}
pub fn has_started(&self) -> bool {
self.started_tick_id.is_some()
}
pub fn started_tick(&self) -> Option<usize> {
self.started_tick_id
}
pub fn is_active(&self) -> bool {
self.active
}
pub fn deactivate(&mut self) {
self.active = false;
}
pub fn activate(&mut self) {
self.active = true;
}
pub fn is_done(&self) -> bool {
self.is_done
}
pub fn get_coordinate(&self, _tick_id: usize) -> Coords {
match self.direction {
Direction::Relative => {
let parent_coords = self
.parent_object
.as_ref()
.unwrap()
.borrow_mut()
.movement()
.get_coordinate(_tick_id);
parent_coords + self.offset
}
_ => {
if self.path.is_empty() {
panic!("Object has no movement defined");
}
if self.current_coordinate_id >= self.path.len() {
return self.path[self.path.len() - 1];
}
self.path[self.current_coordinate_id] + self.offset
}
}
}
pub fn predefined_path(&self) -> Vec<Coords> {
self.path.clone()
}
pub fn compute_predefined_path(&mut self, terminal_size: Size, sprite_size: Size) {
self.path = match self.direction {
Direction::Linear => {
let path = bresenham_path(
self.start.resolve(terminal_size, sprite_size),
self.end.resolve(terminal_size, sprite_size),
);
self.add_speed(path)
}
Direction::Relative => {
vec![]
}
Direction::Stationary => {
let start_coord = self.start.resolve(terminal_size, sprite_size);
let mut path = vec![start_coord];
if self.ttl > 0 {
path = stationary_path(start_coord, self.ttl);
}
path
}
Direction::Circular => {
let path = arc_path(
self.start.resolve(terminal_size, sprite_size),
self.end.resolve(terminal_size, sprite_size),
self.radius as i32,
);
self.add_speed(path)
}
_ => vec![],
};
}
fn add_speed(&self, path: Vec<Coords>) -> Vec<Coords> {
let mut extended_path = Vec::new();
for coord in path {
for _ in 0..(self.speed as usize) {
extended_path.push(coord);
}
}
extended_path
}
pub fn add_offset(&mut self, coord: Coords) {
self.offset = self.offset + coord;
}
pub fn offset(&self) -> Coords {
self.offset
}
pub fn advance(&mut self, tick_id: usize, terminal_size: Size, sprite_size: Size) {
if !self.has_started() {
self.started_tick_id = Some(tick_id);
}
self.current_coordinate_id += 1;
if self.ttl > 0 && tick_id >= self.ttl {
self.is_done = true;
}
if self.direction() == Direction::Relative {
let coords = self.get_coordinate(tick_id);
if coords.x() >= terminal_size.width() as i32
|| coords.y() >= terminal_size.height() as i32
{
self.is_done = true;
} else if coords.x() <= 0 - sprite_size.width() as i32
|| coords.y() <= 0 - sprite_size.height() as i32
{
self.is_done = true;
}
}
if self.direction() == Direction::Linear {
let start_tick = self.started_tick_id.unwrap();
self.is_done = tick_id - start_tick >= self.path.len();
}
if self.direction() == Direction::Circular {
let start_tick = self.started_tick_id.unwrap();
self.is_done = tick_id - start_tick >= self.path.len();
}
}
}
fn stationary_path(position: Coords, ttl: usize) -> Vec<Coords> {
let mut points = Vec::new();
let total_nb_frame = ttl;
for _ in 0..total_nb_frame {
points.push(position)
}
points
}
pub fn arc_path(start: Coords, end: Coords, radius: i32) -> Vec<Coords> {
let mut path = Vec::new();
let dx = end.x() - start.x();
let dy = end.y() - start.y();
let dz = end.z() - start.z();
let steps = dz.abs().max(dx.abs()).max(dy.abs()).max(1);
for i in 0..=steps {
let t = i as f32 / steps as f32;
let curve = 4.0 * t * (1.0 - t);
let x = start.x() as f32 + dx as f32 * t;
let y = start.y() as f32 + dy as f32 * t + curve * radius as f32;
let z = start.z() as f32 + dz as f32 * t;
let coord = Coords::new(x.round() as i32, y.round() as i32, z.round() as i32);
if path.last().copied() != Some(coord) {
path.push(coord);
}
}
path
}
fn bresenham_path(start: Coords, end: Coords) -> Vec<Coords> {
let mut points = Vec::new();
let delta_x = (end.x() - start.x()).abs();
let delta_y = (end.y() - start.y()).abs();
let delta_z = (end.z() - start.z()).abs();
let step_x = if start.x() < end.x() { 1 } else { -1 };
let step_y = if start.y() < end.y() { 1 } else { -1 };
let step_z = if start.z() < end.z() { 1 } else { -1 };
let mut current = start;
let max_delta = delta_x.max(delta_y).max(delta_z);
if max_delta == 0 {
points.push(current);
return points;
}
let mut error_x = max_delta / 2;
let mut error_y = max_delta / 2;
let mut error_z = max_delta / 2;
for _ in 0..=max_delta {
points.push(current);
if current.x() == end.x() && current.y() == end.y() && current.z() == end.z() {
break;
}
error_x += delta_x;
if error_x >= max_delta {
error_x -= max_delta;
current.set_x(current.x() + step_x);
}
error_y += delta_y;
if error_y >= max_delta {
error_y -= max_delta;
current.set_y(current.y() + step_y);
}
error_z += delta_z;
if error_z >= max_delta {
error_z -= max_delta;
current.set_z(current.z() + step_z);
}
}
points
}