Initial commit. Darkness plugin with basic lighting.
commit
2bc44d694d
|
|
@ -0,0 +1,3 @@
|
||||||
|
/target
|
||||||
|
/graphs
|
||||||
|
/assets/levels/*/backups/
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "moonlit"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bevy = "0.11.1"
|
||||||
|
bevy-inspector-egui = "0.19.0"
|
||||||
|
bevy_ecs_ldtk = { version = "0.8.0", features = ["derive", "atlas"] }
|
||||||
|
bevy_mod_debugdump = "0.8.0"
|
||||||
|
bevy_prototype_debug_lines = "0.11.1"
|
||||||
|
bevy_rapier2d = "0.22.0"
|
||||||
|
num-traits = "0.2.16"
|
||||||
|
|
||||||
|
# Enable a small amount of optimization in debug mode
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,88 @@
|
||||||
|
#import bevy_pbr::mesh_vertex_output MeshVertexOutput
|
||||||
|
#import bevy_sprite::mesh2d_view_bindings globals
|
||||||
|
|
||||||
|
struct DarknessMaterial {
|
||||||
|
color: vec4<f32>,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PointLight {
|
||||||
|
position: vec2<f32>,
|
||||||
|
radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SpotLight {
|
||||||
|
position: vec2<f32>,
|
||||||
|
radius: f32,
|
||||||
|
rotation: f32,
|
||||||
|
angle: f32,
|
||||||
|
padding1: u32,
|
||||||
|
padding2: u32,
|
||||||
|
padding3: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> material: DarknessMaterial;
|
||||||
|
|
||||||
|
@group(1) @binding(1)
|
||||||
|
var<uniform> point_light_count: i32;
|
||||||
|
|
||||||
|
@group(1) @binding(2)
|
||||||
|
var<uniform> point_lights: array<PointLight, 64>;
|
||||||
|
|
||||||
|
@group(1) @binding(3)
|
||||||
|
var<uniform> spot_light_count: i32;
|
||||||
|
|
||||||
|
@group(1) @binding(4)
|
||||||
|
var<uniform> spot_lights: array<SpotLight, 64>;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fragment(
|
||||||
|
mesh: MeshVertexOutput,
|
||||||
|
) -> @location(0) vec4<f32> {
|
||||||
|
let color = material.color;
|
||||||
|
let pos = mesh.world_position.xy;
|
||||||
|
let t = globals.time;
|
||||||
|
let light_edge = 6.0;
|
||||||
|
let light_edge_mult = 0.8;
|
||||||
|
|
||||||
|
var bright_light = 0.0;
|
||||||
|
var dim_light = 0.0;
|
||||||
|
|
||||||
|
for (var i: i32 = 0; i < point_light_count; i++) {
|
||||||
|
if (point_lights[i].radius <= 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let radius = point_lights[i].radius + sin(t * 5.0) * 0.2;
|
||||||
|
|
||||||
|
let dist = distance(pos, point_lights[i].position);
|
||||||
|
let edge_treshold = radius * light_edge_mult - light_edge;
|
||||||
|
let edge_dist = max(dist - edge_treshold, 0.0);
|
||||||
|
bright_light = bright_light + step(dist, edge_treshold);
|
||||||
|
bright_light = bright_light + 1.0 / edge_dist;
|
||||||
|
dim_light = dim_light + step(dist, radius);
|
||||||
|
dim_light = dim_light + 1.0 / max(dist - radius, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i: i32 = 0; i < spot_light_count; i++) {
|
||||||
|
if (spot_lights[i].radius <= 0.0 || spot_lights[i].angle <= 0.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let spot = spot_lights[i];
|
||||||
|
let radius = spot.radius + sin(t * 5.0) * 0.2;
|
||||||
|
let diff = pos - spot.position;
|
||||||
|
let dist = length(diff);
|
||||||
|
let spot_dir = vec2<f32>(cos(spot.rotation), sin(spot.rotation));
|
||||||
|
let angle_diff = acos(dot(spot_dir, normalize(diff)));
|
||||||
|
|
||||||
|
let edge_treshold = radius * light_edge_mult - light_edge;
|
||||||
|
let edge_dist = max(dist - edge_treshold, 0.0);
|
||||||
|
let angle_mult = step(angle_diff, spot.angle / 2.0);
|
||||||
|
bright_light = bright_light + step(dist, edge_treshold) * angle_mult;
|
||||||
|
dim_light = dim_light + step(dist, radius) * angle_mult;
|
||||||
|
}
|
||||||
|
|
||||||
|
let edge1 = step(bright_light, 0.75);
|
||||||
|
let edge2 = step(dim_light, 0.75) * 0.5 + 0.5;
|
||||||
|
|
||||||
|
return vec4(color.rgb, color.a * edge1 * (edge2 * 0.5 + 0.5));
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 321 B |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 429 B |
|
|
@ -0,0 +1,47 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_prototype_debug_lines::DebugLinesPlugin;
|
||||||
|
|
||||||
|
pub struct DebugPlugin;
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
|
pub struct DebugSet;
|
||||||
|
|
||||||
|
impl Plugin for DebugPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.configure_set(Last, DebugSet.run_if(is_debug_enabled));
|
||||||
|
|
||||||
|
app.insert_resource(DebugMode::off())
|
||||||
|
.add_plugins((
|
||||||
|
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
||||||
|
bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(),
|
||||||
|
))
|
||||||
|
.add_plugins(DebugLinesPlugin::default())
|
||||||
|
.add_systems(Update, debug_toggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Resource, Default)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct DebugMode {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugMode {
|
||||||
|
pub fn on() -> Self {
|
||||||
|
Self { enabled: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn off() -> Self {
|
||||||
|
Self { enabled: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_debug_enabled(debug_mode: Res<DebugMode>) -> bool {
|
||||||
|
debug_mode.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_toggle(input: Res<Input<KeyCode>>, mut debug_mode: ResMut<DebugMode>) {
|
||||||
|
if input.just_pressed(KeyCode::P) {
|
||||||
|
debug_mode.enabled = !debug_mode.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::{debug, game_setup};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_ecs_ldtk::{LdtkWorldBundle, LevelSelection};
|
||||||
|
|
||||||
|
use self::darkness::SpotLight2D;
|
||||||
|
|
||||||
|
pub mod camera;
|
||||||
|
pub mod darkness;
|
||||||
|
pub mod ldtk;
|
||||||
|
|
||||||
|
pub fn init(app: &mut App) {
|
||||||
|
app.insert_resource(LevelSelection::Index(0))
|
||||||
|
.add_plugins((
|
||||||
|
game_setup::GameSetupPlugin,
|
||||||
|
bevy_rapier2d::prelude::RapierPhysicsPlugin::<bevy_rapier2d::prelude::NoUserData>::default(),
|
||||||
|
ldtk::LdtkHelperPlugin,
|
||||||
|
darkness::DarknessPlugin,
|
||||||
|
camera::GameCameraPlugin,
|
||||||
|
debug::DebugPlugin,
|
||||||
|
))
|
||||||
|
.add_systems(Startup, setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
|
||||||
|
commands.spawn(LdtkWorldBundle {
|
||||||
|
ldtk_handle: assets.load("levels/world.ldtk"),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.spawn((
|
||||||
|
Name::new("Spot light"),
|
||||||
|
SpatialBundle::from_transform(Transform::from_xyz(32.0, 31.0, 0.0).with_rotation(
|
||||||
|
Quat::from_euler(EulerRot::YXZ, 0.0, 0.0, f32::to_radians(-90.0)),
|
||||||
|
)),
|
||||||
|
SpotLight2D {
|
||||||
|
radius: 100.0,
|
||||||
|
angle: 1.0,
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,246 @@
|
||||||
|
use crate::util::{move_towards_vec3, vec3_lerp};
|
||||||
|
use bevy::input::mouse::{MouseScrollUnit, MouseWheel};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::camera::ScalingMode;
|
||||||
|
use bevy::{input::mouse::MouseMotion, transform::TransformSystem};
|
||||||
|
use bevy_ecs_ldtk::prelude::*;
|
||||||
|
|
||||||
|
use super::darkness::{PointLight2D, SpotLight2D};
|
||||||
|
|
||||||
|
pub struct GameCameraPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameCameraPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.configure_set(
|
||||||
|
PostUpdate,
|
||||||
|
CameraSystem.before(TransformSystem::TransformPropagate),
|
||||||
|
)
|
||||||
|
.register_type::<CameraFollow>()
|
||||||
|
.register_type::<GameCamera>()
|
||||||
|
.add_systems(Startup, camera_setup)
|
||||||
|
.add_systems(Update, control_light)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
(follow_detach_system, follow_system, free_system).in_set(CameraSystem),
|
||||||
|
)
|
||||||
|
.add_systems(
|
||||||
|
PostUpdate,
|
||||||
|
room_restraint
|
||||||
|
.after(CameraSystem)
|
||||||
|
.before(TransformSystem::TransformPropagate),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, SystemSet)]
|
||||||
|
pub struct CameraSystem;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Reflect)]
|
||||||
|
pub enum FollowMovement {
|
||||||
|
Instant,
|
||||||
|
Linear(f32),
|
||||||
|
Smooth(f32),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FollowMovement {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Instant
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Reflect, PartialEq)]
|
||||||
|
pub enum CameraMode {
|
||||||
|
#[default]
|
||||||
|
Free,
|
||||||
|
Follow,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct GameCamera {
|
||||||
|
pub mode: CameraMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct CameraFollow {
|
||||||
|
pub target: Option<Entity>,
|
||||||
|
pub movement: FollowMovement,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Component)]
|
||||||
|
pub struct CameraRoomRestraint;
|
||||||
|
|
||||||
|
fn camera_setup(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
Name::new("Game Camera"),
|
||||||
|
Camera2dBundle {
|
||||||
|
projection: OrthographicProjection {
|
||||||
|
scaling_mode: ScalingMode::AutoMax {
|
||||||
|
max_width: 256.0,
|
||||||
|
max_height: 160.0,
|
||||||
|
},
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
camera_2d: Camera2d {
|
||||||
|
clear_color: bevy::core_pipeline::clear_color::ClearColorConfig::Custom(
|
||||||
|
Color::rgb(0.0, 0.0, 0.0),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
transform: Transform::from_xyz(0.0, 0.0, 999.9),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
GameCamera::default(),
|
||||||
|
Visibility::default(),
|
||||||
|
ComputedVisibility::default(),
|
||||||
|
CameraRoomRestraint,
|
||||||
|
PointLight2D { radius: 30.0 },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn follow_detach_system(
|
||||||
|
mouse_input: Res<Input<MouseButton>>,
|
||||||
|
mut mouse_events: EventReader<MouseMotion>,
|
||||||
|
mut camera_query: Query<&mut GameCamera>,
|
||||||
|
) {
|
||||||
|
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
|
||||||
|
for mut camera in camera_query.iter_mut() {
|
||||||
|
if camera.mode != CameraMode::Free
|
||||||
|
&& mouse_input.pressed(MouseButton::Middle)
|
||||||
|
&& raw_mouse_motion != Vec2::ZERO
|
||||||
|
{
|
||||||
|
camera.mode = CameraMode::Free
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn free_system(
|
||||||
|
mut free_query: Query<(&mut Transform, &GameCamera, &OrthographicProjection)>,
|
||||||
|
mut mouse_events: EventReader<MouseMotion>,
|
||||||
|
mouse_input: Res<Input<MouseButton>>,
|
||||||
|
) {
|
||||||
|
let raw_mouse_motion: Vec2 = mouse_events.iter().map(|e| e.delta).sum();
|
||||||
|
for (mut transform, camera, projection) in free_query.iter_mut() {
|
||||||
|
if camera.mode == CameraMode::Free {
|
||||||
|
let mouse_motion = raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
||||||
|
if mouse_input.pressed(MouseButton::Middle) {
|
||||||
|
input_movement(&mut transform, mouse_motion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn follow_system(
|
||||||
|
time: Res<Time>,
|
||||||
|
follow_query: Query<(Entity, &CameraFollow, Option<&GameCamera>)>,
|
||||||
|
mut transform_query: Query<&mut Transform>,
|
||||||
|
) {
|
||||||
|
for (entity, follow, camera) in follow_query.iter() {
|
||||||
|
if camera.map_or(true, |cam| cam.mode == CameraMode::Follow) {
|
||||||
|
if let Some(target_entity) = follow.target {
|
||||||
|
let target = transform_query
|
||||||
|
.get(target_entity)
|
||||||
|
.ok()
|
||||||
|
.map_or(Vec3::ZERO, |transform| transform.translation);
|
||||||
|
let target = Vec3 { z: 999.9, ..target };
|
||||||
|
|
||||||
|
if let Ok(mut camera_transform) = transform_query.get_mut(entity) {
|
||||||
|
follow_movement(
|
||||||
|
&mut camera_transform,
|
||||||
|
&follow.movement,
|
||||||
|
target,
|
||||||
|
time.delta_seconds(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn room_restraint(
|
||||||
|
mut camera_query: Query<(&mut Transform, &OrthographicProjection), With<CameraRoomRestraint>>,
|
||||||
|
level_query: Query<(&GlobalTransform, &Handle<LdtkLevel>), Without<OrthographicProjection>>,
|
||||||
|
level_selection: Res<LevelSelection>,
|
||||||
|
ldtk_levels: Res<Assets<LdtkLevel>>,
|
||||||
|
) {
|
||||||
|
for (mut camera_transform, projection) in camera_query.iter_mut() {
|
||||||
|
for (level_transform, level_handle) in &level_query {
|
||||||
|
if let Some(ldtk_level) = ldtk_levels.get(level_handle) {
|
||||||
|
let level = &ldtk_level.level;
|
||||||
|
if level_selection.is_match(&0, level) {
|
||||||
|
let top = camera_transform.translation.y + projection.area.max.y;
|
||||||
|
let bottom = camera_transform.translation.y + projection.area.min.y;
|
||||||
|
let left = camera_transform.translation.x + projection.area.min.x;
|
||||||
|
let right = camera_transform.translation.x + projection.area.max.x;
|
||||||
|
|
||||||
|
let top_limit = level_transform.translation().y + level.px_hei as f32;
|
||||||
|
let bottom_limit = level_transform.translation().y;
|
||||||
|
let left_limit = level_transform.translation().x;
|
||||||
|
let right_limit = level_transform.translation().x + level.px_wid as f32;
|
||||||
|
|
||||||
|
let top_move = (top_limit - top).min(0.0);
|
||||||
|
let bottom_move = (bottom_limit - bottom).max(0.0);
|
||||||
|
let left_move = (left_limit - left).max(0.0);
|
||||||
|
let right_move = (right_limit - right).min(0.0);
|
||||||
|
|
||||||
|
// Move camera back to room
|
||||||
|
camera_transform.translation.x += left_move + right_move;
|
||||||
|
camera_transform.translation.y += top_move + bottom_move;
|
||||||
|
|
||||||
|
// Center camera on room if room is too small
|
||||||
|
if top - bottom > level.px_hei as f32 {
|
||||||
|
camera_transform.translation.y = (top_limit + bottom_limit) / 2.0;
|
||||||
|
}
|
||||||
|
if right - left > level.px_wid as f32 {
|
||||||
|
camera_transform.translation.x = (right_limit + left_limit) / 2.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn follow_movement(
|
||||||
|
transform: &mut Transform,
|
||||||
|
movement: &FollowMovement,
|
||||||
|
target: Vec3,
|
||||||
|
delta_time: f32,
|
||||||
|
) {
|
||||||
|
match movement {
|
||||||
|
FollowMovement::Instant => {
|
||||||
|
transform.translation = target;
|
||||||
|
}
|
||||||
|
FollowMovement::Linear(speed) => {
|
||||||
|
transform.translation =
|
||||||
|
move_towards_vec3(transform.translation, target, speed * delta_time);
|
||||||
|
}
|
||||||
|
FollowMovement::Smooth(speed) => {
|
||||||
|
transform.translation =
|
||||||
|
vec3_lerp(transform.translation, target, (speed * delta_time).min(1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input_movement(transform: &mut Transform, movement: Vec2) {
|
||||||
|
transform.translation += movement.extend(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_light(
|
||||||
|
mut mouse_scroll_events: EventReader<MouseWheel>,
|
||||||
|
mut point_light_query: Query<&mut PointLight2D>,
|
||||||
|
mut spot_light_query: Query<&mut SpotLight2D>,
|
||||||
|
) {
|
||||||
|
let mut scrolling = 0.0;
|
||||||
|
for event in mouse_scroll_events.iter() {
|
||||||
|
match event.unit {
|
||||||
|
MouseScrollUnit::Line => scrolling += event.y * 0.05,
|
||||||
|
MouseScrollUnit::Pixel => scrolling += event.y * 0.025,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for mut point_light in point_light_query.iter_mut() {
|
||||||
|
point_light.radius *= 1.0 + scrolling;
|
||||||
|
}
|
||||||
|
for mut spot_light in spot_light_query.iter_mut() {
|
||||||
|
spot_light.radius *= 1.0 + scrolling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
use bevy::{
|
||||||
|
prelude::*,
|
||||||
|
render::{mesh::Indices, render_resource::ShaderType},
|
||||||
|
sprite::{Material2dPlugin, Mesh2dHandle},
|
||||||
|
};
|
||||||
|
use bevy_ecs_ldtk::{LdtkLevel, LevelEvent};
|
||||||
|
|
||||||
|
mod material;
|
||||||
|
|
||||||
|
pub use material::*;
|
||||||
|
|
||||||
|
// Needs to be the same as in darkness.wgsl
|
||||||
|
pub const MAX_POINT_LIGHTS: usize = 64;
|
||||||
|
pub const MAX_SPOT_LIGHTS: usize = 64;
|
||||||
|
|
||||||
|
pub struct DarknessPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DarknessPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<PointLight2D>()
|
||||||
|
.register_type::<SpotLight2D>()
|
||||||
|
.register_asset_reflect::<DarknessMaterial>()
|
||||||
|
.add_plugins(Material2dPlugin::<DarknessMaterial>::default())
|
||||||
|
.add_systems(Update, add_to_level)
|
||||||
|
.add_systems(Last, prepare_lights);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct PointLight2D {
|
||||||
|
pub radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Reflect, ShaderType)]
|
||||||
|
pub(crate) struct GpuPointLight2D {
|
||||||
|
pub(crate) position: Vec2,
|
||||||
|
pub(crate) radius: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct SpotLight2D {
|
||||||
|
pub radius: f32,
|
||||||
|
pub angle: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, Reflect, ShaderType)]
|
||||||
|
pub(crate) struct GpuSpotLight2D {
|
||||||
|
pub(crate) position: Vec2,
|
||||||
|
pub(crate) radius: f32,
|
||||||
|
pub(crate) rotation: f32,
|
||||||
|
pub(crate) angle: f32,
|
||||||
|
padding1: u32,
|
||||||
|
padding2: u32,
|
||||||
|
padding3: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_level(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut level_events: EventReader<LevelEvent>,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<DarknessMaterial>>,
|
||||||
|
level_query: Query<(Entity, &Handle<LdtkLevel>)>,
|
||||||
|
levels: Res<Assets<LdtkLevel>>,
|
||||||
|
) {
|
||||||
|
for event in level_events.iter() {
|
||||||
|
match event {
|
||||||
|
LevelEvent::Spawned(iid) => {
|
||||||
|
for (entity, ldtk_level) in level_query
|
||||||
|
.iter()
|
||||||
|
.map(|(entity, handle)| {
|
||||||
|
(
|
||||||
|
entity,
|
||||||
|
levels.get(handle).expect(
|
||||||
|
format!("LDTK level '{}' should have been loaded", iid).as_str(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.filter(|(_, ldtk_level)| ldtk_level.level.iid == *iid)
|
||||||
|
{
|
||||||
|
let size = Vec2::new(
|
||||||
|
ldtk_level.level.px_wid as f32,
|
||||||
|
ldtk_level.level.px_hei as f32,
|
||||||
|
);
|
||||||
|
let mut plane =
|
||||||
|
Mesh::new(bevy::render::render_resource::PrimitiveTopology::TriangleStrip);
|
||||||
|
|
||||||
|
// 2 -------- 3
|
||||||
|
// | |
|
||||||
|
// | |
|
||||||
|
// 0 -------- 1
|
||||||
|
let indices = vec![0, 1, 2, 3];
|
||||||
|
let positions = vec![
|
||||||
|
[0.0, 0.0, 0.0],
|
||||||
|
[size.x, 0.0, 0.0],
|
||||||
|
[0.0, size.y, 0.0],
|
||||||
|
[size.x, size.y, 0.0],
|
||||||
|
];
|
||||||
|
let normals = vec![
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
[0.0, 0.0, 1.0],
|
||||||
|
];
|
||||||
|
let uvs = vec![[0.0, 1.0], [1.0, 1.0], [0.0, 0.0], [1.0, 0.0]];
|
||||||
|
|
||||||
|
plane.set_indices(Some(Indices::U32(indices)));
|
||||||
|
plane.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
||||||
|
plane.insert_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
|
||||||
|
plane.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
||||||
|
|
||||||
|
commands.entity(entity).with_children(|builder| {
|
||||||
|
builder.spawn((
|
||||||
|
Name::new("Darkness plane"),
|
||||||
|
DarknessMeshBundle {
|
||||||
|
transform: Transform::from_xyz(0.0, 0.0, 100.0),
|
||||||
|
mesh: Mesh2dHandle(meshes.add(plane)),
|
||||||
|
material: materials.add(DarknessMaterial::default()),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare_lights(
|
||||||
|
mut materials: ResMut<Assets<DarknessMaterial>>,
|
||||||
|
material_query: Query<&Handle<DarknessMaterial>>,
|
||||||
|
point_light_query: Query<(&GlobalTransform, &PointLight2D)>,
|
||||||
|
spot_light_query: Query<(&GlobalTransform, &SpotLight2D)>,
|
||||||
|
) {
|
||||||
|
let point_lights: Vec<(_, _)> = point_light_query.iter().collect();
|
||||||
|
let spot_lights: Vec<(_, _)> = spot_light_query.iter().collect();
|
||||||
|
for handle in &material_query {
|
||||||
|
let material = match materials.get_mut(handle) {
|
||||||
|
Some(material) => material,
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
material.point_light_count = point_lights.len() as i32;
|
||||||
|
point_lights
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, (transform, light))| {
|
||||||
|
material.point_lights[i] = GpuPointLight2D {
|
||||||
|
position: transform.translation().truncate(),
|
||||||
|
radius: light.radius,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
material.spot_light_count = spot_lights.len() as i32;
|
||||||
|
spot_lights
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, (transform, light))| {
|
||||||
|
material.spot_lights[i] = GpuSpotLight2D {
|
||||||
|
position: transform.translation().truncate(),
|
||||||
|
radius: light.radius,
|
||||||
|
rotation: f32::atan2(transform.right().y, transform.right().x),
|
||||||
|
angle: light.angle,
|
||||||
|
padding1: 0,
|
||||||
|
padding2: 0,
|
||||||
|
padding3: 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
use bevy::{
|
||||||
|
prelude::*,
|
||||||
|
reflect::TypeUuid,
|
||||||
|
render::render_resource::{AsBindGroup, ShaderRef},
|
||||||
|
sprite::{Material2d, MaterialMesh2dBundle},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub type DarknessMeshBundle = MaterialMesh2dBundle<DarknessMaterial>;
|
||||||
|
|
||||||
|
#[derive(AsBindGroup, Reflect, Debug, Clone, TypeUuid)]
|
||||||
|
#[reflect(Default, Debug)]
|
||||||
|
#[uuid = "7229bcd8-c4f8-4067-a4c2-98b04e203fd5"]
|
||||||
|
pub struct DarknessMaterial {
|
||||||
|
#[uniform(0)]
|
||||||
|
pub color: Color,
|
||||||
|
#[uniform(1)]
|
||||||
|
pub(crate) point_light_count: i32,
|
||||||
|
#[uniform(2)]
|
||||||
|
pub(crate) point_lights: [GpuPointLight2D; MAX_POINT_LIGHTS],
|
||||||
|
#[uniform(3)]
|
||||||
|
pub(crate) spot_light_count: i32,
|
||||||
|
#[uniform(4)]
|
||||||
|
pub(crate) spot_lights: [GpuSpotLight2D; MAX_SPOT_LIGHTS],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DarknessMaterial {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
color: Color::Rgba {
|
||||||
|
red: 0.0,
|
||||||
|
green: 0.0,
|
||||||
|
blue: 0.0,
|
||||||
|
alpha: 0.75,
|
||||||
|
},
|
||||||
|
point_light_count: 0,
|
||||||
|
point_lights: [GpuPointLight2D::default(); MAX_POINT_LIGHTS],
|
||||||
|
spot_light_count: 0,
|
||||||
|
spot_lights: [GpuSpotLight2D::default(); MAX_SPOT_LIGHTS],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DarknessMaterial {
|
||||||
|
pub fn new(color: Color) -> Self {
|
||||||
|
Self { color, ..default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Material2d for DarknessMaterial {
|
||||||
|
fn fragment_shader() -> ShaderRef {
|
||||||
|
"shaders/darkness.wgsl".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,305 @@
|
||||||
|
use bevy::ecs::prelude::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::utils::HashMap;
|
||||||
|
use bevy_ecs_ldtk::prelude::*;
|
||||||
|
use bevy_rapier2d::prelude::*;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::util::Vector2I;
|
||||||
|
|
||||||
|
pub struct LdtkHelperPlugin;
|
||||||
|
|
||||||
|
impl From<IVec2> for Vector2I {
|
||||||
|
fn from(value: IVec2) -> Self {
|
||||||
|
Self {
|
||||||
|
x: value.x,
|
||||||
|
y: value.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Plugin for LdtkHelperPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.add_plugins(LdtkPlugin)
|
||||||
|
.insert_resource(LdtkSettings {
|
||||||
|
level_spawn_behavior: LevelSpawnBehavior::UseWorldTranslation {
|
||||||
|
load_level_neighbors: false,
|
||||||
|
},
|
||||||
|
set_clear_color: SetClearColor::FromLevelBackground,
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.add_event::<EntityInstanceAdded>()
|
||||||
|
.register_ldtk_int_cell::<WallBundle>(1)
|
||||||
|
.insert_resource(WordlyInstances::default())
|
||||||
|
.add_systems(Update, entity_instance_events)
|
||||||
|
.add_systems(PostUpdate, (entity_namer, unique_handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Event)]
|
||||||
|
pub struct EntityInstanceAdded {
|
||||||
|
pub entity: Entity,
|
||||||
|
pub instance: EntityInstance,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Default)]
|
||||||
|
pub struct WordlyInstances {
|
||||||
|
pub def_uid_map: HashMap<i32, Entity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Bundle, LdtkIntCell, Default, Clone, Debug)]
|
||||||
|
pub struct WallBundle {
|
||||||
|
wall: Wall,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default, Clone, Copy, Debug)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct Wall;
|
||||||
|
|
||||||
|
fn entity_instance_events(
|
||||||
|
query: Query<(Entity, &EntityInstance), Added<EntityInstance>>,
|
||||||
|
worldly_instances: Res<WordlyInstances>,
|
||||||
|
mut events: EventWriter<EntityInstanceAdded>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
for (entity, instance) in query.iter() {
|
||||||
|
// Spawn the entity if it's not in the unique instances list (or if the old one is deleted)
|
||||||
|
// TODO: Detect deleted entities safely: https://github.com/bevyengine/bevy/issues/3845
|
||||||
|
if worldly_instances
|
||||||
|
.def_uid_map
|
||||||
|
.get(&instance.def_uid)
|
||||||
|
.map_or(true, |ent| commands.get_entity(*ent).is_none())
|
||||||
|
{
|
||||||
|
events.send(EntityInstanceAdded {
|
||||||
|
entity,
|
||||||
|
instance: instance.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entity_namer(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut events: EventReader<EntityInstanceAdded>,
|
||||||
|
nameless_query: Query<(), Without<Name>>,
|
||||||
|
) {
|
||||||
|
for event in events.iter() {
|
||||||
|
if nameless_query.contains(event.entity) {
|
||||||
|
commands
|
||||||
|
.entity(event.entity)
|
||||||
|
.insert(Name::new(event.instance.identifier.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unique_handler(
|
||||||
|
query: Query<(Entity, &EntityInstance), Added<Worldly>>,
|
||||||
|
mut worldly_instances: ResMut<WordlyInstances>,
|
||||||
|
) {
|
||||||
|
for (entity, instance) in query.iter() {
|
||||||
|
worldly_instances
|
||||||
|
.def_uid_map
|
||||||
|
.insert(instance.def_uid, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FieldValueGetter {
|
||||||
|
fn find_string(&self, identifier: &str) -> Option<String>;
|
||||||
|
fn find_i32(&self, identifier: &str) -> Option<i32>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FieldValueGetter for EntityInstance {
|
||||||
|
fn find_string(&self, identifier: &str) -> Option<String> {
|
||||||
|
match self
|
||||||
|
.field_instances
|
||||||
|
.iter()
|
||||||
|
.find(|fi| fi.identifier.as_str() == identifier)?
|
||||||
|
.value
|
||||||
|
.clone()
|
||||||
|
{
|
||||||
|
FieldValue::String(value) => value,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_i32(&self, identifier: &str) -> Option<i32> {
|
||||||
|
match self
|
||||||
|
.field_instances
|
||||||
|
.iter()
|
||||||
|
.find(|fi| fi.identifier.as_str() == identifier)?
|
||||||
|
.value
|
||||||
|
{
|
||||||
|
FieldValue::Int(value) => value,
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wall collider system from bevy_ecs_ldtk example.
|
||||||
|
///
|
||||||
|
/// Spawns colliders for the walls of a level
|
||||||
|
///
|
||||||
|
/// You could just insert a ColliderBundle in to the WallBundle,
|
||||||
|
/// but this spawns a different collider for EVERY wall tile.
|
||||||
|
/// This approach leads to bad performance.
|
||||||
|
///
|
||||||
|
/// Instead, by flagging the wall tiles and spawning the collisions later,
|
||||||
|
/// we can minimize the amount of colliding entities.
|
||||||
|
///
|
||||||
|
/// The algorithm used here is a nice compromise between simplicity, speed,
|
||||||
|
/// and a small number of rectangle colliders.
|
||||||
|
/// In basic terms, it will:
|
||||||
|
/// 1. consider where the walls are
|
||||||
|
/// 2. combine wall tiles into flat "plates" in each individual row
|
||||||
|
/// 3. combine the plates into rectangles across multiple rows wherever possible
|
||||||
|
/// 4. spawn colliders for each rectangle
|
||||||
|
pub fn wall_setup(
|
||||||
|
mut commands: Commands,
|
||||||
|
wall_query: Query<(&GridCoords, &Parent), Added<Wall>>,
|
||||||
|
parent_query: Query<&Parent, Without<Wall>>,
|
||||||
|
level_query: Query<(Entity, &Handle<LdtkLevel>)>,
|
||||||
|
levels: Res<Assets<LdtkLevel>>,
|
||||||
|
) {
|
||||||
|
/// Represents a wide wall that is 1 tile tall
|
||||||
|
/// Used to spawn wall collisions
|
||||||
|
#[derive(Clone, Eq, PartialEq, Debug, Default, Hash)]
|
||||||
|
struct Plate {
|
||||||
|
left: i32,
|
||||||
|
right: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A simple rectangle type representing a wall of any size
|
||||||
|
struct Rect {
|
||||||
|
left: i32,
|
||||||
|
right: i32,
|
||||||
|
top: i32,
|
||||||
|
bottom: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consider where the walls are
|
||||||
|
// storing them as GridCoords in a HashSet for quick, easy lookup
|
||||||
|
//
|
||||||
|
// The key of this map will be the entity of the level the wall belongs to.
|
||||||
|
// This has two consequences in the resulting collision entities:
|
||||||
|
// 1. it forces the walls to be split along level boundaries
|
||||||
|
// 2. it lets us easily add the collision entities as children of the appropriate level entity
|
||||||
|
let mut level_to_wall_locations: HashMap<Entity, HashSet<GridCoords>> = HashMap::new();
|
||||||
|
|
||||||
|
wall_query.for_each(|(&grid_coords, parent)| {
|
||||||
|
// An intgrid tile's direct parent will be a layer entity, not the level entity
|
||||||
|
// To get the level entity, you need the tile's grandparent.
|
||||||
|
// This is where parent_query comes in.
|
||||||
|
if let Ok(grandparent) = parent_query.get(parent.get()) {
|
||||||
|
level_to_wall_locations
|
||||||
|
.entry(grandparent.get())
|
||||||
|
.or_default()
|
||||||
|
.insert(grid_coords);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if !wall_query.is_empty() {
|
||||||
|
level_query.for_each(|(level_entity, level_handle)| {
|
||||||
|
if let Some(level_walls) = level_to_wall_locations.get(&level_entity) {
|
||||||
|
let level = levels
|
||||||
|
.get(level_handle)
|
||||||
|
.expect("Level should be loaded by this point");
|
||||||
|
|
||||||
|
let LayerInstance {
|
||||||
|
c_wid: width,
|
||||||
|
c_hei: height,
|
||||||
|
grid_size,
|
||||||
|
..
|
||||||
|
} = level
|
||||||
|
.level
|
||||||
|
.layer_instances
|
||||||
|
.clone()
|
||||||
|
.expect("Level asset should have layers")[0];
|
||||||
|
|
||||||
|
// combine wall tiles into flat "plates" in each individual row
|
||||||
|
let mut plate_stack: Vec<Vec<Plate>> = Vec::new();
|
||||||
|
|
||||||
|
for y in 0..height {
|
||||||
|
let mut row_plates: Vec<Plate> = Vec::new();
|
||||||
|
let mut plate_start = None;
|
||||||
|
|
||||||
|
// + 1 to the width so the algorithm "terminates" plates that touch the right edge
|
||||||
|
for x in 0..width + 1 {
|
||||||
|
match (plate_start, level_walls.contains(&GridCoords { x, y })) {
|
||||||
|
(Some(s), false) => {
|
||||||
|
row_plates.push(Plate {
|
||||||
|
left: s,
|
||||||
|
right: x - 1,
|
||||||
|
});
|
||||||
|
plate_start = None;
|
||||||
|
}
|
||||||
|
(None, true) => plate_start = Some(x),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plate_stack.push(row_plates);
|
||||||
|
}
|
||||||
|
|
||||||
|
// combine "plates" into rectangles across multiple rows
|
||||||
|
let mut rect_builder: HashMap<Plate, Rect> = HashMap::new();
|
||||||
|
let mut prev_row: Vec<Plate> = Vec::new();
|
||||||
|
let mut wall_rects: Vec<Rect> = Vec::new();
|
||||||
|
|
||||||
|
// an extra empty row so the algorithm "finishes" the rects that touch the top edge
|
||||||
|
plate_stack.push(Vec::new());
|
||||||
|
|
||||||
|
for (y, current_row) in plate_stack.into_iter().enumerate() {
|
||||||
|
for prev_plate in &prev_row {
|
||||||
|
if !current_row.contains(prev_plate) {
|
||||||
|
// remove the finished rect so that the same plate in the future starts a new rect
|
||||||
|
if let Some(rect) = rect_builder.remove(prev_plate) {
|
||||||
|
wall_rects.push(rect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for plate in ¤t_row {
|
||||||
|
rect_builder
|
||||||
|
.entry(plate.clone())
|
||||||
|
.and_modify(|e| e.top += 1)
|
||||||
|
.or_insert(Rect {
|
||||||
|
bottom: y as i32,
|
||||||
|
top: y as i32,
|
||||||
|
left: plate.left,
|
||||||
|
right: plate.right,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
prev_row = current_row;
|
||||||
|
}
|
||||||
|
|
||||||
|
commands.entity(level_entity).with_children(|level| {
|
||||||
|
// Spawn colliders for every rectangle..
|
||||||
|
// Making the collider a child of the level serves two purposes:
|
||||||
|
// 1. Adjusts the transforms to be relative to the level for free
|
||||||
|
// 2. the colliders will be despawned automatically when levels unload
|
||||||
|
for wall_rect in wall_rects {
|
||||||
|
level
|
||||||
|
.spawn_empty()
|
||||||
|
.insert(Collider::cuboid(
|
||||||
|
(wall_rect.right as f32 - wall_rect.left as f32 + 1.)
|
||||||
|
* grid_size as f32
|
||||||
|
/ 2.,
|
||||||
|
(wall_rect.top as f32 - wall_rect.bottom as f32 + 1.)
|
||||||
|
* grid_size as f32
|
||||||
|
/ 2.,
|
||||||
|
))
|
||||||
|
.insert(RigidBody::Fixed)
|
||||||
|
.insert(Friction::new(1.0))
|
||||||
|
.insert(Transform::from_xyz(
|
||||||
|
(wall_rect.left + wall_rect.right + 1) as f32 * grid_size as f32
|
||||||
|
/ 2.,
|
||||||
|
(wall_rect.bottom + wall_rect.top + 1) as f32 * grid_size as f32
|
||||||
|
/ 2.,
|
||||||
|
0.,
|
||||||
|
))
|
||||||
|
.insert(GlobalTransform::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
use bevy::{prelude::*, window::WindowResolution};
|
||||||
|
|
||||||
|
pub struct GameSetupPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameSetupPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(ClearColor(Color::BLACK)).add_plugins(
|
||||||
|
DefaultPlugins
|
||||||
|
.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
resolution: WindowResolution::new(512.0 * 2.0, 320.0 * 2.0),
|
||||||
|
title: "Moonlit".to_string(),
|
||||||
|
resizable: false,
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.set(ImagePlugin::default_nearest()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use util::create_app_graphs;
|
||||||
|
|
||||||
|
pub mod debug;
|
||||||
|
pub mod game;
|
||||||
|
pub mod game_setup;
|
||||||
|
pub mod util;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut app = App::new();
|
||||||
|
game::init(&mut app);
|
||||||
|
create_app_graphs(&mut app);
|
||||||
|
app.run();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
use bevy::{ecs::schedule::ScheduleLabel, prelude::*};
|
||||||
|
use bevy_mod_debugdump::{render_graph, render_graph_dot, schedule_graph, schedule_graph_dot};
|
||||||
|
use core::{fmt, ops};
|
||||||
|
use std::{borrow::Borrow, fs, path::Path};
|
||||||
|
|
||||||
|
mod basis;
|
||||||
|
mod transform_f64;
|
||||||
|
mod vector2;
|
||||||
|
mod vector2_i32;
|
||||||
|
mod vector3;
|
||||||
|
mod vector3_i32;
|
||||||
|
|
||||||
|
pub use basis::*;
|
||||||
|
pub use transform_f64::*;
|
||||||
|
pub use vector2::*;
|
||||||
|
pub use vector2_i32::*;
|
||||||
|
pub use vector3::*;
|
||||||
|
pub use vector3_i32::*;
|
||||||
|
|
||||||
|
pub trait VectorComponent:
|
||||||
|
Sized
|
||||||
|
+ Copy
|
||||||
|
+ PartialOrd
|
||||||
|
+ Reflect
|
||||||
|
+ fmt::Display
|
||||||
|
+ ops::Add<Output = Self>
|
||||||
|
+ ops::Neg<Output = Self>
|
||||||
|
+ ops::Sub<Output = Self>
|
||||||
|
+ ops::Mul<Output = Self>
|
||||||
|
+ ops::Div<Output = Self>
|
||||||
|
+ num_traits::identities::Zero
|
||||||
|
+ num_traits::identities::One
|
||||||
|
+ num_traits::sign::Signed
|
||||||
|
{
|
||||||
|
fn min(self, b: Self) -> Self;
|
||||||
|
fn max(self, b: Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> VectorComponent for T
|
||||||
|
where
|
||||||
|
T: Sized
|
||||||
|
+ Copy
|
||||||
|
+ PartialOrd
|
||||||
|
+ Reflect
|
||||||
|
+ fmt::Display
|
||||||
|
+ ops::Neg<Output = T>
|
||||||
|
+ ops::Add<Output = T>
|
||||||
|
+ ops::Sub<Output = T>
|
||||||
|
+ ops::Mul<Output = T>
|
||||||
|
+ ops::Div<Output = T>
|
||||||
|
+ num_traits::identities::Zero
|
||||||
|
+ num_traits::identities::One
|
||||||
|
+ num_traits::sign::Signed,
|
||||||
|
{
|
||||||
|
fn min(self, b: Self) -> Self {
|
||||||
|
if self < b {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(self, b: Self) -> Self {
|
||||||
|
if self > b {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerp<
|
||||||
|
T: Copy
|
||||||
|
+ ops::Add<Output = T>
|
||||||
|
+ ops::Sub<Output = T>
|
||||||
|
+ ops::Mul<Output = T>
|
||||||
|
+ num_traits::identities::One,
|
||||||
|
>(
|
||||||
|
a: T,
|
||||||
|
b: T,
|
||||||
|
t: T,
|
||||||
|
) -> T {
|
||||||
|
a * (T::one() - t) + b * t
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse_lerp<T: Copy + ops::Sub<Output = T> + ops::Div<Output = T>>(
|
||||||
|
a: T,
|
||||||
|
b: T,
|
||||||
|
value: T,
|
||||||
|
) -> T {
|
||||||
|
(value - a) / (b - a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec2_lerp(a: Vec2, b: Vec2, t: f32) -> Vec2 {
|
||||||
|
Vec2 {
|
||||||
|
x: lerp(a.x, b.x, t),
|
||||||
|
y: lerp(a.y, b.y, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec3_lerp(a: Vec3, b: Vec3, t: f32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: lerp(a.x, b.x, t),
|
||||||
|
y: lerp(a.y, b.y, t),
|
||||||
|
z: lerp(a.z, b.z, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_towards_vec2(from: Vec2, to: Vec2, amount: f32) -> Vec2 {
|
||||||
|
let diff = to - from;
|
||||||
|
let length = diff.length();
|
||||||
|
if length <= f32::EPSILON {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
from + diff.normalize() * length.min(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_towards_vec3(from: Vec3, to: Vec3, amount: f32) -> Vec3 {
|
||||||
|
let diff = to - from;
|
||||||
|
let length = diff.length();
|
||||||
|
if length <= f32::EPSILON {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
from + diff.normalize() * length.min(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
||||||
|
let range = to - from;
|
||||||
|
if !range.is_normal() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
value - inverse_lerp(from, to, value).floor() * range
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_app_graphs(app: &mut App) {
|
||||||
|
let schedules: Vec<Box<dyn ScheduleLabel>> = app
|
||||||
|
.world
|
||||||
|
.borrow()
|
||||||
|
.resource::<Schedules>()
|
||||||
|
.iter()
|
||||||
|
.map(|(label, _)| label.dyn_clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!("Writing {} schedule graphs", schedules.len());
|
||||||
|
|
||||||
|
schedules.iter().for_each(|schedule_label| {
|
||||||
|
let path = Path::new("graphs").join(format!("schedule_{schedule_label:?}.dot"));
|
||||||
|
write_schedule_graph(app, &path, &schedule_label.to_owned());
|
||||||
|
println!("\t- {}", path.to_string_lossy())
|
||||||
|
});
|
||||||
|
|
||||||
|
println!("Writing render graph");
|
||||||
|
{
|
||||||
|
let path = Path::new("graphs").join("render.dot");
|
||||||
|
write_render_graph(app, &path);
|
||||||
|
println!("\t- {}", path.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_schedule_graph(app: &mut App, path: &Path, schedule: &dyn ScheduleLabel) {
|
||||||
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
fs::write(
|
||||||
|
path,
|
||||||
|
schedule_graph_dot(
|
||||||
|
app,
|
||||||
|
schedule.dyn_clone(),
|
||||||
|
&schedule_graph::settings::Settings::default(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_render_graph(app: &mut App, path: &Path) {
|
||||||
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
fs::write(
|
||||||
|
path,
|
||||||
|
render_graph_dot(app, &render_graph::settings::Settings::default()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{Vector3, VectorComponent};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)]
|
||||||
|
pub struct Basis<T: VectorComponent> {
|
||||||
|
pub x: Vector3<T>,
|
||||||
|
pub y: Vector3<T>,
|
||||||
|
pub z: Vector3<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Default for Basis<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
x: Vector3::x(),
|
||||||
|
y: Vector3::y(),
|
||||||
|
z: Vector3::z(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Basis<T> {
|
||||||
|
pub fn identity() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x * -T::one(),
|
||||||
|
y: self.y * -T::one(),
|
||||||
|
z: self.z * -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform(&self, point: Vector3<T>) -> Vector3<T> {
|
||||||
|
self.x * point.x + self.y * point.y + self.z * point.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse_transform(&self, point: Vector3<T>) -> Vector3<T> {
|
||||||
|
self.inverse().transform(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
mod basis_f64;
|
||||||
|
mod vector3_f64;
|
||||||
|
|
||||||
|
pub use basis_f64::*;
|
||||||
|
pub use vector3_f64::*;
|
||||||
|
|
||||||
|
pub struct DoublePrecisionPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DoublePrecisionPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(WorldOffset::default())
|
||||||
|
.register_type::<TranslationD>()
|
||||||
|
.add_systems(PostUpdate, translation_system);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Reflect, Default)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct WorldOffset(pub Vector3D);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TranslationD(pub Vector3D);
|
||||||
|
|
||||||
|
fn translation_system(
|
||||||
|
mut query: Query<(&mut Transform, &TranslationD)>,
|
||||||
|
world_offset: Res<WorldOffset>,
|
||||||
|
) {
|
||||||
|
for (mut real_transform, translation) in query.iter_mut() {
|
||||||
|
real_transform.translation = Vec3::from(translation.0 - world_offset.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::util::Basis;
|
||||||
|
|
||||||
|
use super::Vector3D;
|
||||||
|
|
||||||
|
pub type BasisD = Basis<f64>;
|
||||||
|
|
||||||
|
impl BasisD {
|
||||||
|
pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Self {
|
||||||
|
let basis = Self::identity();
|
||||||
|
let basis = basis.rotate_by_axis(&basis.y, yaw);
|
||||||
|
let basis = basis.rotate_by_axis(&basis.x, pitch);
|
||||||
|
let basis = basis.rotate_by_axis(&basis.z, roll);
|
||||||
|
basis
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate_by_axis(&self, axis: &Vector3D, angle: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x.rotate_around_axis(axis, angle),
|
||||||
|
y: self.y.rotate_around_axis(axis, angle),
|
||||||
|
z: self.z.rotate_around_axis(axis, angle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::util::Vector3;
|
||||||
|
|
||||||
|
pub type Vector3D = Vector3<f64>;
|
||||||
|
|
||||||
|
impl Vector3D {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const ONE: Self = Self {
|
||||||
|
x: 1.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 1.0,
|
||||||
|
};
|
||||||
|
pub const UP: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const DOWN: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: -1.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const LEFT: Self = Self {
|
||||||
|
x: -1.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const RIGHT: Self = Self {
|
||||||
|
x: 1.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const FORWARD: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: -1.0,
|
||||||
|
};
|
||||||
|
pub const BACK: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn rotate_around_axis(&self, axis: &Self, angle: f64) -> Self {
|
||||||
|
*self * angle.cos()
|
||||||
|
+ axis.cross(self) * angle.sin()
|
||||||
|
+ *axis * axis.dot(self) * (1.0 - angle.cos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for Vector3D {
|
||||||
|
fn from(vec: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as f64,
|
||||||
|
y: vec.y as f64,
|
||||||
|
z: vec.y as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector3D> for Vec3 {
|
||||||
|
fn from(vec: Vector3D) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: vec.z as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
use bevy::reflect::Reflect;
|
||||||
|
use core::{fmt, ops};
|
||||||
|
|
||||||
|
use super::VectorComponent;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
|
||||||
|
pub struct Vector2<T: VectorComponent> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Vector2<T> {
|
||||||
|
pub fn x() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn one() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn up() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn down() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn left() -> Self {
|
||||||
|
Self {
|
||||||
|
x: -T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn right() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(x: T, y: T) -> Vector2<T> {
|
||||||
|
Vector2 { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||||
|
Vector2 {
|
||||||
|
x: VectorComponent::min(self.x, other.x),
|
||||||
|
y: VectorComponent::min(self.y, other.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||||
|
Vector2 {
|
||||||
|
x: VectorComponent::max(self.x, other.x),
|
||||||
|
y: VectorComponent::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,46 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
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<Vec2> for Vector2I {
|
||||||
|
fn from(vec: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as i32,
|
||||||
|
y: vec.y as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector2I> for Vec2 {
|
||||||
|
fn from(vec: Vector2I) -> Self {
|
||||||
|
Vec2 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector2I> for Vec3 {
|
||||||
|
fn from(vec: Vector2I) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
use bevy::reflect::Reflect;
|
||||||
|
use core::{fmt, ops};
|
||||||
|
|
||||||
|
use super::VectorComponent;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
|
||||||
|
pub struct Vector3<T: VectorComponent> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
pub z: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Vector3<T> {
|
||||||
|
pub fn x() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn z() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn one() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn up() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn down() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: -T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn left() -> Self {
|
||||||
|
Self {
|
||||||
|
x: -T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn right() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn forward() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn back() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(x: T, y: T, z: T) -> Self {
|
||||||
|
Vector3 { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(&self, other: &Self) -> Self {
|
||||||
|
Vector3 {
|
||||||
|
x: VectorComponent::min(self.x, other.x),
|
||||||
|
y: VectorComponent::min(self.y, other.y),
|
||||||
|
z: VectorComponent::min(self.z, other.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self, other: &Self) -> Self {
|
||||||
|
Vector3 {
|
||||||
|
x: VectorComponent::max(self.x, other.x),
|
||||||
|
y: VectorComponent::max(self.y, other.y),
|
||||||
|
z: VectorComponent::max(self.z, other.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dot(&self, other: &Self) -> T {
|
||||||
|
self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross(&self, other: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.y * other.z - self.z * other.y,
|
||||||
|
y: self.z * other.x - self.x * other.z,
|
||||||
|
z: self.x * other.y - self.y * other.x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> fmt::Display for Vector3<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "({}, {}, {})", self.x, self.y, self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Add<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x + rhs.x,
|
||||||
|
y: self.y + rhs.y,
|
||||||
|
z: self.z + rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Neg for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: -self.x,
|
||||||
|
y: -self.y,
|
||||||
|
z: -self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Sub<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
self + (-rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x * rhs.x,
|
||||||
|
y: self.y * rhs.y,
|
||||||
|
z: self.z * rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<T> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x * rhs,
|
||||||
|
y: self.y * rhs,
|
||||||
|
z: self.z * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: Vector3<T>) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x / rhs.x,
|
||||||
|
y: self.y / rhs.y,
|
||||||
|
z: self.z / rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<T> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x / rhs,
|
||||||
|
y: self.y / rhs,
|
||||||
|
z: self.z / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::Vector3;
|
||||||
|
|
||||||
|
pub type Vector3I = Vector3<i32>;
|
||||||
|
|
||||||
|
impl Vector3I {
|
||||||
|
pub const ZERO: Self = Self { x: 0, y: 0, z: 0 };
|
||||||
|
pub const ONE: Self = Self { x: 1, y: 1, z: 1 };
|
||||||
|
pub const UP: Self = Self { x: 0, y: 1, z: 0 };
|
||||||
|
pub const DOWN: Self = Self { x: 0, y: -1, z: 0 };
|
||||||
|
pub const LEFT: Self = Self { x: -1, y: 0, z: 0 };
|
||||||
|
pub const RIGHT: Self = Self { x: 1, y: 0, z: 0 };
|
||||||
|
pub const FORWARD: Self = Self { x: 0, y: 0, z: -1 };
|
||||||
|
pub const BACK: Self = Self { x: 0, y: 0, z: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for Vector3I {
|
||||||
|
fn from(vec: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as i32,
|
||||||
|
y: vec.y as i32,
|
||||||
|
z: vec.y as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector3I> for Vec3 {
|
||||||
|
fn from(vec: Vector3I) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: vec.z as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue