veloren_common_net/msg/world_msg.rs
1use common::{grid::Grid, map::Marker, terrain::TerrainChunk, trade::Good};
2use serde::{Deserialize, Serialize};
3use std::{collections::HashMap, sync::Arc};
4use vek::*;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
7/// World map information. Note that currently, we always send the whole thing
8/// in one go, but the structure aims to try to provide information as locally
9/// as possible, so that in the future we can split up large maps into multiple
10/// WorldMapMsg fragments.
11///
12/// TODO: Update message format to make fragmentable, allowing us to send more
13/// information without running into bandwidth issues.
14///
15/// TODO: Add information for rivers (currently, we just prerender them on the
16/// server, but this is not a great solution for LoD. The map rendering code is
17/// already set up to be able to take advantage of the river rendering being
18/// split out, but the format is a little complicated for space reasons and it
19/// may take some tweaking to get right, so we avoid sending it for now).
20///
21/// TODO: measure explicit compression schemes that might save space, e.g.
22/// repeating the "small angles" optimization that works well on more detailed
23/// shadow maps intended for height maps.
24pub struct WorldMapMsg {
25 /// Log base 2 of world map dimensions (width × height) in chunks.
26 ///
27 /// NOTE: Invariant: chunk count fits in a u16.
28 pub dimensions_lg: Vec2<u32>,
29 /// Max height (used to scale altitudes).
30 pub max_height: f32,
31 /// RGB+A; the alpha channel is currently unused, but will be used in the
32 /// future. Entries are in the usual chunk order.
33 pub rgba: Grid<u32>,
34 /// Altitudes: bits 2 to 0 are unused, then bits 15 to 3 are used for
35 /// altitude. The remainder are currently unused, but we have plans to
36 /// use 7 bits for water depth (using an integer f7 encoding), and we
37 /// will find other uses for the remaining 12 bits.
38 pub alt: Grid<u32>,
39 /// Horizon mapping. This is a variant of shadow mapping that is
40 /// specifically designed for height maps; it takes advantage of their
41 /// regular structure (e.g. no holes) to compress all information needed
42 /// to decide when to cast a sharp shadow into a single nagle, the "horizon
43 /// angle." This is the smallest angle with the ground at which light can
44 /// pass through any occluders to reach the chunk, in some chosen
45 /// horizontal direction. This would not be sufficient for a more
46 /// complicated 3D structure, but it works for height maps since:
47 ///
48 /// 1. they have no gaps, so as soon as light can shine through it will
49 /// always be able to do so, and
50 /// 2. we only care about lighting from the top, and only from the east and
51 /// west (since at a large scale like this we mostly just want to handle
52 /// variable sunlight; moonlight would present more challenges but we
53 /// currently have no plans to try to cast accurate shadows in
54 /// moonlight).
55 ///
56 /// Our chosen format is two pairs of vectors,
57 /// with the first pair representing west-facing light (casting shadows on
58 /// the left side) and the second representing east-facing light
59 /// (casting shadows on the east side).
60 ///
61 /// The pair of vectors consists of (with each vector in the usual chunk
62 /// order):
63 ///
64 /// * Horizon angle pointing east (1 byte, scaled so 1 unit = 255° / 360).
65 /// We might consider switching to tangent if that represents the
66 /// information we care about better.
67 /// * Approximate (floor) height of maximal occluder. We currently use this
68 /// to try to deliver some approximation of soft shadows, which isn't that
69 /// big a deal on the world map but is probably needed in order to ensure
70 /// smooth transitions between chunks in LoD view. Additionally, when we
71 /// start using the shadow information to do local lighting on the world
72 /// map, we'll want a quick way to test where we can go out of shadow at
73 /// arbitrary heights (since the player and other entities cajn find
74 /// themselves far from the ground at times). While this is only an
75 /// approximation to a proper distance map, hopefully it will give us
76 /// something that feels reasonable enough for Veloren's style.
77 ///
78 /// NOTE: On compression.
79 ///
80 /// Horizon mapping has a lot of advantages for height maps (simple, easy to
81 /// understand, doesn't require any fancy math or approximation beyond
82 /// precision loss), though it loses a few of them by having to store
83 /// distance to occluder as well. However, just storing tons
84 /// and tons of regular shadow maps (153 for a full day cycle, stored at
85 /// irregular intervals) combined with clever explicit compression and
86 /// avoiding recording sharp local shadows (preferring retracing for
87 /// these), yielded a compression rate of under 3 bits per column! Since
88 /// we likely want to avoid per-column shadows for worlds of the sizes we
89 /// want, we'd still need to store *some* extra information to create
90 /// soft shadows, but it would still be nice to try to drive down our
91 /// size as much as possible given how compressible shadows of height
92 /// maps seem to be in practice. Therefore, we try to take advantage of the
93 /// way existing compression algorithms tend to work to see if we can
94 /// achieve significant gains without doing a lot of custom work.
95 ///
96 /// Specifically, since our rays are cast east/west, we expect that for each
97 /// row, the horizon angles in each direction should be sequences of
98 /// monotonically increasing values (as chunks approach a tall
99 /// occluder), followed by sequences of no shadow, repeated
100 /// until the end of the map. Monotonic sequences and same-byte sequences
101 /// are usually easy to compress and existing algorithms are more likely
102 /// to be able to deal with them than jumbled data. If we were to keep
103 /// both directions in the same vector, off-the-shelf compression would
104 /// probably be less effective.
105 ///
106 /// For related reasons, rather than storing distances as in a standard
107 /// distance map (which would lead to monotonically *decreasing* values
108 /// as we approached the occluder from a given direction), we store the
109 /// estimated *occluder height.* The idea here is that we replace the
110 /// monotonic sequences with constant sequences, which are extremely
111 /// straightforward to compress and mostly handled automatically by anything
112 /// that does run-length encoding (i.e. most off-the-shelf compression
113 /// algorithms).
114 ///
115 /// We still need to benchmark this properly, as there's no guarantee our
116 /// current compression algorithms will actually work well on this data
117 /// in practice. It's possible that some other permutation (e.g. more
118 /// bits reserved for "distance to occluder" in exchange for an even
119 /// more predictible sequence) would end up compressing better than storing
120 /// angles, or that we don't need as much precision as we currently have
121 /// (256 possible angles).
122 pub horizons: [(Vec<u8>, Vec<u8>); 2],
123 pub sites: Vec<Marker>,
124 pub possible_starting_sites: Vec<SiteId>,
125 pub pois: Vec<PoiInfo>,
126 /// Default chunk (representing the ocean outside the map bounds). Sea
127 /// level (used to provide a base altitude) is the lower bound of this
128 /// chunk.
129 pub default_chunk: Arc<TerrainChunk>,
130}
131
132pub type SiteId = common::trade::SiteId;
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct EconomyInfo {
136 pub id: SiteId,
137 pub population: u32,
138 pub stock: HashMap<Good, f32>,
139 pub labor_values: HashMap<Good, f32>,
140 pub values: HashMap<Good, f32>,
141 pub labors: Vec<f32>,
142 pub last_exports: HashMap<Good, f32>,
143 pub resources: HashMap<Good, f32>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct PoiInfo {
148 pub kind: PoiKind,
149 pub wpos: Vec2<i32>,
150 pub name: String,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
154#[repr(u8)]
155pub enum PoiKind {
156 Peak(u32),
157 Lake(u32),
158}