Struct veloren_common_net::msg::world_msg::WorldMapMsg

source ·
pub struct WorldMapMsg {
    pub dimensions_lg: Vec2<u32>,
    pub max_height: f32,
    pub rgba: Grid<u32>,
    pub alt: Grid<u32>,
    pub horizons: [(Vec<u8>, Vec<u8>); 2],
    pub sites: Vec<SiteInfo>,
    pub possible_starting_sites: Vec<SiteId>,
    pub pois: Vec<PoiInfo>,
    pub default_chunk: Arc<TerrainChunk>,
}
Expand description

World map information. Note that currently, we always send the whole thing in one go, but the structure aims to try to provide information as locally as possible, so that in the future we can split up large maps into multiple WorldMapMsg fragments.

TODO: Update message format to make fragmentable, allowing us to send more information without running into bandwidth issues.

TODO: Add information for rivers (currently, we just prerender them on the server, but this is not a great solution for LoD. The map rendering code is already set up to be able to take advantage of the river rendering being split out, but the format is a little complicated for space reasons and it may take some tweaking to get right, so we avoid sending it for now).

TODO: measure explicit compression schemes that might save space, e.g. repeating the “small angles” optimization that works well on more detailed shadow maps intended for height maps.

Fields§

§dimensions_lg: Vec2<u32>

Log base 2 of world map dimensions (width × height) in chunks.

NOTE: Invariant: chunk count fits in a u16.

§max_height: f32

Max height (used to scale altitudes).

§rgba: Grid<u32>

RGB+A; the alpha channel is currently unused, but will be used in the future. Entries are in the usual chunk order.

§alt: Grid<u32>

Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for altitude. The remainder are currently unused, but we have plans to use 7 bits for water depth (using an integer f7 encoding), and we will find other uses for the remaining 12 bits.

§horizons: [(Vec<u8>, Vec<u8>); 2]

Horizon mapping. This is a variant of shadow mapping that is specifically designed for height maps; it takes advantage of their regular structure (e.g. no holes) to compress all information needed to decide when to cast a sharp shadow into a single nagle, the “horizon angle.” This is the smallest angle with the ground at which light can pass through any occluders to reach the chunk, in some chosen horizontal direction. This would not be sufficient for a more complicated 3D structure, but it works for height maps since:

  1. they have no gaps, so as soon as light can shine through it will always be able to do so, and
  2. we only care about lighting from the top, and only from the east and west (since at a large scale like this we mostly just want to handle variable sunlight; moonlight would present more challenges but we currently have no plans to try to cast accurate shadows in moonlight).

Our chosen format is two pairs of vectors, with the first pair representing west-facing light (casting shadows on the left side) and the second representing east-facing light (casting shadows on the east side).

The pair of vectors consists of (with each vector in the usual chunk order):

  • Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360). We might consider switching to tangent if that represents the information we care about better.
  • Approximate (floor) height of maximal occluder. We currently use this to try to deliver some approximation of soft shadows, which isn’t that big a deal on the world map but is probably needed in order to ensure smooth transitions between chunks in LoD view. Additionally, when we start using the shadow information to do local lighting on the world map, we’ll want a quick way to test where we can go out of shadow at arbitrary heights (since the player and other entities cajn find themselves far from the ground at times). While this is only an approximation to a proper distance map, hopefully it will give us something that feels reasonable enough for Veloren’s style.

NOTE: On compression.

Horizon mapping has a lot of advantages for height maps (simple, easy to understand, doesn’t require any fancy math or approximation beyond precision loss), though it loses a few of them by having to store distance to occluder as well. However, just storing tons and tons of regular shadow maps (153 for a full day cycle, stored at irregular intervals) combined with clever explicit compression and avoiding recording sharp local shadows (preferring retracing for these), yielded a compression rate of under 3 bits per column! Since we likely want to avoid per-column shadows for worlds of the sizes we want, we’d still need to store some extra information to create soft shadows, but it would still be nice to try to drive down our size as much as possible given how compressible shadows of height maps seem to be in practice. Therefore, we try to take advantage of the way existing compression algorithms tend to work to see if we can achieve significant gains without doing a lot of custom work.

Specifically, since our rays are cast east/west, we expect that for each row, the horizon angles in each direction should be sequences of monotonically increasing values (as chunks approach a tall occluder), followed by sequences of no shadow, repeated until the end of the map. Monotonic sequences and same-byte sequences are usually easy to compress and existing algorithms are more likely to be able to deal with them than jumbled data. If we were to keep both directions in the same vector, off-the-shelf compression would probably be less effective.

For related reasons, rather than storing distances as in a standard distance map (which would lead to monotonically decreasing values as we approached the occluder from a given direction), we store the estimated occluder height. The idea here is that we replace the monotonic sequences with constant sequences, which are extremely straightforward to compress and mostly handled automatically by anything that does run-length encoding (i.e. most off-the-shelf compression algorithms).

We still need to benchmark this properly, as there’s no guarantee our current compression algorithms will actually work well on this data in practice. It’s possible that some other permutation (e.g. more bits reserved for “distance to occluder” in exchange for an even more predictible sequence) would end up compressing better than storing angles, or that we don’t need as much precision as we currently have (256 possible angles).

§sites: Vec<SiteInfo>§possible_starting_sites: Vec<SiteId>§pois: Vec<PoiInfo>§default_chunk: Arc<TerrainChunk>

Default chunk (representing the ocean outside the map bounds). Sea level (used to provide a base altitude) is the lower bound of this chunk.

Trait Implementations§

source§

impl Clone for WorldMapMsg

source§

fn clone(&self) -> WorldMapMsg

Returns a copy of the value. Read more
1.0.0 · source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
source§

impl Debug for WorldMapMsg

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl<'de> Deserialize<'de> for WorldMapMsg

source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
source§

impl Serialize for WorldMapMsg

source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> CloneToUninit for T
where T: Clone,

source§

default unsafe fn clone_to_uninit(&self, dst: *mut T)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dst. Read more
§

impl<C, M> ConvertSaveload<M> for C

§

type Data = C

(De)Serializable data representation for data type
§

type Error = Infallible

Error may occur during serialization or deserialization of component
§

fn convert_into<F>( &self, _: F, ) -> Result<<C as ConvertSaveload<M>>::Data, <C as ConvertSaveload<M>>::Error>
where F: FnMut(Entity) -> Option<M>,

Convert this data type into serializable form (Data) using entity to marker mapping function
§

fn convert_from<F>( data: <C as ConvertSaveload<M>>::Data, _: F, ) -> Result<C, <C as ConvertSaveload<M>>::Error>
where F: FnMut(M) -> Option<Entity>,

Convert this data from a deserializable form (Data) using entity to marker mapping function
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T> IntoEither for T

source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
§

impl<T> Pointable for T

§

const ALIGN: usize = _

The alignment of pointer.
§

type Init = T

The type for initializers.
§

unsafe fn init(init: <T as Pointable>::Init) -> usize

Initializes a with the given initializer. Read more
§

unsafe fn deref<'a>(ptr: usize) -> &'a T

Dereferences the given pointer. Read more
§

unsafe fn deref_mut<'a>(ptr: usize) -> &'a mut T

Mutably dereferences the given pointer. Read more
§

unsafe fn drop(ptr: usize)

Drops the object pointed to by the given pointer. Read more
source§

impl<T> Same for T

§

type Output = T

Should always be Self
§

impl<Context> SubContext<Context> for Context

§

fn sub_context(self) -> Context

source§

impl<T> ToOwned for T
where T: Clone,

§

type Owned = T

The resulting type after obtaining ownership.
source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

§

impl<T> Event for T
where T: Send + Sync + 'static,

§

impl<T> Resource for T
where T: Any + Send + Sync,

§

impl<T> Storable for T
where T: Send + Sync + 'static,