mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Initial implementation of indoor camera. Enclosure detection and camera switch
This commit is contained in:
parent
98c8240879
commit
7f6c310d38
@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- New level of detail feature, letting you see all the world's terrain at any view distance.
|
||||
- Point and directional lights now cast realistic shadows, using shadow mapping.
|
||||
- Added leaf and chimney particles
|
||||
@ -20,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Adaptive stride setup for more dynamic run behavior
|
||||
- Theropod body
|
||||
- Several new animals
|
||||
- Added a separate camera mode for when the player is indoors
|
||||
|
||||
### Changed
|
||||
|
||||
@ -95,11 +97,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add detection of entities under the cursor
|
||||
- Functional group-system with exp-sharing and disabled damage to group members
|
||||
- Some Campfire, fireball & bomb; particle, light & sound effects.
|
||||
- Added firework recipe
|
||||
- Added setting to change resolution
|
||||
- Rare (unfinished) castles
|
||||
- Caves with monsters and treasure
|
||||
- Furniture and decals in towns
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -96,4 +96,71 @@ impl<'a, V: ReadVol, F: RayUntil<V::Vox>, G: RayForEach<V::Vox>> Ray<'a, V, F, G
|
||||
|
||||
(dist, Ok(None))
|
||||
}
|
||||
|
||||
///
|
||||
/// Unlike cast(), this method traverses the whole ray identifying
|
||||
/// "edges". Edges are defined as the point where the until condition
|
||||
/// switches from false to true, or true to false.
|
||||
///
|
||||
/// It then returns the last edge's distance.
|
||||
pub fn last_edge_cast(mut self) -> Result<f32, V::Error> {
|
||||
const PLANCK: f32 = 0.001;
|
||||
|
||||
let mut dist = 0.0;
|
||||
let dir = (self.to - self.from).normalized();
|
||||
let max = (self.to - self.from).magnitude();
|
||||
|
||||
let mut until_condition_met = true;
|
||||
|
||||
let mut distances = vec![];
|
||||
|
||||
for _ in 0..self.max_iter {
|
||||
let pos = self.from + dir * dist;
|
||||
let ipos = pos.map(|e| e.floor() as i32);
|
||||
|
||||
// Allow one iteration above max.
|
||||
if dist > max {
|
||||
break;
|
||||
}
|
||||
|
||||
let vox = self.vol.get(ipos);
|
||||
|
||||
// for_each
|
||||
if let Some(g) = &mut self.for_each {
|
||||
if let Ok(vox) = vox {
|
||||
g(vox, ipos);
|
||||
}
|
||||
}
|
||||
|
||||
match vox.map(|vox| (vox, (self.until)(vox))) {
|
||||
Ok((_vox, true)) => {
|
||||
if !until_condition_met {
|
||||
// This is an edge
|
||||
distances.push(dist);
|
||||
until_condition_met = true;
|
||||
}
|
||||
},
|
||||
Ok((_vox, false)) => {
|
||||
if until_condition_met {
|
||||
// This is also an edge
|
||||
distances.push(dist);
|
||||
until_condition_met = false;
|
||||
}
|
||||
},
|
||||
Err(err) if !self.ignore_error => return Err(err),
|
||||
_ => {},
|
||||
}
|
||||
|
||||
let deltas =
|
||||
(dir.map(|e| if e < 0.0 { 0.0 } else { 1.0 }) - pos.map(|e| e.abs().fract())) / dir;
|
||||
|
||||
dist += deltas.reduce(f32::min).max(PLANCK);
|
||||
}
|
||||
|
||||
if !distances.is_empty() {
|
||||
Ok(distances[distances.len() - 1])
|
||||
} else {
|
||||
Ok(dist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
use common::{
|
||||
span,
|
||||
terrain::TerrainGrid,
|
||||
vol::{ReadVol, Vox},
|
||||
};
|
||||
use std::f32::consts::PI;
|
||||
@ -48,6 +49,9 @@ pub struct Camera {
|
||||
|
||||
last_time: Option<f64>,
|
||||
|
||||
enclosed: bool,
|
||||
enclosed_last_checked: Option<Vec3<f32>>,
|
||||
|
||||
dependents: Dependents,
|
||||
frustum: Frustum<f32>,
|
||||
}
|
||||
@ -68,6 +72,11 @@ impl Camera {
|
||||
|
||||
last_time: None,
|
||||
|
||||
// Whether the camera was detected to be inside of a structure. Switches us to indoor
|
||||
// camera.
|
||||
enclosed: false,
|
||||
enclosed_last_checked: None,
|
||||
|
||||
dependents: Dependents {
|
||||
view_mat: Mat4::identity(),
|
||||
proj_mat: Mat4::identity(),
|
||||
@ -78,13 +87,146 @@ impl Camera {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Estimate whether the player is indoors with rays from their position
|
||||
/// The player is marked "enclosed" if >= 75% of the rays hit a solid block
|
||||
pub fn check_enclosure(&mut self, terrain: &TerrainGrid) {
|
||||
// We can expose these values in the settings at some point if they become a
|
||||
// performance concern
|
||||
let vertical_resolution = 3;
|
||||
let horizontal_resolution = 12;
|
||||
let ray_distance = 30.0;
|
||||
let raycast_resolution = 50;
|
||||
|
||||
let mut total = 0;
|
||||
let mut hit = 0;
|
||||
|
||||
for horizontal in 0..horizontal_resolution {
|
||||
// Do a ray horizontally out from the player to check for things
|
||||
let horizontal_angle = (horizontal as f32 * PI) / (horizontal_resolution as f32 / 2.0);
|
||||
let (start, end) = (
|
||||
self.focus,
|
||||
self.focus
|
||||
+ (Vec3::new(
|
||||
-f32::sin(horizontal as f32),
|
||||
-f32::cos(horizontal as f32),
|
||||
0.0,
|
||||
) * ray_distance),
|
||||
);
|
||||
|
||||
match terrain
|
||||
.ray(start, end)
|
||||
.ignore_error()
|
||||
.max_iter(raycast_resolution)
|
||||
.until(|b| b.is_solid())
|
||||
.cast()
|
||||
{
|
||||
(_, Ok(Some(_))) => {
|
||||
total += 1;
|
||||
hit += 1;
|
||||
},
|
||||
(_, Ok(None)) => total += 1,
|
||||
(_, Err(_)) => total += 1,
|
||||
}
|
||||
|
||||
if horizontal % vertical_resolution == 0 {
|
||||
for vertical in 0..3 {
|
||||
let vertical_angle = (vertical as f32 * PI) / 6.0;
|
||||
let (vertical_start, vertical_end) = (
|
||||
self.focus,
|
||||
self.focus
|
||||
+ (Vec3::new(
|
||||
-f32::sin(horizontal_angle) * f32::cos(vertical_angle),
|
||||
-f32::cos(horizontal_angle) * f32::cos(vertical_angle),
|
||||
f32::sin(vertical_angle),
|
||||
) * ray_distance),
|
||||
);
|
||||
|
||||
match terrain
|
||||
.ray(vertical_start, vertical_end)
|
||||
.ignore_error()
|
||||
.max_iter(raycast_resolution)
|
||||
.until(|b| b.is_solid())
|
||||
.cast()
|
||||
{
|
||||
(_, Ok(Some(_))) => {
|
||||
total += 1;
|
||||
hit += 1;
|
||||
},
|
||||
(_, Ok(None)) => total += 1,
|
||||
(_, Err(_)) => total += 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let percentage = hit as f32 / total as f32;
|
||||
self.enclosed = percentage >= 0.75;
|
||||
}
|
||||
|
||||
/// Compute_dependents adjusted for when there is no terrain data (character
|
||||
/// selection)
|
||||
pub fn compute_dependents_no_terrain(&mut self) {
|
||||
self.dependents.view_mat = Mat4::<f32>::identity()
|
||||
* Mat4::translation_3d(-Vec3::unit_z() * self.dist)
|
||||
* Mat4::rotation_z(self.ori.z)
|
||||
* Mat4::rotation_x(self.ori.y)
|
||||
* Mat4::rotation_y(self.ori.x)
|
||||
* Mat4::rotation_3d(PI / 2.0, -Vec4::unit_x())
|
||||
* Mat4::translation_3d(-self.focus);
|
||||
|
||||
self.dependents.proj_mat =
|
||||
Mat4::perspective_rh_no(self.fov, self.aspect, NEAR_PLANE, FAR_PLANE);
|
||||
|
||||
// TODO: Make this more efficient.
|
||||
self.dependents.cam_pos = Vec3::from(self.dependents.view_mat.inverted() * Vec4::unit_w());
|
||||
}
|
||||
|
||||
/// Compute the transformation matrices (view matrix and projection matrix)
|
||||
/// and position of the camera.
|
||||
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
|
||||
pub fn compute_dependents(&mut self, terrain: &TerrainGrid) {
|
||||
span!(_guard, "compute_dependents", "Camera::compute_dependents");
|
||||
// Check enclosure if necessary
|
||||
let mut should_check_enclosure = false;
|
||||
match self.enclosed_last_checked {
|
||||
Some(vec) => {
|
||||
// We don't need to check enclosure every frame. Only check if we have moved.
|
||||
if vec.distance_squared(self.focus) >= 1.2 {
|
||||
should_check_enclosure = true;
|
||||
self.enclosed_last_checked = Some(self.focus);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
should_check_enclosure = true;
|
||||
self.enclosed_last_checked = Some(self.focus);
|
||||
},
|
||||
}
|
||||
|
||||
if should_check_enclosure {
|
||||
self.check_enclosure(&*terrain);
|
||||
}
|
||||
|
||||
let dist = {
|
||||
let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
|
||||
|
||||
if self.enclosed {
|
||||
match terrain
|
||||
.ray(start, end)
|
||||
.ignore_error()
|
||||
.max_iter(500)
|
||||
.until(|vox| !vox.is_solid())
|
||||
.last_edge_cast()
|
||||
{
|
||||
Ok(d) => {
|
||||
if d >= self.dist {
|
||||
self.dist
|
||||
} else {
|
||||
f32::min(self.dist - d - 0.1, self.dist)
|
||||
}
|
||||
},
|
||||
Err(_) => self.dist,
|
||||
}
|
||||
} else {
|
||||
match terrain
|
||||
.ray(start, end)
|
||||
.ignore_error()
|
||||
@ -97,6 +239,7 @@ impl Camera {
|
||||
(_, Err(_)) => self.dist,
|
||||
}
|
||||
.max(0.0)
|
||||
}
|
||||
};
|
||||
|
||||
self.dependents.view_mat = Mat4::<f32>::identity()
|
||||
|
@ -22,29 +22,10 @@ use common::{
|
||||
comp::{humanoid, item::ItemKind, Loadout},
|
||||
figure::Segment,
|
||||
terrain::BlockKind,
|
||||
vol::{BaseVol, ReadVol, Vox},
|
||||
};
|
||||
use tracing::error;
|
||||
use vek::*;
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone)]
|
||||
struct VoidVox;
|
||||
impl Vox for VoidVox {
|
||||
fn empty() -> Self { VoidVox }
|
||||
|
||||
fn is_empty(&self) -> bool { true }
|
||||
|
||||
fn or(self, _other: Self) -> Self { VoidVox }
|
||||
}
|
||||
struct VoidVol;
|
||||
impl BaseVol for VoidVol {
|
||||
type Error = ();
|
||||
type Vox = VoidVox;
|
||||
}
|
||||
impl ReadVol for VoidVol {
|
||||
fn get<'a>(&'a self, _pos: Vec3<i32>) -> Result<&'a Self::Vox, Self::Error> { Ok(&VoidVox) }
|
||||
}
|
||||
|
||||
fn generate_mesh<'a>(
|
||||
greedy: &mut GreedyMesh<'a>,
|
||||
mesh: &mut Mesh<TerrainPipeline>,
|
||||
@ -234,7 +215,7 @@ impl Scene {
|
||||
scene_data.mouse_smoothing,
|
||||
);
|
||||
|
||||
self.camera.compute_dependents(&VoidVol);
|
||||
self.camera.compute_dependents_no_terrain();
|
||||
let camera::Dependents {
|
||||
view_mat,
|
||||
proj_mat,
|
||||
|
Loading…
Reference in New Issue
Block a user