rafx_plugins/assets/ldtk/
importer.rs

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        //
42        // If the vertex count exceeds what a u16 index buffer support, start a new draw call
43        //
44
45        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        //
82        // Handle flipping the image
83        //
84        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        //
93        // Insert vertex data
94        //
95        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        //
113        // Insert index data
114        //
115        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        //
123        // Update the draw call to include the new data
124        //
125        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        //
143        // Read the file
144        //
145        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        //
162        // Read the file
163        //
164        let source = std::fs::read_to_string(context.path)?;
165        // We don't use this immediately but at least make sure it's well formed
166        let _project: ldtk_rust::Project = serde_json::from_str(&source)?;
167
168        //
169        // Create the default asset
170        //
171        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        //
177        // Return the created objects
178        //
179        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        //
212        // Read import data
213        //
214        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        // CPU-form of tileset data
221        let mut tilesets_temp = FnvHashMap::default();
222
223        // The one material we always use for tile layers
224        //let material_handle = make_handle_from_str("ae8320e2-9d84-432d-879b-e34ebef90a82")?;
225
226        for tileset in &project.defs.tilesets {
227            //
228            // Create a material instance
229            //
230            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", // tile_layer.material
243                        )?));
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            //TODO: Support for levels in separate files
306            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                    //TODO: Data drive this from the asset
321                    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                //
363                // Create a vertex buffer for the level
364                //
365                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                //
374                // Create an index buffer for the level
375                //
376                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        //Future: Might produce jobs per-platform
474        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}