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: (
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"),
),
bone1: (
offset: (0.0, 0.0, 0.0),
central: ("propeller-l"),
offset: (0.0, 40.0, -8.0),
central: ("object.propeller-l"),
),
bone2: (
offset: (0.0, 0.0, 0.0),
central: ("propeller-r"),
offset: (0.0, 0.0, -4.0),
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::{
make_case_elim
};
use crate::make_case_elim;
use serde::{Deserialize, Serialize};
make_case_elim!(
@ -17,22 +15,29 @@ impl From<Body> for super::Body {
}
impl Body {
pub fn manifest_id(&self) -> &'static str {
pub fn manifest_entry(&self) -> &'static str {
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
/// refactor all of that to `common` for using voxels as collider geometry
/// Duplicate of some of the things defined in `voxygen::scene::figure::load` to
/// avoid having to refactor all of that to `common` for using voxels as
/// collider geometry
pub mod figuredata {
use crate::{
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 lazy_static::lazy_static;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct VoxSimple(pub String);
@ -57,13 +62,43 @@ pub mod figuredata {
#[derive(Clone)]
pub struct ShipSpec {
pub central: AssetHandle<Ron<ShipCentralSpec>>,
pub voxes: HashMap<String, Dyna<Block, (), ColumnAccess>>,
}
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 {
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 {
pub fn get_radius(&self) -> f32 {
match self {
Collider::Voxel { .. } => 0.0,
Collider::Voxel { .. } => 1.0,
Collider::Box { radius, .. } => *radius,
Collider::Point => 0.0,
}
@ -74,7 +74,7 @@ impl Collider {
pub fn get_z_limits(&self, modifier: f32) -> (f32, f32) {
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::Point => (0.0, 0.0),
}

View File

@ -44,6 +44,16 @@ impl<V, M, A: Access> Dyna<V, M, A> {
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> {

View File

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

View File

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

View File

@ -41,7 +41,7 @@ pub trait StateExt {
) -> EcsEntityBuilder;
/// Build a static object entity
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
fn create_projectile(
&mut self,
@ -203,16 +203,24 @@ impl StateExt for State {
.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()
.create_entity_synced()
.with(pos)
.with(comp::Vel(Vec3::zero()))
.with(comp::Ori::default())
.with(comp::Mass(50.0))
.with(comp::Collider::Voxel { id: object.manifest_id().to_string() })
.with(comp::Body::Ship(object))
.with(comp::Collider::Voxel { id: ship.manifest_entry().to_string() })
.with(comp::Body::Ship(ship))
.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(

View File

@ -4120,7 +4120,7 @@ impl FigureMgr {
.join()
// Don't render dead entities
.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(
tick,
camera,
@ -4130,7 +4130,7 @@ impl FigureMgr {
inventory,
false,
pos.0,
figure_lod_render_distance,
figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.can_shadow_sun(),
) {
renderer.render_figure_shadow_directed(
@ -4162,7 +4162,7 @@ impl FigureMgr {
let character_state_storage = state.read_storage::<common::comp::CharacterState>();
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.read_storage::<Pos>(),
ecs.read_storage::<Ori>().maybe(),
@ -4187,7 +4187,7 @@ impl FigureMgr {
inventory,
false,
pos.0,
figure_lod_render_distance,
figure_lod_render_distance * scale.map_or(1.0, |s| s.0),
|state| state.visible(),
) {
renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod);