1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
// liquid_converter.rs - Convert between MCLQ and MH2O formats
use crate::chunk::*;
use crate::error::Result;
use crate::mcnk_subchunks::*;
use crate::mh2o::Mh2oChunk as AdvancedMh2oChunk;
use crate::mh2o::{
Mh2oEntry, Mh2oHeader, Mh2oInstance, Mh2oRenderMask, WaterLevelData, WaterVertex,
WaterVertexData,
};
/// Convert from MCLQ (pre-WotLK) to MH2O (WotLK+) format
pub fn convert_mclq_to_mh2o(
mclq_chunks: &[MclqSubchunk],
chunk_positions: &[McnkChunk],
) -> Result<AdvancedMh2oChunk> {
// Each MCNK can have one MCLQ chunk, and there are 256 MCNKs
// MH2O has 256 entries, one for each MCNK
let mut mh2o_entries = Vec::with_capacity(256);
// Process each MCNK/MCLQ pair
for (i, chunk) in chunk_positions.iter().enumerate() {
// Get the corresponding MCLQ if it exists
let mclq = if i < mclq_chunks.len() {
Some(&mclq_chunks[i])
} else {
None
};
// Create the MH2O entry for this chunk
let entry = if let Some(mclq) = mclq {
// MCLQ exists, convert it
convert_chunk_mclq_to_mh2o(mclq, chunk, i)
} else {
// No MCLQ for this chunk, create an empty entry
create_empty_mh2o_entry()
};
mh2o_entries.push(entry);
}
// Fill any remaining entries
while mh2o_entries.len() < 256 {
mh2o_entries.push(create_empty_mh2o_entry());
}
Ok(AdvancedMh2oChunk {
chunks: mh2o_entries,
})
}
/// Convert a single MCLQ chunk to MH2O format
fn convert_chunk_mclq_to_mh2o(
mclq: &MclqSubchunk,
chunk: &McnkChunk,
_chunk_index: usize,
) -> Mh2oEntry {
// Create the header for this entry
let header = Mh2oHeader {
offset_instances: 0, // Will be filled during serialization
layer_count: if mclq.vertices.is_empty() { 0 } else { 1 },
offset_attributes: 0, // Will be filled during serialization
};
// If there's no liquid data, return an empty entry
if mclq.vertices.is_empty() {
return Mh2oEntry {
header,
instances: Vec::new(),
attributes: None,
render_mask: None,
};
}
// Calculate min/max heights
let (min_height, max_height) = calculate_min_max_heights(mclq, chunk);
// Create water instance
let instance = create_water_instance(mclq, min_height, max_height);
// Create render mask
let render_mask = create_render_mask(mclq);
Mh2oEntry {
header,
instances: vec![instance],
attributes: None,
render_mask,
}
}
/// Create an empty MH2O entry
fn create_empty_mh2o_entry() -> Mh2oEntry {
Mh2oEntry {
header: Mh2oHeader {
offset_instances: 0,
layer_count: 0,
offset_attributes: 0,
},
instances: Vec::new(),
attributes: None,
render_mask: None,
}
}
/// Calculate minimum and maximum heights from MCLQ
fn calculate_min_max_heights(mclq: &MclqSubchunk, _chunk: &McnkChunk) -> (f32, f32) {
// Start with the base height
let mut min_height = mclq.base_height;
let mut max_height = mclq.base_height;
// Factor in vertex depths
for vertex in &mclq.vertices {
let height = mclq.base_height - vertex.depth;
min_height = min_height.min(height);
max_height = max_height.max(height);
}
(min_height, max_height)
}
/// Create a water instance from MCLQ data
fn create_water_instance(mclq: &MclqSubchunk, min_height: f32, max_height: f32) -> Mh2oInstance {
// Extract liquid type from vertices (use the first one)
let liquid_type = if !mclq.vertices.is_empty() {
mclq.vertices[0].liquid_id
} else {
0 // Default liquid type
};
// Extract flow data from vertices
let vertex_data = if mclq.x_vertices > 0 && mclq.y_vertices > 0 {
// Create water vertices
let mut vertices = Vec::with_capacity(mclq.vertices.len());
for vertex in &mclq.vertices {
vertices.push(WaterVertex {
depth: vertex.depth,
flow: [0, 0], // MCLQ doesn't have flow data
});
}
Some(WaterVertexData {
offset_vertex_data: 0, // Will be filled during serialization
x_vertices: mclq.x_vertices as u8,
y_vertices: mclq.y_vertices as u8,
vertices: Some(vertices),
})
} else {
None
};
// Determine level data type
let level_data = if vertex_data.is_some() {
// Variable height water
WaterLevelData::Variable {
min_height,
max_height,
offset_height_map: 0, // Will be filled during serialization
heights: None, // Will be generated during serialization
}
} else {
// Uniform height water
WaterLevelData::Uniform {
min_height,
max_height,
}
};
Mh2oInstance {
liquid_type,
liquid_object: 0, // Default liquid object ID
x_offset: 0,
y_offset: 0,
width: 8, // Full chunk coverage (8 cells = 9 vertices)
height: 8, // Full chunk coverage (8 cells = 9 vertices)
level_data,
vertex_data,
attributes: Vec::new(), // No attributes in older versions
}
}
/// Create a render mask from MCLQ data
fn create_render_mask(mclq: &MclqSubchunk) -> Option<Mh2oRenderMask> {
if mclq.vertices.is_empty() {
return None;
}
// In MCLQ, we don't have explicit render masks
// We need to derive them from the vertex data
// The render mask is an 8x8 grid, 1 bit per cell
// We'll set bits for areas that have liquid
let mut mask = [0u8; 8];
// If MCLQ exists, we'll assume the entire chunk has liquid
// A more accurate conversion would look at vertex depths
// and only set bits for areas with non-zero depth
// Set all bits to indicate liquid is present everywhere
mask.fill(0xFF);
Some(Mh2oRenderMask { mask })
}
/// Convert from MH2O (WotLK+) to MCLQ (pre-WotLK) format
pub fn convert_mh2o_to_mclq(
mh2o: &AdvancedMh2oChunk,
chunk_positions: &[McnkChunk],
) -> Result<Vec<MclqSubchunk>> {
let mut mclq_chunks = Vec::with_capacity(chunk_positions.len());
// Process each MCNK/MH2O pair
for (i, chunk) in chunk_positions.iter().enumerate() {
// Get the corresponding MH2O entry
let mh2o_entry = if i < mh2o.chunks.len() {
&mh2o.chunks[i]
} else {
// No MH2O entry for this chunk
mclq_chunks.push(create_empty_mclq());
continue;
};
// Convert the MH2O entry to MCLQ
let mclq = convert_chunk_mh2o_to_mclq(mh2o_entry, chunk);
mclq_chunks.push(mclq);
}
Ok(mclq_chunks)
}
/// Convert a single MH2O entry to MCLQ format
fn convert_chunk_mh2o_to_mclq(mh2o_entry: &Mh2oEntry, _chunk: &McnkChunk) -> MclqSubchunk {
// If there's no liquid data, return an empty MCLQ
if mh2o_entry.instances.is_empty() {
return create_empty_mclq();
}
// Get the first liquid instance
let instance = &mh2o_entry.instances[0];
// Extract height information
let (base_height, _min_height, _max_height) = match &instance.level_data {
WaterLevelData::Uniform {
min_height,
max_height,
} => (*max_height, *min_height, *max_height),
WaterLevelData::Variable {
min_height,
max_height,
..
} => (*max_height, *min_height, *max_height),
};
// Extract vertex data
let (x_vertices, y_vertices, vertices) = if let Some(ref vertex_data) = instance.vertex_data {
if let Some(ref vertices) = vertex_data.vertices {
(
vertex_data.x_vertices as u32,
vertex_data.y_vertices as u32,
vertices.as_slice(),
)
} else {
(0, 0, &[] as &[WaterVertex])
}
} else {
(0, 0, &[] as &[WaterVertex])
};
// Convert water vertices to MCLQ format
let mut mclq_vertices = Vec::with_capacity(vertices.len());
for vertex in vertices {
mclq_vertices.push(LiquidVertex {
depth: vertex.depth,
liquid_id: instance.liquid_type,
flags: 0, // No flags in newer versions
});
}
// Create the MCLQ
MclqSubchunk {
x_vertices,
y_vertices,
base_height,
vertices: mclq_vertices,
}
}
/// Create an empty MCLQ chunk
fn create_empty_mclq() -> MclqSubchunk {
MclqSubchunk {
x_vertices: 0,
y_vertices: 0,
base_height: 0.0,
vertices: Vec::new(),
}
}