wowlib 0.0.0
Read & write World of Warcraft client files — a C++26 core
Loading...
Searching...
No Matches
liquid.hpp
Go to the documentation of this file.
1#pragma once
2
9
10#include <bit>
11#include <cstddef>
12#include <cstdint>
13#include <cstring>
14#include <format>
15#include <span>
16#include <vector>
17
18#include <welder/vocabulary.hpp>
19
21#include <wowlib/core/error.hpp>
23
25 // --- MLIQ -------------------------------------------------------------------
26
27 struct [[
28 =welder::weld,
29 =welder::doc(R"(
30 The magma/slime reading of an MLIQ vertex (8 bytes): two int16 texture
31 coordinates sharing the layout with the water reading (SMOLVert). Obtain
32 one with SMOLVert.as_magma(); which reading is correct is set by the
33 group's liquid type, not stored per vertex.)")
34 ]] SMOMVert {
35 [[=welder::doc("Texture coordinate s (the water flow1/flow2 bytes).")]]
36 std::int16_t s = 0;
37
38 [[=welder::doc("Texture coordinate t (the water flow1_pct/filler bytes).")]]
39 std::int16_t t = 0;
41 [[=welder::doc("Liquid surface height, shared with the water reading.")]]
42 float height = 0;
43 };
44
45 static_assert(sizeof(SMOMVert) == 0x8);
46
47 struct [[
48 =welder::weld,
49 =welder::doc(R"(
50 One MLIQ liquid vertex (8 bytes). The byte layout is fixed but its
51 interpretation depends on the group's liquid type: water/ocean read the
52 named flow fields; magma/slime reinterpret the first four bytes as two
53 int16 texture coordinates (s, t) — use as_magma()/set_magma() for that
54 reading. Both are 8 bytes, so round-trip is layout-agnostic — pick the
55 reading from the group's liquid type.)")
56 ]] SMOLVert {
57 [[=welder::doc("Water: flow velocity 1. Magma/slime: low byte of int16 s.")]
58 ]
59 std::uint8_t flow1 = 0;
60
61 [[=welder::doc("Water: flow velocity 2. Magma/slime: high byte of int16 s.")
62 ]]
63 std::uint8_t flow2 = 0;
64
65 [[=welder::doc(
66 "Water: flow-1 percentage. Magma/slime: low byte of int16 t.")]]
67 std::uint8_t flow1Pct = 0;
68
69 [[=welder::doc("Water: filler. Magma/slime: high byte of int16 t.")]]
70 std::uint8_t filler = 0;
71
72 [[=welder::doc("Liquid surface height at this vertex.")]]
73 float height = 0;
75 [[nodiscard]]
76 [[=welder::doc("Reinterpret this vertex under the magma/slime reading "
77 "(int16 s, t texcoords). A pure byte reinterpretation; use "
78 "it only when the group's liquid type is magma or slime.")]]
79 SMOMVert asMagma() const { return std::bit_cast<SMOMVert>(*this); }
81 [[=welder::doc("Overwrite this vertex's bytes from a magma/slime reading.")]
82 ]
83 void setMagma(const SMOMVert& magma) {
84 *this = std::bit_cast<SMOLVert>(magma);
85 }
86 };
87
88 static_assert(sizeof(SMOLVert) == 0x8);
89
90 struct [[
91 =welder::weld,
92 =welder::doc("One MLIQ tile flag byte: bits 0-5 the legacy liquid type, "
93 "bit 6 fishable, bit 7 shared with an adjacent group.")
94 ]] SMOLTile {
95 [[=welder::doc("Packed liquid-type / fishable / shared flags.")]]
96 std::uint8_t flags = 0;
97 };
98
99 static_assert(sizeof(SMOLTile) == 0x1);
100
101 struct [[
102 =welder::weld,
103 =welder::doc(R"(
104 Structured MLIQ liquid data: a vertex grid and a tile-flag grid with a
105 base position and material id. The on-disk header is 30 bytes (the
106 uint16 material id sits directly after a 12-byte vector, so the header
107 is read field-by-field, not memcpy'd) followed by verts_dim.x*verts_dim.y
108 vertices and tiles_dim.x*tiles_dim.y tile bytes.)")
109 ]] MLIQData {
110 [[=welder::doc("Vertex grid dimensions (xverts, yverts).")]]
112
113 [[=welder::doc("Tile grid dimensions (xtiles, ytiles); one less than the "
114 "vertex dimensions in each axis.")]]
116
117 [[=welder::doc("Grid origin in group space.")]]
118 C3Vector baseCoords{};
120 [[=welder::doc("Liquid material id: an MOMT index, or a LiquidType id when "
121 "the root's use_liquid_type_dbc_id flag is set.")]]
122 std::uint16_t materialId = 0;
123
124 [[=welder::doc("verts_dim.x * verts_dim.y liquid vertices, row-major."),
125 =welder::mark::no_reassign]]
126 std::vector<SMOLVert> vertices;
127
128 [[=welder::doc("tiles_dim.x * tiles_dim.y tile flag bytes, row-major."),
129 =welder::mark::no_reassign]]
130 std::vector<SMOLTile> tiles;
131
135 static constexpr std::size_t HeaderSize = 30;
136
143 [[=welder::mark::exclude]]
144 Result<void> read(std::span<const std::byte> payload) {
145 if (payload.size() < HeaderSize)
147 std::format("MLIQ header needs {} bytes, got {}",
148 HeaderSize, payload.size()));
149
150 std::memcpy(&vertsDim, payload.data() + 0, sizeof vertsDim);
151 std::memcpy(&tilesDim, payload.data() + 8, sizeof tilesDim);
152 std::memcpy(&baseCoords, payload.data() + 16, sizeof baseCoords);
153 std::memcpy(&materialId, payload.data() + 28, sizeof materialId);
154
155 const std::size_t nVerts =
156 static_cast<std::size_t>(vertsDim.x) * static_cast<std::size_t>(
157 vertsDim.y);
158 const std::size_t nTiles =
159 static_cast<std::size_t>(tilesDim.x) * static_cast<std::size_t>(
160 tilesDim.y);
161 const std::size_t need =
162 HeaderSize + nVerts * sizeof(SMOLVert) + nTiles * sizeof(SMOLTile);
163 if (payload.size() < need)
165 std::format("MLIQ body needs {} bytes ({} verts, {} "
166 "tiles), got {}",
167 need, nVerts, nTiles, payload.size()));
169 vertices.resize(nVerts);
170 std::memcpy(vertices.data(), payload.data() + HeaderSize,
171 nVerts * sizeof(SMOLVert));
172 tiles.resize(nTiles);
173 std::memcpy(tiles.data(),
174 payload.data() + HeaderSize + nVerts * sizeof(SMOLVert),
175 nTiles * sizeof(SMOLTile));
176 return {};
177 }
178
183 [[=welder::mark::exclude]]
184 Result<void> write(FileBuffer& out) const {
185 const auto put = [&out](const auto& field) {
186 const auto* b = reinterpret_cast<const std::byte*>(&field);
187 out.insert(out.end(), b, b + sizeof field);
188 };
189 put(vertsDim);
190 put(tilesDim);
191 put(baseCoords);
192 put(materialId);
193
194 const auto* vb = reinterpret_cast<const std::byte*>(vertices.data());
195 out.insert(out.end(), vb, vb + vertices.size() * sizeof(SMOLVert));
196 const auto* tb = reinterpret_cast<const std::byte*>(tiles.data());
197 out.insert(out.end(), tb, tb + tiles.size() * sizeof(SMOLTile));
198 return {};
199 }
200
201 [[nodiscard]]
202 [[=welder::getter, =welder::doc(
203 "Whether this group carries no liquid grid.")]]
204 bool empty() const { return vertices.empty(); }
205 };
206}
The owning byte buffer file contents are read into.
The error-handling vocabulary: ErrorCode, Error and the Result<T> alias every fallible wowlib operati...
WMO group-file chunk binary structs (the MOGP header, render batches, BSP nodes, group lights) and th...
Definition geometry.hpp:18
std::expected< T, Error > Result
Every fallible wowlib operation returns Result<T>; bindings translate the error branch into a target-...
Definition error.hpp:100
std::unexpected< Error > makeError(ErrorCode code, std::string message, std::uint32_t nativeError=0)
Shorthand for constructing the error branch of a Result.
Definition error.hpp:107
@ ChunkTruncated
A chunk header or payload overruns the file buffer.
Definition error.hpp:36
std::vector< std::byte > FileBuffer
Owning byte buffer for file contents read out of a client storage.
Definition buffer.hpp:16
A 2D integer vector.
Definition types.hpp:39
A 3D float vector — the workhorse: positions, normals, rotations-as-Euler-degrees in placements.
Definition types.hpp:49
static constexpr std::size_t HeaderSize
The on-disk header size: four 4-byte ints, a 12-byte vector and a 2-byte material id,...
Definition liquid.hpp:119
Result< void > read(std::span< const std::byte > payload)
Decode the MLIQ payload (the serializer's read hook).
Definition liquid.hpp:128
Result< void > write(FileBuffer &out) const
Encode the MLIQ payload (the serializer's write hook): the 30-byte header field-by-field,...
Definition liquid.hpp:168
float height
Liquid surface height, shared with the water reading.
Definition liquid.hpp:35
std::int16_t t
Texture coordinate t (the water flow1_pct/filler bytes).
Definition liquid.hpp:32
std::int16_t s
Texture coordinate s (the water flow1/flow2 bytes).
Definition liquid.hpp:29
The binary-level math and color primitives shared across WoW file formats (wowdev....