Airship progress: now possessable, and physics kind of works (desyncs from the visuals by a shift + scale, and rotation isn't done at all yet, but the contours are correct).

This commit is contained in:
Avi Weinstock 2021-03-11 17:27:03 -05:00
parent 1d0600851b
commit 6d35e7c6d0
10 changed files with 359 additions and 287 deletions

View File

@ -1,16 +1,17 @@
({ ({
DefaultAirship: ( DefaultAirship: (
bone0: ( bone0: (
offset: (-20.0, -35.0, 1.0), //offset: (-20.0, -35.0, 1.0),
offset: (3.0, 7.0, 1.0),
central: ("object.Human_Airship"), central: ("object.Human_Airship"),
), ),
bone1: ( bone1: (
offset: (0.0, 0.0, 0.0), offset: (0.0, 40.0, -8.0),
central: ("propeller-l"), central: ("object.propeller-l"),
), ),
bone2: ( bone2: (
offset: (0.0, 0.0, 0.0), offset: (0.0, 0.0, -4.0),
central: ("propeller-r"), central: ("object.propeller-r"),
), ),
), ),
}) })

BIN
assets/server/voxel/Human_Airship.vox (Stored with Git LFS)

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
../../../server/voxel/Human_Airship.vox

View File

@ -1,6 +1,4 @@
use crate::{ use crate::make_case_elim;
make_case_elim
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
make_case_elim!( make_case_elim!(
@ -17,22 +15,29 @@ impl From<Body> for super::Body {
} }
impl Body { impl Body {
pub fn manifest_id(&self) -> &'static str { pub fn manifest_entry(&self) -> &'static str {
match self { match self {
Body::DefaultAirship => "server.manifests.ship_manifest", Body::DefaultAirship => "object.Human_Airship",
} }
} }
} }
/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to avoid having to /// Duplicate of some of the things defined in `voxygen::scene::figure::load` to
/// refactor all of that to `common` for using voxels as collider geometry /// avoid having to refactor all of that to `common` for using voxels as
/// collider geometry
pub mod figuredata { pub mod figuredata {
use crate::{ use crate::{
assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron}, assets::{self, AssetExt, AssetHandle, DotVoxAsset, Ron},
volumes::dyna::Dyna, figure::cell::Cell,
terrain::{
block::{Block, BlockKind},
sprite::SpriteKind,
},
volumes::dyna::{ColumnAccess, Dyna},
}; };
use serde::Deserialize;
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::Deserialize;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct VoxSimple(pub String); pub struct VoxSimple(pub String);
@ -57,13 +62,43 @@ pub mod figuredata {
#[derive(Clone)] #[derive(Clone)]
pub struct ShipSpec { pub struct ShipSpec {
pub central: AssetHandle<Ron<ShipCentralSpec>>, pub central: AssetHandle<Ron<ShipCentralSpec>>,
pub voxes: HashMap<String, Dyna<Block, (), ColumnAccess>>,
} }
impl assets::Compound for ShipSpec { impl assets::Compound for ShipSpec {
fn load<S: assets::source::Source>(_: &assets::AssetCache<S>, _: &str) -> Result<Self, assets::Error> { fn load<S: assets::source::Source>(
cache: &assets::AssetCache<S>,
_: &str,
) -> Result<Self, assets::Error> {
let manifest: AssetHandle<Ron<ShipCentralSpec>> = AssetExt::load("server.manifests.ship_manifest")?;
let mut voxes = HashMap::new();
for (_, spec) in (manifest.read().0).0.iter() {
for bone in [&spec.bone0, &spec.bone1, &spec.bone2].iter() {
// TODO: avoid the requirement for symlinks in "voxygen.voxel.object.", and load
// the models from "server.voxel." instead
let vox =
cache.load::<DotVoxAsset>(&["voxygen.voxel.", &bone.central.0].concat())?;
let dyna = Dyna::<Cell, (), ColumnAccess>::from_vox(&vox.read().0, false);
voxes.insert(bone.central.0.clone(), dyna.map_into(|cell| {
if let Some(rgb) = cell.get_color() {
Block::new(BlockKind::Misc, rgb)
} else {
Block::air(SpriteKind::Empty)
}
}));
}
}
Ok(ShipSpec { Ok(ShipSpec {
central: AssetExt::load("server.manifests.ship_manifest")? central: manifest,
voxes,
}) })
} }
} }
lazy_static! {
// TODO: load this from the ECS as a resource, and maybe make it more general than ships
// (although figuring out how to keep the figure bones in sync with the terrain offsets seems
// like a hard problem if they're not the same manifest)
pub static ref VOXEL_COLLIDER_MANIFEST: ShipSpec = AssetExt::load_expect_cloned("server.manifests.ship_manifest");
}
} }

View File

@ -66,7 +66,7 @@ pub enum Collider {
impl Collider { impl Collider {
pub fn get_radius(&self) -> f32 { pub fn get_radius(&self) -> f32 {
match self { match self {
Collider::Voxel { .. } => 0.0, Collider::Voxel { .. } => 1.0,
Collider::Box { radius, .. } => *radius, Collider::Box { radius, .. } => *radius,
Collider::Point => 0.0, Collider::Point => 0.0,
} }
@ -74,7 +74,7 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) { pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
match self { match self {
Collider::Voxel { .. } => (0.0, 0.0), Collider::Voxel { .. } => (0.0, 1.0),
Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier), Collider::Box { z_min, z_max, .. } => (*z_min * modifier, *z_max * modifier),
Collider::Point => (0.0, 0.0), Collider::Point => (0.0, 0.0),
} }

View File

@ -44,6 +44,16 @@ impl<V, M, A: Access> Dyna<V, M, A> {
None None
} }
} }
pub fn map_into<W, F: FnMut(V) -> W>(self, f: F) -> Dyna<W, M, A> {
let Dyna { vox, meta, sz, _phantom } = self;
Dyna {
vox: vox.into_iter().map(f).collect(),
meta,
sz,
_phantom,
}
}
} }
impl<V, M, A: Access> BaseVol for Dyna<V, M, A> { impl<V, M, A: Access> BaseVol for Dyna<V, M, A> {

View File

@ -1,7 +1,7 @@
use common::{ use common::{
comp::{ comp::{
BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos, BeamSegment, CharacterState, Collider, Gravity, Mass, Mounting, Ori, PhysicsState, Pos,
PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, PreviousPhysCache, Projectile, Scale, Shockwave, Sticky, Vel, body::ship::figuredata::VOXEL_COLLIDER_MANIFEST,
}, },
consts::{FRIC_GROUND, GRAVITY}, consts::{FRIC_GROUND, GRAVITY},
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
@ -15,8 +15,9 @@ use common_ecs::{Job, Origin, ParMode, Phase, PhysicsMetrics, System};
use hashbrown::HashMap; use hashbrown::HashMap;
use rayon::iter::ParallelIterator; use rayon::iter::ParallelIterator;
use specs::{ use specs::{
shred::{World, ResourceId}, shred::{ResourceId, World},
Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, WriteExpect, WriteStorage, SystemData, Entities, Entity, Join, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteExpect,
WriteStorage,
}; };
use std::ops::Range; use std::ops::Range;
use vek::*; use vek::*;
@ -114,7 +115,9 @@ impl<'a> PhysicsSystemData<'a> {
) )
.join() .join()
{ {
let _ = self.w.physics_states let _ = self
.w
.physics_states
.entry(entity) .entry(entity)
.map(|e| e.or_insert_with(Default::default)); .map(|e| e.or_insert_with(Default::default));
} }
@ -137,7 +140,10 @@ impl<'a> PhysicsSystemData<'a> {
.map(|(e, _, _, _, _, _, _)| e) .map(|(e, _, _, _, _, _, _)| e)
.collect::<Vec<_>>() .collect::<Vec<_>>()
{ {
let _ = self.w.previous_phys_cache.insert(entity, PreviousPhysCache { let _ = self
.w
.previous_phys_cache
.insert(entity, PreviousPhysCache {
velocity_dt: Vec3::zero(), velocity_dt: Vec3::zero(),
center: Vec3::zero(), center: Vec3::zero(),
collision_boundary: 0.0, collision_boundary: 0.0,
@ -180,10 +186,14 @@ impl<'a> PhysicsSystemData<'a> {
} }
drop(guard); drop(guard);
} }
fn apply_pushback(&mut self, job: &mut Job<Sys>) { fn apply_pushback(&mut self, job: &mut Job<Sys>) {
span!(guard, "Apply pushback"); span!(guard, "Apply pushback");
job.cpu_stats.measure(ParMode::Rayon); job.cpu_stats.measure(ParMode::Rayon);
let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; let PhysicsSystemData {
r: ref psdr,
w: ref mut psdw,
} = self;
let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache);
let metrics = ( let metrics = (
&psdr.entities, &psdr.entities,
@ -310,8 +320,13 @@ impl<'a> PhysicsSystemData<'a> {
metrics.entity_entity_collisions += 1; metrics.entity_entity_collisions += 1;
} }
// Don't apply repulsive force to projectiles // Don't apply repulsive force to projectiles or if we're colliding
if diff.magnitude_squared() > 0.0 && !is_projectile { // with a terrain-like entity, or if we are a terrain-like entity
if diff.magnitude_squared() > 0.0
&& !is_projectile
&& !matches!(collider_other, Some(Collider::Voxel { .. }))
&& !matches!(collider, Some(Collider::Voxel { .. }))
{
let force = let force =
400.0 * (collision_dist - diff.magnitude()) * mass_other 400.0 * (collision_dist - diff.magnitude()) * mass_other
/ (mass + mass_other); / (mass + mass_other);
@ -335,18 +350,21 @@ impl<'a> PhysicsSystemData<'a> {
entity_entity_collisions: old.entity_entity_collisions entity_entity_collisions: old.entity_entity_collisions
+ new.entity_entity_collisions, + new.entity_entity_collisions,
}); });
psdw.physics_metrics.entity_entity_collision_checks = metrics.entity_entity_collision_checks; psdw.physics_metrics.entity_entity_collision_checks =
metrics.entity_entity_collision_checks;
psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions; psdw.physics_metrics.entity_entity_collisions = metrics.entity_entity_collisions;
drop(guard); drop(guard);
} }
fn handle_movement_and_terrain(&mut self, job: &mut Job<Sys>) { fn handle_movement_and_terrain(&mut self, job: &mut Job<Sys>) {
let PhysicsSystemData { r: ref psdr, w: ref mut psdw } = self; let PhysicsSystemData {
r: ref psdr,
w: ref mut psdw,
} = self;
// Apply movement inputs // Apply movement inputs
span!(guard, "Apply movement and terrain collision"); span!(guard, "Apply movement and terrain collision");
let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache); let (positions, previous_phys_cache) = (&psdw.positions, &psdw.previous_phys_cache);
let (pos_writes, land_on_grounds) = let (pos_writes, land_on_grounds) = (
(
&psdr.entities, &psdr.entities,
psdr.scales.maybe(), psdr.scales.maybe(),
psdr.stickies.maybe(), psdr.stickies.maybe(),
@ -405,7 +423,8 @@ impl<'a> PhysicsSystemData<'a> {
} else { } else {
0.0 0.0
}); });
let in_loaded_chunk = psdr.terrain let in_loaded_chunk = psdr
.terrain
.get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32))) .get_key(psdr.terrain.pos_key(pos.0.map(|e| e.floor() as i32)))
.is_some(); .is_some();
let downward_force = let downward_force =
@ -474,7 +493,8 @@ impl<'a> PhysicsSystemData<'a> {
); );
}, },
Collider::Point => { Collider::Point => {
let (dist, block) = psdr.terrain let (dist, block) = psdr
.terrain
.ray(pos.0, pos.0 + pos_delta) .ray(pos.0, pos.0 + pos_delta)
.until(|block: &Block| block.is_filled()) .until(|block: &Block| block.is_filled())
.ignore_error() .ignore_error()
@ -511,7 +531,8 @@ impl<'a> PhysicsSystemData<'a> {
} }
} }
physics_state.in_liquid = psdr.terrain physics_state.in_liquid = psdr
.terrain
.get(pos.0.map(|e| e.floor() as i32)) .get(pos.0.map(|e| e.floor() as i32))
.ok() .ok()
.and_then(|vox| vox.is_liquid().then_some(1.0)); .and_then(|vox| vox.is_liquid().then_some(1.0));
@ -546,7 +567,7 @@ impl<'a> PhysicsSystemData<'a> {
) )
.join() .join()
{ {
let collision_boundary = previous_cache.collision_boundary /*let collision_boundary = previous_cache.collision_boundary
+ previous_cache_other.collision_boundary; + previous_cache_other.collision_boundary;
if previous_cache if previous_cache
.center .center
@ -555,7 +576,7 @@ impl<'a> PhysicsSystemData<'a> {
|| entity == entity_other || entity == entity_other
{ {
continue; continue;
} }*/
if let Collider::Voxel { id } = collider_other { if let Collider::Voxel { id } = collider_other {
// use bounding cylinder regardless of our collider // use bounding cylinder regardless of our collider
@ -563,19 +584,21 @@ impl<'a> PhysicsSystemData<'a> {
let radius = collider.get_radius() * scale; let radius = collider.get_radius() * scale;
let (z_min, z_max) = collider.get_z_limits(scale); let (z_min, z_max) = collider.get_z_limits(scale);
pos.0 -= pos_other.0;
let cylinder = (radius, z_min, z_max); let cylinder = (radius, z_min, z_max);
// TODO: load .vox into a Dyna, and use it (appropriately rotated) if let Some(dyna) = VOXEL_COLLIDER_MANIFEST.voxes.get(id) {
// as the terrain cylinder_voxel_collision(
/*cylinder_voxel_collision(
cylinder, cylinder,
&*psdr.terrain, &*dyna,
entity, entity,
&mut pos, &mut pos,
pos_delta, pos_delta,
vel, vel,
&mut physics_state, &mut physics_state,
&mut land_on_grounds, &mut land_on_grounds,
);*/ );
}
pos.0 += pos_other.0;
} }
} }
if pos != old_pos { if pos != old_pos {
@ -620,10 +643,7 @@ impl<'a> System<'a> for Sys {
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587 #[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
fn run( fn run(job: &mut Job<Self>, mut psd: Self::SystemData) {
job: &mut Job<Self>,
mut psd: Self::SystemData,
) {
psd.reset(); psd.reset();
// Apply pushback // Apply pushback
@ -642,7 +662,6 @@ impl<'a> System<'a> for Sys {
psd.maintain_pushback_cache(); psd.maintain_pushback_cache();
psd.apply_pushback(job); psd.apply_pushback(job);
psd.handle_movement_and_terrain(job); psd.handle_movement_and_terrain(job);
} }
} }

View File

@ -966,11 +966,12 @@ fn handle_spawn_airship(
_action: &ChatCommand, _action: &ChatCommand,
) { ) {
match server.state.read_component_copied::<comp::Pos>(target) { match server.state.read_component_copied::<comp::Pos>(target) {
Some(pos) => { Some(mut pos) => {
pos.0.z += 50.0;
server server
.state .state
.create_ship(pos, comp::ship::Body::DefaultAirship) .create_ship(pos, comp::ship::Body::DefaultAirship, 1)
.with(comp::Scale(50.0)) .with(comp::Scale(11.0))
.with(LightEmitter { .with(LightEmitter {
col: Rgb::new(1.0, 0.65, 0.2), col: Rgb::new(1.0, 0.65, 0.2),
strength: 2.0, strength: 2.0,

View File

@ -41,7 +41,7 @@ pub trait StateExt {
) -> EcsEntityBuilder; ) -> EcsEntityBuilder;
/// Build a static object entity /// Build a static object entity
fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder; fn create_object(&mut self, pos: comp::Pos, object: comp::object::Body) -> EcsEntityBuilder;
fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder; fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body, level: u16) -> EcsEntityBuilder;
/// Build a projectile /// Build a projectile
fn create_projectile( fn create_projectile(
&mut self, &mut self,
@ -203,16 +203,24 @@ impl StateExt for State {
.with(comp::Gravity(1.0)) .with(comp::Gravity(1.0))
} }
fn create_ship(&mut self, pos: comp::Pos, object: comp::ship::Body) -> EcsEntityBuilder { fn create_ship(&mut self, pos: comp::Pos, ship: comp::ship::Body, level: u16) -> EcsEntityBuilder {
self.ecs_mut() self.ecs_mut()
.create_entity_synced() .create_entity_synced()
.with(pos) .with(pos)
.with(comp::Vel(Vec3::zero())) .with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default()) .with(comp::Ori::default())
.with(comp::Mass(50.0)) .with(comp::Mass(50.0))
.with(comp::Collider::Voxel { id: object.manifest_id().to_string() }) .with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() })
.with(comp::Body::Ship(object)) .with(comp::Body::Ship(ship))
.with(comp::Gravity(1.0)) .with(comp::Gravity(1.0))
.with(comp::Controller::default())
.with(comp::inventory::Inventory::new_empty())
.with(comp::CharacterState::default())
.with(comp::Energy::new(ship.into(), level))
.with(comp::Health::new(ship.into(), level))
.with(comp::Stats::new("Airship".to_string()))
.with(comp::Buffs::default())
.with(comp::Combo::default())
} }
fn create_projectile( fn create_projectile(

View File

@ -4120,7 +4120,7 @@ impl FigureMgr {
.join() .join()
// Don't render dead entities // Don't render dead entities
.filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead))
.for_each(|(entity, pos, _, body, _, inventory, _)| { .for_each(|(entity, pos, _, body, _, inventory, scale)| {
if let Some((locals, bone_consts, model, _)) = self.get_model_for_render( if let Some((locals, bone_consts, model, _)) = self.get_model_for_render(
tick, tick,
camera, camera,
@ -4130,7 +4130,7 @@ impl FigureMgr {
inventory, inventory,
false, false,
pos.0, pos.0,
figure_lod_render_distance, figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.can_shadow_sun(), |state| state.can_shadow_sun(),
) { ) {
renderer.render_figure_shadow_directed( renderer.render_figure_shadow_directed(
@ -4162,7 +4162,7 @@ impl FigureMgr {
let character_state_storage = state.read_storage::<common::comp::CharacterState>(); let character_state_storage = state.read_storage::<common::comp::CharacterState>();
let character_state = character_state_storage.get(player_entity); let character_state = character_state_storage.get(player_entity);
for (entity, pos, _, body, _, inventory, _) in ( for (entity, pos, _, body, _, inventory, scale) in (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),
ecs.read_storage::<Ori>().maybe(), ecs.read_storage::<Ori>().maybe(),
@ -4187,7 +4187,7 @@ impl FigureMgr {
inventory, inventory,
false, false,
pos.0, pos.0,
figure_lod_render_distance, figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.visible(), |state| state.visible(),
) { ) {
renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod); renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod);