veloren_common_net/msg/world_msg.rs
1use common::{grid::Grid, 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<SiteInfo>,
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 SiteInfo {
136 pub id: SiteId,
137 pub kind: SiteKind,
138 pub wpos: Vec2<i32>,
139 pub name: Option<String>,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143#[repr(u8)]
144pub enum SiteKind {
145 Town,
146 Castle,
147 Cave,
148 Tree,
149 Gnarling,
150 GliderCourse,
151 ChapelSite,
152 Terracotta,
153 Bridge,
154 Adlet,
155 Haniwa,
156 DwarvenMine,
157 Cultist,
158 Sahagin,
159 VampireCastle,
160 Myrmidon,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize)]
164pub struct EconomyInfo {
165 pub id: SiteId,
166 pub population: u32,
167 pub stock: HashMap<Good, f32>,
168 pub labor_values: HashMap<Good, f32>,
169 pub values: HashMap<Good, f32>,
170 pub labors: Vec<f32>,
171 pub last_exports: HashMap<Good, f32>,
172 pub resources: HashMap<Good, f32>,
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct PoiInfo {
177 pub kind: PoiKind,
178 pub wpos: Vec2<i32>,
179 pub name: String,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize)]
183#[repr(u8)]
184pub enum PoiKind {
185 Peak(u32),
186 Lake(u32),
187}