wip: started implementing terrain system
parent
954ac51bd6
commit
cf66379a23
|
|
@ -2061,6 +2061,7 @@ dependencies = [
|
|||
"bevy",
|
||||
"bevy-inspector-egui",
|
||||
"bevy_rapier2d",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ edition = "2021"
|
|||
bevy = { version = "0.9.0", features = ["dynamic"] }
|
||||
bevy-inspector-egui = "0.14.0"
|
||||
bevy_rapier2d = { path = "../bevy_rapier/bevy_rapier2d" }
|
||||
lazy_static = "1.4.0"
|
||||
|
||||
# Enable a small amount of optimization in debug mode
|
||||
[profile.dev]
|
||||
|
|
|
|||
|
|
@ -16,12 +16,12 @@ pub fn init() {
|
|||
.add_plugin(WorldInspectorPlugin::new())
|
||||
.add_plugin(KinematicPlugin)
|
||||
.add_plugin(GameCameraPlugin)
|
||||
.add_plugin(PlayerPlugin)
|
||||
.add_startup_system(setup)
|
||||
// .add_plugin(PlayerPlugin)
|
||||
// .add_startup_system(setup_debug_ground)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
fn setup_debug_ground(mut commands: Commands) {
|
||||
// Static ground
|
||||
commands
|
||||
.spawn(())
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ fn camera_setup(mut commands: Commands) {
|
|||
commands.spawn((
|
||||
Name::new("Camera"),
|
||||
Camera2dBundle {
|
||||
projection: OrthographicProjection {
|
||||
scaling_mode: ScalingMode::FixedHorizontal(320.0),
|
||||
..default()
|
||||
},
|
||||
// projection: OrthographicProjection {
|
||||
// scaling_mode: ScalingMode::FixedHorizontal(320.0),
|
||||
// ..default()
|
||||
// },
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ pub struct KinematicBundle {
|
|||
pub events: ActiveEvents,
|
||||
pub collisions: ActiveCollisionTypes,
|
||||
pub properties: KinematicProperties,
|
||||
#[bundle]
|
||||
pub transform: TransformBundle,
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +70,7 @@ impl KinematicState {
|
|||
}
|
||||
|
||||
pub fn can_jump(&self) -> bool {
|
||||
if self.on_ground && !self.did_jump {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
self.on_ground && !self.did_jump
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ pub fn player_system(
|
|||
};
|
||||
|
||||
kinematic_input.movement = movement;
|
||||
kinematic_input.want_jump = input.pressed(KeyCode::Space)
|
||||
kinematic_input.want_jump = input.just_pressed(KeyCode::Space)
|
||||
}
|
||||
|
||||
fn input_to_axis(negative: bool, positive: bool) -> f32 {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
pub mod game;
|
||||
pub mod terrain2d;
|
||||
pub mod util;
|
||||
|
||||
fn main() {
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
use box2d_rs::{
|
||||
b2_body::BodyPtr,
|
||||
b2_math::B2vec2,
|
||||
b2_world::{B2world, B2worldPtr},
|
||||
b2rs_common::UserDataType,
|
||||
};
|
||||
use unsafe_send_sync::UnsafeSendSync;
|
||||
|
||||
pub type UnsafeBox2D = UnsafeSendSync<Box2D>;
|
||||
pub type UnsafeBody = UnsafeSendSync<BodyPtr<UserData>>;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct UserData;
|
||||
impl UserDataType for UserData {
|
||||
type Body = Option<u32>;
|
||||
type Fixture = u32;
|
||||
type Joint = ();
|
||||
}
|
||||
|
||||
pub struct Box2D {
|
||||
pub gravity: B2vec2,
|
||||
pub world_ptr: B2worldPtr<UserData>,
|
||||
}
|
||||
|
||||
impl Box2D {
|
||||
pub const METERS_TO_TEXELS: f32 = 4.0;
|
||||
pub const TEXELS_TO_METERS: f32 = 1.0 / Self::METERS_TO_TEXELS;
|
||||
pub const INIT_POS: B2vec2 = B2vec2 {
|
||||
x: -1000.0,
|
||||
y: -1000.0,
|
||||
};
|
||||
|
||||
fn new() -> Box2D {
|
||||
let gravity: B2vec2 = B2vec2 { x: 0.0, y: 100.0 };
|
||||
// let gravity: B2vec2 = B2vec2 { x: 0.0, y: 1.0 };
|
||||
// let gravity: B2vec2 = B2vec2 { x: 0.0, y: 0.0 };
|
||||
let world_ptr: B2worldPtr<UserData> = B2world::new(gravity);
|
||||
|
||||
Box2D { gravity, world_ptr }
|
||||
}
|
||||
|
||||
pub fn new_unsafe() -> UnsafeBox2D {
|
||||
UnsafeBox2D::new(Self::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Box2D {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
use std::collections::{
|
||||
hash_map::{Iter, IterMut},
|
||||
HashMap,
|
||||
};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
mod chunk2d;
|
||||
mod texel2d;
|
||||
|
||||
pub use chunk2d::*;
|
||||
pub use texel2d::*;
|
||||
|
||||
use crate::util::{math::*, Vector2I};
|
||||
|
||||
pub struct Terrain2DPlugin;
|
||||
|
||||
impl Plugin for Terrain2DPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.insert_resource(Terrain2D::new())
|
||||
.add_system(emit_terrain_events);
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_terrain_events(
|
||||
mut terrain: ResMut<Terrain2D>,
|
||||
mut terrain_events: EventWriter<TerrainEvent>,
|
||||
) {
|
||||
for event in terrain.events.drain(..) {
|
||||
terrain_events.send(event)
|
||||
}
|
||||
for (chunk_index, mut chunk) in terrain.chunk_iter_mut() {
|
||||
if let Some(rect) = &chunk.dirty_rect {
|
||||
terrain_events.send(TerrainEvent::TexelsUpdated(*chunk_index, *rect));
|
||||
chunk.dirty_rect = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum TerrainEvent {
|
||||
ChunkAdded(ChunkIndex),
|
||||
ChunkRemoved(ChunkIndex),
|
||||
TexelsUpdated(ChunkIndex, ChunkRect),
|
||||
}
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
pub struct Terrain2D {
|
||||
chunk_map: HashMap<ChunkIndex, Chunk>,
|
||||
events: Vec<TerrainEvent>,
|
||||
}
|
||||
|
||||
impl Terrain2D {
|
||||
pub fn new() -> Terrain2D {
|
||||
Terrain2D {
|
||||
chunk_map: HashMap::new(),
|
||||
events: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk) {
|
||||
self.chunk_map.insert(index, chunk);
|
||||
self.events.push(TerrainEvent::ChunkAdded(index))
|
||||
}
|
||||
|
||||
pub fn remove_chunk(&mut self, index: ChunkIndex) {
|
||||
self.events.push(TerrainEvent::ChunkRemoved(index));
|
||||
self.chunk_map.remove(&index);
|
||||
}
|
||||
|
||||
pub fn chunk_iter(&self) -> Iter<ChunkIndex, Chunk> {
|
||||
self.chunk_map.iter()
|
||||
}
|
||||
|
||||
pub fn chunk_iter_mut(&mut self) -> IterMut<ChunkIndex, Chunk> {
|
||||
self.chunk_map.iter_mut()
|
||||
}
|
||||
|
||||
pub fn index_to_chunk(&self, index: &ChunkIndex) -> Option<&Chunk> {
|
||||
self.chunk_map.get(index)
|
||||
}
|
||||
|
||||
pub fn index_to_chunk_mut(&mut self, index: &ChunkIndex) -> Option<&mut Chunk> {
|
||||
self.chunk_map.get_mut(index)
|
||||
}
|
||||
|
||||
pub fn global_to_chunk(&self, global: &Vector2I) -> Option<&Chunk> {
|
||||
self.index_to_chunk(&global_to_chunk_index(global))
|
||||
}
|
||||
|
||||
pub fn global_to_chunk_mut(&mut self, global: &Vector2I) -> Option<&mut Chunk> {
|
||||
self.index_to_chunk_mut(&global_to_chunk_index(global))
|
||||
}
|
||||
|
||||
pub fn global_to_texel(&self, global: &Vector2I) -> Option<Texel> {
|
||||
match self.global_to_chunk(global) {
|
||||
Some(chunk) => chunk.get_texel(&global_to_local(global)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_to_texel_mut(&mut self, global: &Vector2I) -> Option<Texel> {
|
||||
match self.global_to_chunk(global) {
|
||||
Some(chunk) => chunk.get_texel(&global_to_local(global)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_texel(&mut self, global: &Vector2I, id: TexelID) {
|
||||
let index = global_to_chunk_index(global);
|
||||
match self.index_to_chunk_mut(&index) {
|
||||
Some(chunk) => chunk.set_texel(&global_to_local(global), id),
|
||||
None => {
|
||||
let mut chunk = Chunk::new();
|
||||
chunk.set_texel(&global_to_local(global), id);
|
||||
self.add_chunk(index, chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn local_to_texel_index(position: &Vector2I) -> Option<usize> {
|
||||
match position.x >= 0
|
||||
&& position.y >= 0
|
||||
&& position.x < Chunk::SIZE.x
|
||||
&& position.y < Chunk::SIZE.y
|
||||
{
|
||||
true => Some(position.y as usize * Chunk::SIZE_X + position.x as usize),
|
||||
false => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn texel_index_to_local(i: usize) -> Vector2I {
|
||||
Vector2I {
|
||||
x: i as i32 % Chunk::SIZE.x,
|
||||
y: i as i32 / Chunk::SIZE.y,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_to_local(position: &Vector2I) -> Vector2I {
|
||||
Vector2I {
|
||||
x: wrapping_remainder(position.x, Chunk::SIZE.x),
|
||||
y: wrapping_remainder(position.y, Chunk::SIZE.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global_to_chunk_index(position: &Vector2I) -> ChunkIndex {
|
||||
Vector2I {
|
||||
x: wrapping_quotient(position.x, Chunk::SIZE.x),
|
||||
y: wrapping_quotient(position.y, Chunk::SIZE.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_index_to_global(chunk_pos: &ChunkIndex) -> Vector2I {
|
||||
*chunk_pos * Chunk::SIZE
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
use super::{local_to_texel_index, Texel, TexelID, NEIGHBOUR_INDEX_MAP};
|
||||
use crate::util::Vector2I;
|
||||
|
||||
pub type ChunkIndex = Vector2I;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ChunkRect {
|
||||
pub min: Vector2I,
|
||||
pub max: Vector2I,
|
||||
}
|
||||
|
||||
pub struct Chunk {
|
||||
pub texels: [Texel; (Self::SIZE_X * Self::SIZE_Y) as usize],
|
||||
// TODO: handle multiple dirty rects
|
||||
pub dirty_rect: Option<ChunkRect>,
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub const SIZE_X: usize = 64;
|
||||
pub const SIZE_Y: usize = 64;
|
||||
pub const SIZE: Vector2I = Vector2I {
|
||||
x: Self::SIZE_X as i32,
|
||||
y: Self::SIZE_Y as i32,
|
||||
};
|
||||
|
||||
pub fn new() -> Chunk {
|
||||
Chunk {
|
||||
texels: Self::new_texel_array(),
|
||||
dirty_rect: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_texel_array() -> [Texel; Self::SIZE_X * Self::SIZE_Y] {
|
||||
[Texel::default(); Self::SIZE_X * Self::SIZE_Y]
|
||||
}
|
||||
|
||||
pub fn mark_all_dirty(&mut self) {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: Vector2I::ZERO,
|
||||
max: Self::SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn mark_dirty(&mut self, position: &Vector2I) {
|
||||
match &self.dirty_rect {
|
||||
Some(rect) => {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: Vector2I::min(&rect.min, position),
|
||||
max: Vector2I::max(&rect.max, position),
|
||||
})
|
||||
}
|
||||
None => {
|
||||
self.dirty_rect = Some(ChunkRect {
|
||||
min: *position,
|
||||
max: *position,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_texel(&self, position: &Vector2I) -> Option<Texel> {
|
||||
local_to_texel_index(position).map(|i| self.texels[i])
|
||||
}
|
||||
|
||||
pub fn get_texel_option_mut(&mut self, position: &Vector2I) -> Option<&mut Texel> {
|
||||
local_to_texel_index(position).map(|i| &mut self.texels[i])
|
||||
}
|
||||
|
||||
pub fn set_texel(&mut self, position: &Vector2I, id: TexelID) {
|
||||
let i = local_to_texel_index(position).expect("Texel index out of range");
|
||||
if self.texels[i].id != id {
|
||||
self.mark_dirty(position);
|
||||
}
|
||||
let update_neighbours = self.texels[i].is_empty()
|
||||
!= (Texel {
|
||||
id,
|
||||
..self.texels[i]
|
||||
})
|
||||
.is_empty();
|
||||
self.texels[i].id = id;
|
||||
// Update neighbour mask
|
||||
if update_neighbours {
|
||||
for offset in Texel::NEIGHBOUR_OFFSET_VECTORS {
|
||||
// Flip neighbour's bit
|
||||
match self.get_texel_option_mut(&(*position + offset)) {
|
||||
Some(mut neighbour) => {
|
||||
neighbour.neighbour_mask ^= 1 << NEIGHBOUR_INDEX_MAP[&-offset];
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use u8 as TexelID;
|
||||
pub use u8 as NeighbourMask;
|
||||
|
||||
use crate::util::Vector2I;
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Texel {
|
||||
pub id: TexelID,
|
||||
/// bitmask of empty/non-empty neighbours, see NEIGHBOUR_OFFSET_VECTORS for the order
|
||||
pub neighbour_mask: NeighbourMask,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
pub static ref NEIGHBOUR_INDEX_MAP: HashMap<Vector2I, u8> = {
|
||||
let mut map = HashMap::new();
|
||||
for i in 0..Texel::NEIGHBOUR_OFFSET_VECTORS.len() {
|
||||
map.insert(Texel::NEIGHBOUR_OFFSET_VECTORS[i], i as u8);
|
||||
}
|
||||
map
|
||||
};
|
||||
}
|
||||
|
||||
impl Texel {
|
||||
pub const EMPTY: TexelID = 0;
|
||||
pub const NEIGHBOUR_OFFSET_VECTORS: [Vector2I; 4] = [
|
||||
Vector2I { x: 0, y: 1 },
|
||||
Vector2I { x: 1, y: 0 },
|
||||
Vector2I { x: 0, y: -1 },
|
||||
Vector2I { x: -1, y: 0 },
|
||||
];
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.id == 0
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,12 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub mod math;
|
||||
mod vector2;
|
||||
mod vector2_i32;
|
||||
|
||||
pub use vector2::*;
|
||||
pub use vector2_i32::*;
|
||||
|
||||
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a * (1.0 - t) + b * t
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
|
||||
a * (1.0 - t) + b * t
|
||||
}
|
||||
|
||||
/// Calculate quotient, but take into account negative values so that they continue the cycle seamlessly.
|
||||
/// e.g. (0, 4) -> 0, (-4, 4) -> -1, (-5, 4) -> -2
|
||||
pub fn wrapping_quotient(dividend: i32, divisor: i32) -> i32 {
|
||||
let res = (if dividend < 0 { dividend + 1 } else { dividend }) / divisor;
|
||||
if dividend < 0 {
|
||||
res - 1
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate remainder, but take into account negative values so that they continue the cycle seamlessly.
|
||||
/// e.g. (0, 4) -> 0, (-4, 4) -> 0, (-5, 4) -> 3
|
||||
pub fn wrapping_remainder(dividend: i32, divisor: i32) -> i32 {
|
||||
let res = dividend % divisor;
|
||||
if dividend < 0 {
|
||||
(divisor + res) % divisor
|
||||
} else {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
use core::{fmt, ops};
|
||||
|
||||
pub trait VectorComponent:
|
||||
Sized
|
||||
+ Copy
|
||||
+ Ord
|
||||
+ fmt::Display
|
||||
+ ops::Add<Output = Self>
|
||||
+ ops::Neg<Output = Self>
|
||||
+ ops::Sub<Output = Self>
|
||||
+ ops::Mul<Output = Self>
|
||||
+ ops::Div<Output = Self>
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> VectorComponent for T where
|
||||
T: Sized
|
||||
+ Copy
|
||||
+ Ord
|
||||
+ fmt::Display
|
||||
+ ops::Neg<Output = T>
|
||||
+ ops::Add<Output = T>
|
||||
+ ops::Sub<Output = T>
|
||||
+ ops::Mul<Output = T>
|
||||
+ ops::Div<Output = T>
|
||||
{
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug)]
|
||||
pub struct Vector2<T: VectorComponent> {
|
||||
pub x: T,
|
||||
pub y: T,
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> Vector2<T> {
|
||||
pub fn min(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||
Vector2 {
|
||||
x: Ord::min(self.x, other.x),
|
||||
y: Ord::min(self.y, other.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||
Vector2 {
|
||||
x: Ord::max(self.x, other.x),
|
||||
y: Ord::max(self.y, other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> fmt::Display for Vector2<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "({}, {})", self.x, self.y)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Add<Vector2<T>> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn add(self, rhs: Vector2<T>) -> Self::Output {
|
||||
Vector2 {
|
||||
x: self.x + rhs.x,
|
||||
y: self.y + rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Neg for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn neg(self) -> Self::Output {
|
||||
Vector2 {
|
||||
x: -self.x,
|
||||
y: -self.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Sub<Vector2<T>> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn sub(self, rhs: Vector2<T>) -> Self::Output {
|
||||
self + (-rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Mul<Vector2<T>> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn mul(self, rhs: Vector2<T>) -> Self::Output {
|
||||
Vector2 {
|
||||
x: self.x * rhs.x,
|
||||
y: self.y * rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Mul<T> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn mul(self, rhs: T) -> Self::Output {
|
||||
Vector2 {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Div<Vector2<T>> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn div(self, rhs: Vector2<T>) -> Self::Output {
|
||||
Vector2 {
|
||||
x: self.x / rhs.x,
|
||||
y: self.y / rhs.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: VectorComponent> ops::Div<T> for Vector2<T> {
|
||||
type Output = Vector2<T>;
|
||||
fn div(self, rhs: T) -> Self::Output {
|
||||
Vector2 {
|
||||
x: self.x / rhs,
|
||||
y: self.y / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
use bevy::prelude::Vec2;
|
||||
|
||||
use super::Vector2;
|
||||
|
||||
pub type Vector2I = Vector2<i32>;
|
||||
|
||||
impl Vector2I {
|
||||
pub const ZERO: Vector2I = Vector2I { x: 0, y: 0 };
|
||||
pub const ONE: Vector2I = Vector2I { x: 1, y: 1 };
|
||||
pub const UP: Vector2I = Vector2I { x: 0, y: 1 };
|
||||
pub const DOWN: Vector2I = Vector2I { x: 0, y: -1 };
|
||||
pub const LEFT: Vector2I = Vector2I { x: -1, y: 0 };
|
||||
pub const RIGHT: Vector2I = Vector2I { x: 1, y: 0 };
|
||||
|
||||
pub fn angle(&self) -> f32 {
|
||||
(self.y as f32).atan2(self.x as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vector2I> for Vec2 {
|
||||
fn from(vec: Vector2I) -> Self {
|
||||
Vec2 {
|
||||
x: vec.x as f32,
|
||||
y: vec.y as f32,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue