1use crate::assets::ldtk::{
2 LdtkAssetData, LdtkLayerData, LdtkLayerDrawCallData, LdtkLevelData, LdtkTileSet, LevelUid,
3};
4use crate::features::tile_layer::TileLayerVertex;
5use crate::schema::{LdtkAssetAccessor, LdtkAssetRecord, LdtkImportDataRecord};
6use fnv::FnvHashMap;
7use hydrate_base::{ArtifactId, AssetId, Handle};
8use hydrate_data::{Record, RecordAccessor};
9use hydrate_pipeline::{
10 AssetPlugin, AssetPluginSetupContext, Builder, BuilderContext, ImportContext, Importer,
11 JobInput, JobOutput, JobProcessor, PipelineResult, RunContext, ScanContext,
12};
13use ldtk_rust::{LayerInstance, Level, TileInstance};
14use rafx::api::RafxResourceType;
15use rafx::assets::{
16 BufferAssetData, MaterialAsset, MaterialInstanceAssetData, MaterialInstanceSlotAssignment,
17};
18use serde::{Deserialize, Serialize};
19use type_uuid::*;
20use uuid::Uuid;
21
22#[derive(Clone, Debug)]
23pub struct HydrateLdtkTileSetTemp {
24 pub image: AssetId,
25 pub material_instance: ArtifactId,
26 pub image_width: u32,
27 pub image_height: u32,
28}
29
30fn generate_draw_data(
31 level: &Level,
32 layer: &LayerInstance,
33 z_pos: f32,
34 tile_instances: &[TileInstance],
35 tileset: &HydrateLdtkTileSetTemp,
36 vertex_data: &mut Vec<TileLayerVertex>,
37 index_data: &mut Vec<u16>,
38 layer_draw_call_data: &mut Vec<LdtkLayerDrawCallData>,
39) {
40 for tile in tile_instances {
41 let mut vertex_count = (layer_draw_call_data
46 .last()
47 .map(|x| x.index_count)
48 .unwrap_or(0)
49 / 6)
50 * 4;
51 if layer_draw_call_data.is_empty() || vertex_count + 4 > std::u16::MAX as u32 {
52 layer_draw_call_data.push(LdtkLayerDrawCallData {
53 vertex_data_offset_in_bytes: (vertex_data.len()
54 * std::mem::size_of::<TileLayerVertex>())
55 as u32,
56 index_data_offset_in_bytes: (index_data.len() * std::mem::size_of::<u16>()) as u32,
57 index_count: 0,
58 z_pos,
59 });
60
61 vertex_count = 0;
62 }
63
64 let current_draw_call_data = layer_draw_call_data.last_mut().unwrap();
65
66 let flip_bits = tile.f;
67 let x_pos = (tile.px[0] + layer.px_total_offset_x + level.world_x) as f32;
68 let y_pos = -1.0 * (tile.px[1] + layer.px_total_offset_y + level.world_y) as f32;
69 let tileset_src_x_pos = tile.src[0];
70 let tileset_src_y_pos = tile.src[1];
71 let tile_width = layer.grid_size as f32;
72 let tile_height = layer.grid_size as f32;
73
74 let mut texture_rect_left = tileset_src_x_pos as f32 / tileset.image_width as f32;
75 let mut texture_rect_right =
76 (tileset_src_x_pos as f32 + tile_width) / tileset.image_width as f32;
77 let mut texture_rect_top =
78 (tileset_src_y_pos as f32 + tile_height) / tileset.image_height as f32;
79 let mut texture_rect_bottom = (tileset_src_y_pos as f32) / tileset.image_height as f32;
80
81 if (flip_bits & 1) == 1 {
85 std::mem::swap(&mut texture_rect_left, &mut texture_rect_right);
86 }
87
88 if (flip_bits & 2) == 2 {
89 std::mem::swap(&mut texture_rect_top, &mut texture_rect_bottom);
90 }
91
92 vertex_data.push(TileLayerVertex {
96 position: [x_pos + tile_width, y_pos + tile_height, z_pos],
97 uv: [texture_rect_right, texture_rect_bottom],
98 });
99 vertex_data.push(TileLayerVertex {
100 position: [x_pos, y_pos + tile_height, z_pos],
101 uv: [texture_rect_left, texture_rect_bottom],
102 });
103 vertex_data.push(TileLayerVertex {
104 position: [x_pos + tile_width, y_pos, z_pos],
105 uv: [texture_rect_right, texture_rect_top],
106 });
107 vertex_data.push(TileLayerVertex {
108 position: [x_pos, y_pos, z_pos],
109 uv: [texture_rect_left, texture_rect_top],
110 });
111
112 index_data.push(vertex_count as u16 + 0);
116 index_data.push(vertex_count as u16 + 1);
117 index_data.push(vertex_count as u16 + 2);
118 index_data.push(vertex_count as u16 + 2);
119 index_data.push(vertex_count as u16 + 1);
120 index_data.push(vertex_count as u16 + 3);
121
122 current_draw_call_data.index_count += 6;
126 }
127}
128
129#[derive(TypeUuid, Default)]
130#[uuid = "7d507fac-ccb8-47fb-a4af-15da5e751601"]
131pub struct LdtkImporter;
132
133impl Importer for LdtkImporter {
134 fn supported_file_extensions(&self) -> &[&'static str] {
135 &["ldtk"]
136 }
137
138 fn scan_file(
139 &self,
140 context: ScanContext,
141 ) -> PipelineResult<()> {
142 let source = std::fs::read_to_string(context.path)?;
146 let project: ldtk_rust::Project = serde_json::from_str(&source)?;
147
148 let importable = context.add_default_importable::<LdtkAssetRecord>()?;
149
150 for tileset in &project.defs.tilesets {
151 importable.add_path_reference(&tileset.rel_path)?;
152 }
153
154 Ok(())
155 }
156
157 fn import_file(
158 &self,
159 context: ImportContext,
160 ) -> PipelineResult<()> {
161 let source = std::fs::read_to_string(context.path)?;
165 let _project: ldtk_rust::Project = serde_json::from_str(&source)?;
167
168 let default_asset = LdtkAssetRecord::new_builder(context.schema_set);
172
173 let import_data = LdtkImportDataRecord::new_builder(context.schema_set);
174 import_data.json_data().set(source)?;
175
176 context
180 .add_default_importable(default_asset.into_inner()?, Some(import_data.into_inner()?));
181 Ok(())
182 }
183}
184
185#[derive(Hash, Serialize, Deserialize)]
186pub struct LdtkJobInput {
187 pub asset_id: AssetId,
188}
189impl JobInput for LdtkJobInput {}
190
191#[derive(Serialize, Deserialize)]
192pub struct LdtkJobOutput {}
193impl JobOutput for LdtkJobOutput {}
194
195#[derive(Default, TypeUuid)]
196#[uuid = "2e4e713e-71ef-4972-bb6b-827a4d291ccb"]
197pub struct LdtkJobProcessor;
198
199impl JobProcessor for LdtkJobProcessor {
200 type InputT = LdtkJobInput;
201 type OutputT = LdtkJobOutput;
202
203 fn version(&self) -> u32 {
204 1
205 }
206
207 fn run<'a>(
208 &self,
209 context: &'a RunContext<'a, Self::InputT>,
210 ) -> PipelineResult<LdtkJobOutput> {
211 let imported_data =
215 context.imported_data::<LdtkImportDataRecord>(context.input.asset_id)?;
216
217 let json_str = imported_data.json_data().get()?;
218 let project: ldtk_rust::Project = serde_json::from_str(&json_str)?;
219
220 let mut tilesets_temp = FnvHashMap::default();
222
223 for tileset in &project.defs.tilesets {
227 let image_object_id = context
231 .data_set
232 .resolve_path_reference(context.input.asset_id, &tileset.rel_path)?
233 .ok_or("Could not find asset ID assocaited with path")?;
234
235 let material_instance_artifact_name = format!("mi_{}", tileset.uid);
236 let material_instance_artifact_id = context.produce_artifact_with_handles(
237 context.input.asset_id,
238 Some(material_instance_artifact_name),
239 |handle_factory| {
240 let material_handle: Handle<MaterialAsset> = handle_factory
241 .make_handle_to_default_artifact(AssetId::from_uuid(Uuid::parse_str(
242 "989f8987-c8d7-4d54-90ce-1af70fddc6ac", )?));
244
245 let image_handle =
246 handle_factory.make_handle_to_default_artifact(image_object_id);
247
248 let mut slot_assignments = vec![];
249 slot_assignments.push(MaterialInstanceSlotAssignment {
250 slot_name: "tilemap_texture".to_string(),
251 array_index: 0,
252 image: Some(image_handle.clone()),
253 sampler: None,
254 buffer_data: None,
255 });
256
257 Ok(MaterialInstanceAssetData {
258 material: material_handle.clone(),
259 slot_assignments,
260 })
261 },
262 )?;
263
264 let image_width = tileset.px_wid as _;
265 let image_height = tileset.px_hei as _;
266
267 tilesets_temp.insert(
268 tileset.uid,
269 HydrateLdtkTileSetTemp {
270 image: image_object_id,
271 material_instance: material_instance_artifact_id,
272 image_width,
273 image_height,
274 },
275 );
276 }
277
278 #[derive(Serialize, Deserialize, Clone, Debug)]
279 pub struct HydrateLdtkLayerDataTemp {
280 pub material_instance: ArtifactId,
281 pub draw_call_data: Vec<LdtkLayerDrawCallData>,
282 pub z_pos: f32,
283 pub world_x_pos: i64,
284 pub world_y_pos: i64,
285 pub grid_width: i64,
286 pub grid_height: i64,
287 pub grid_size: i64,
288 }
289
290 #[derive(Clone, Debug)]
291 pub struct HydrateLdtkLevelDataTemp {
292 pub layer_data: Vec<HydrateLdtkLayerDataTemp>,
293 pub vertex_data: Option<hydrate_pipeline::AssetArtifactIdPair>,
294 pub index_data: Option<hydrate_pipeline::AssetArtifactIdPair>,
295 }
296
297 let mut levels_temp = FnvHashMap::<LevelUid, HydrateLdtkLevelDataTemp>::default();
298
299 for level in &project.levels {
300 let mut vertex_data = Vec::<TileLayerVertex>::default();
301 let mut index_data = Vec::<u16>::default();
302
303 let mut layer_data = Vec::default();
304
305 for (layer_index, layer) in level.layer_instances.as_ref().unwrap().iter().enumerate() {
307 let tileset_uid = if let Some(tileset_uid) = layer.override_tileset_uid {
308 Some(tileset_uid)
309 } else if let Some(tileset_uid) = layer.tileset_def_uid {
310 Some(tileset_uid)
311 } else {
312 None
313 };
314
315 if let Some(tileset_uid) = tileset_uid {
316 let tileset = &tilesets_temp[&tileset_uid];
317
318 let mut layer_draw_call_data: Vec<LdtkLayerDrawCallData> = Vec::default();
319
320 let z_pos = ((level.layer_instances.as_ref().unwrap().len() - layer_index - 1)
322 * 10) as f32;
323
324 generate_draw_data(
325 level,
326 layer,
327 z_pos,
328 &layer.grid_tiles,
329 tileset,
330 &mut vertex_data,
331 &mut index_data,
332 &mut layer_draw_call_data,
333 );
334 generate_draw_data(
335 level,
336 layer,
337 z_pos,
338 &layer.auto_layer_tiles,
339 tileset,
340 &mut vertex_data,
341 &mut index_data,
342 &mut layer_draw_call_data,
343 );
344
345 layer_data.push(HydrateLdtkLayerDataTemp {
346 material_instance: tileset.material_instance,
347 draw_call_data: layer_draw_call_data,
348 z_pos,
349 world_x_pos: level.world_x + layer.px_total_offset_x,
350 world_y_pos: level.world_y + layer.px_total_offset_y,
351 grid_width: layer.c_wid,
352 grid_height: layer.c_hei,
353 grid_size: layer.grid_size,
354 })
355 }
356 }
357
358 let mut vertex_buffer_artifact = None;
359 let mut index_buffer_artifact = None;
360
361 if !vertex_data.is_empty() & !index_data.is_empty() {
362 let vertex_buffer_asset_data =
366 BufferAssetData::from_vec(RafxResourceType::VERTEX_BUFFER, &vertex_data);
367 let vb_artifact = context.produce_artifact(
368 context.input.asset_id,
369 Some(format!("vertex_buffer,{:?}", level.uid)),
370 vertex_buffer_asset_data,
371 )?;
372
373 let index_buffer_asset_data =
377 BufferAssetData::from_vec(RafxResourceType::INDEX_BUFFER, &index_data);
378 let ib_artifact = context.produce_artifact(
379 context.input.asset_id,
380 Some(format!("index_buffer,{:?}", level.uid)),
381 index_buffer_asset_data,
382 )?;
383
384 vertex_buffer_artifact = Some(vb_artifact);
385 index_buffer_artifact = Some(ib_artifact);
386 }
387
388 let old = levels_temp.insert(
389 level.uid,
390 HydrateLdtkLevelDataTemp {
391 layer_data,
392 vertex_data: vertex_buffer_artifact,
393 index_data: index_buffer_artifact,
394 },
395 );
396 assert!(old.is_none());
397 }
398
399 context.produce_default_artifact_with_handles(
400 context.input.asset_id,
401 |handle_factory| {
402 let mut tilesets = FnvHashMap::default();
403 for (uid, tileset) in tilesets_temp {
404 tilesets.insert(
405 uid,
406 LdtkTileSet {
407 material_instance: handle_factory.make_handle_to_artifact_raw(
408 context.input.asset_id,
409 tileset.material_instance,
410 ),
411 image: handle_factory.make_handle_to_default_artifact(tileset.image),
412 image_width: tileset.image_width,
413 image_height: tileset.image_height,
414 },
415 );
416 }
417
418 let mut levels = FnvHashMap::default();
419 for (uid, level) in levels_temp {
420 let layers = level
421 .layer_data
422 .into_iter()
423 .map(|layer| LdtkLayerData {
424 material_instance: handle_factory.make_handle_to_artifact_raw(
425 context.input.asset_id,
426 layer.material_instance,
427 ),
428 draw_call_data: layer.draw_call_data,
429 z_pos: layer.z_pos,
430 world_x_pos: layer.world_x_pos,
431 world_y_pos: layer.world_y_pos,
432 grid_width: layer.grid_width,
433 grid_height: layer.grid_height,
434 grid_size: layer.grid_size,
435 })
436 .collect();
437
438 levels.insert(
439 uid,
440 LdtkLevelData {
441 layer_data: layers,
442 vertex_data: level
443 .vertex_data
444 .map(|x| handle_factory.make_handle_to_artifact(x)),
445 index_data: level
446 .index_data
447 .map(|x| handle_factory.make_handle_to_artifact(x)),
448 },
449 );
450 }
451
452 Ok(LdtkAssetData { tilesets, levels })
453 },
454 )?;
455
456 Ok(LdtkJobOutput {})
457 }
458}
459
460#[derive(TypeUuid, Default)]
461#[uuid = "a0cc5ab1-430c-4052-b082-074f63539fbe"]
462pub struct LdtkBuilder {}
463
464impl Builder for LdtkBuilder {
465 fn asset_type(&self) -> &'static str {
466 LdtkAssetAccessor::schema_name()
467 }
468
469 fn start_jobs(
470 &self,
471 context: BuilderContext,
472 ) -> PipelineResult<()> {
473 context.enqueue_job::<LdtkJobProcessor>(
475 context.data_set,
476 context.schema_set,
477 context.job_api,
478 LdtkJobInput {
479 asset_id: context.asset_id,
480 },
481 )?;
482 Ok(())
483 }
484}
485
486pub struct LdtkAssetPlugin;
487
488impl AssetPlugin for LdtkAssetPlugin {
489 fn setup(context: AssetPluginSetupContext) {
490 context.importer_registry.register_handler::<LdtkImporter>();
491 context.builder_registry.register_handler::<LdtkBuilder>();
492 context
493 .job_processor_registry
494 .register_job_processor::<LdtkJobProcessor>();
495 }
496}