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]
|
## [Unreleased]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- New level of detail feature, letting you see all the world's terrain at any view distance.
|
- 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.
|
- Point and directional lights now cast realistic shadows, using shadow mapping.
|
||||||
- Added leaf and chimney particles
|
- 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
|
- Adaptive stride setup for more dynamic run behavior
|
||||||
- Theropod body
|
- Theropod body
|
||||||
- Several new animals
|
- Several new animals
|
||||||
|
- Added a separate camera mode for when the player is indoors
|
||||||
|
|
||||||
### Changed
|
### 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
|
- Add detection of entities under the cursor
|
||||||
- Functional group-system with exp-sharing and disabled damage to group members
|
- Functional group-system with exp-sharing and disabled damage to group members
|
||||||
- Some Campfire, fireball & bomb; particle, light & sound effects.
|
- 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
|
### 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))
|
(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::{
|
use common::{
|
||||||
span,
|
span,
|
||||||
|
terrain::TerrainGrid,
|
||||||
vol::{ReadVol, Vox},
|
vol::{ReadVol, Vox},
|
||||||
};
|
};
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
@ -48,6 +49,9 @@ pub struct Camera {
|
|||||||
|
|
||||||
last_time: Option<f64>,
|
last_time: Option<f64>,
|
||||||
|
|
||||||
|
enclosed: bool,
|
||||||
|
enclosed_last_checked: Option<Vec3<f32>>,
|
||||||
|
|
||||||
dependents: Dependents,
|
dependents: Dependents,
|
||||||
frustum: Frustum<f32>,
|
frustum: Frustum<f32>,
|
||||||
}
|
}
|
||||||
@ -68,6 +72,11 @@ impl Camera {
|
|||||||
|
|
||||||
last_time: None,
|
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 {
|
dependents: Dependents {
|
||||||
view_mat: Mat4::identity(),
|
view_mat: Mat4::identity(),
|
||||||
proj_mat: Mat4::identity(),
|
proj_mat: Mat4::identity(),
|
||||||
@ -78,25 +87,159 @@ impl Camera {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the transformation matrices (view matrix and projection matrix)
|
///
|
||||||
/// and position of the camera.
|
/// Estimate whether the player is indoors with rays from their position
|
||||||
pub fn compute_dependents(&mut self, terrain: &impl ReadVol) {
|
/// The player is marked "enclosed" if >= 75% of the rays hit a solid block
|
||||||
span!(_guard, "compute_dependents", "Camera::compute_dependents");
|
pub fn check_enclosure(&mut self, terrain: &TerrainGrid) {
|
||||||
let dist = {
|
// We can expose these values in the settings at some point if they become a
|
||||||
let (start, end) = (self.focus - self.forward() * self.dist, self.focus);
|
// 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
|
match terrain
|
||||||
.ray(start, end)
|
.ray(start, end)
|
||||||
.ignore_error()
|
.ignore_error()
|
||||||
.max_iter(500)
|
.max_iter(raycast_resolution)
|
||||||
.until(|b| b.is_empty())
|
.until(|b| b.is_solid())
|
||||||
.cast()
|
.cast()
|
||||||
{
|
{
|
||||||
(d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist),
|
(_, Ok(Some(_))) => {
|
||||||
(_, Ok(None)) => self.dist,
|
total += 1;
|
||||||
(_, Err(_)) => self.dist,
|
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: &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()
|
||||||
|
.max_iter(500)
|
||||||
|
.until(|b| b.is_empty())
|
||||||
|
.cast()
|
||||||
|
{
|
||||||
|
(d, Ok(Some(_))) => f32::min(self.dist - d - 0.03, self.dist),
|
||||||
|
(_, Ok(None)) => self.dist,
|
||||||
|
(_, Err(_)) => self.dist,
|
||||||
|
}
|
||||||
|
.max(0.0)
|
||||||
}
|
}
|
||||||
.max(0.0)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.dependents.view_mat = Mat4::<f32>::identity()
|
self.dependents.view_mat = Mat4::<f32>::identity()
|
||||||
|
@ -22,29 +22,10 @@ use common::{
|
|||||||
comp::{humanoid, item::ItemKind, Loadout},
|
comp::{humanoid, item::ItemKind, Loadout},
|
||||||
figure::Segment,
|
figure::Segment,
|
||||||
terrain::BlockKind,
|
terrain::BlockKind,
|
||||||
vol::{BaseVol, ReadVol, Vox},
|
|
||||||
};
|
};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use vek::*;
|
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>(
|
fn generate_mesh<'a>(
|
||||||
greedy: &mut GreedyMesh<'a>,
|
greedy: &mut GreedyMesh<'a>,
|
||||||
mesh: &mut Mesh<TerrainPipeline>,
|
mesh: &mut Mesh<TerrainPipeline>,
|
||||||
@ -234,7 +215,7 @@ impl Scene {
|
|||||||
scene_data.mouse_smoothing,
|
scene_data.mouse_smoothing,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.camera.compute_dependents(&VoidVol);
|
self.camera.compute_dependents_no_terrain();
|
||||||
let camera::Dependents {
|
let camera::Dependents {
|
||||||
view_mat,
|
view_mat,
|
||||||
proj_mat,
|
proj_mat,
|
||||||
|
Loading…
Reference in New Issue
Block a user