|
| 1 | +# Implementation Guide |
| 2 | + |
| 3 | +This guide is for developers who want to implement the actual file parsing and conversion logic for the EditorImportPlugins. |
| 4 | + |
| 5 | +## Current Status |
| 6 | + |
| 7 | +The EditorImportPlugin infrastructure is complete and functional. The plugins are registered with Godot and will be called when the appropriate file types are added to a project. However, the actual file parsing and conversion is not yet implemented - the `import()` methods currently return `Error::OK` without doing any actual work. |
| 8 | + |
| 9 | +## What Needs to be Implemented |
| 10 | + |
| 11 | +### 1. Mesh Importer (`src/importers/mesh_importer.rs`) |
| 12 | + |
| 13 | +The `import()` method needs to: |
| 14 | + |
| 15 | +1. **Read the source file** |
| 16 | + ```rust |
| 17 | + // Use league-toolkit to parse the mesh file |
| 18 | + use league_toolkit::mesh::{Skn, Scb, Sco}; |
| 19 | + |
| 20 | + // Determine file type from extension |
| 21 | + let extension = source_file.to_string().split('.').last().unwrap(); |
| 22 | + let mesh_data = match extension { |
| 23 | + "skn" => parse_skn_file(&source_file), |
| 24 | + "scb" => parse_scb_file(&source_file), |
| 25 | + "sco" => parse_sco_file(&source_file), |
| 26 | + _ => return Error::ERR_FILE_UNRECOGNIZED, |
| 27 | + }; |
| 28 | + ``` |
| 29 | + |
| 30 | +2. **Convert to Godot's ArrayMesh format** |
| 31 | + ```rust |
| 32 | + use godot::classes::{ArrayMesh, Mesh as GodotMesh}; |
| 33 | + use godot::builtin::VariantArray; |
| 34 | + |
| 35 | + // Create ArrayMesh |
| 36 | + let mut array_mesh = ArrayMesh::new_gd(); |
| 37 | + |
| 38 | + // Convert mesh data to Godot arrays |
| 39 | + let mut arrays = VariantArray::new(); |
| 40 | + arrays.resize(ArrayMesh::ARRAY_MAX as usize); |
| 41 | + |
| 42 | + // Set vertex positions (ARRAY_VERTEX = 0) |
| 43 | + let vertices = PackedVector3Array::from(&mesh_data.vertices); |
| 44 | + arrays.set(ArrayMesh::ARRAY_VERTEX as usize, vertices.to_variant()); |
| 45 | + |
| 46 | + // Set normals (ARRAY_NORMAL = 1) |
| 47 | + if mesh_data.has_normals || generate_normals { |
| 48 | + let normals = calculate_or_use_normals(&mesh_data, generate_normals); |
| 49 | + arrays.set(ArrayMesh::ARRAY_NORMAL as usize, normals.to_variant()); |
| 50 | + } |
| 51 | + |
| 52 | + // Set UVs (ARRAY_TEX_UV = 4) |
| 53 | + let uvs = PackedVector2Array::from(&mesh_data.uvs); |
| 54 | + arrays.set(ArrayMesh::ARRAY_TEX_UV as usize, uvs.to_variant()); |
| 55 | + |
| 56 | + // Set indices (ARRAY_INDEX = 12) |
| 57 | + let indices = PackedInt32Array::from(&mesh_data.indices); |
| 58 | + arrays.set(ArrayMesh::ARRAY_INDEX as usize, indices.to_variant()); |
| 59 | + |
| 60 | + // Add surface to mesh |
| 61 | + array_mesh.add_surface_from_arrays( |
| 62 | + godot::classes::mesh::PrimitiveType::TRIANGLES, |
| 63 | + arrays, |
| 64 | + VariantArray::new(), |
| 65 | + Dictionary::new(), |
| 66 | + 0 |
| 67 | + ); |
| 68 | + ``` |
| 69 | + |
| 70 | +3. **Apply import options** |
| 71 | + ```rust |
| 72 | + // Apply scale |
| 73 | + if scale != 1.0 { |
| 74 | + // Transform all vertices by scale factor |
| 75 | + } |
| 76 | + |
| 77 | + // Generate tangents if requested |
| 78 | + if generate_tangents { |
| 79 | + array_mesh.surface_generate_tangents(0); |
| 80 | + } |
| 81 | + ``` |
| 82 | + |
| 83 | +4. **Save the resource** |
| 84 | + ```rust |
| 85 | + use godot::classes::ResourceSaver; |
| 86 | + |
| 87 | + let mut resource_saver = ResourceSaver::singleton(); |
| 88 | + let save_result = resource_saver.save( |
| 89 | + array_mesh.upcast(), |
| 90 | + format!("{}.{}", save_path, get_save_extension()).into() |
| 91 | + ); |
| 92 | + |
| 93 | + if save_result != Error::OK { |
| 94 | + godot_error!("Failed to save mesh resource: {:?}", save_result); |
| 95 | + return save_result; |
| 96 | + } |
| 97 | + ``` |
| 98 | + |
| 99 | +### 2. Texture Importer (`src/importers/texture_importer.rs`) |
| 100 | + |
| 101 | +The `import()` method needs to: |
| 102 | + |
| 103 | +1. **Read and parse the texture file** |
| 104 | + ```rust |
| 105 | + use league_toolkit::texture::Tex; |
| 106 | + |
| 107 | + // Parse the .tex file |
| 108 | + let tex_data = match Tex::from_file(&source_file.to_string()) { |
| 109 | + Ok(tex) => tex, |
| 110 | + Err(e) => { |
| 111 | + godot_error!("Failed to parse texture: {:?}", e); |
| 112 | + return Error::ERR_FILE_CORRUPT; |
| 113 | + } |
| 114 | + }; |
| 115 | + ``` |
| 116 | + |
| 117 | +2. **Convert to Godot Image** |
| 118 | + ```rust |
| 119 | + use godot::classes::{Image, ImageTexture}; |
| 120 | + |
| 121 | + // Create a Godot Image from the texture data |
| 122 | + let mut image = Image::new(); |
| 123 | + |
| 124 | + // Determine format and create image |
| 125 | + let format = match tex_data.format { |
| 126 | + // Map League texture formats to Godot formats |
| 127 | + TexFormat::DXT1 => Image::FORMAT_DXT1, |
| 128 | + TexFormat::DXT5 => Image::FORMAT_DXT5, |
| 129 | + TexFormat::RGBA8 => Image::FORMAT_RGBA8, |
| 130 | + // ... handle other formats |
| 131 | + }; |
| 132 | + |
| 133 | + image.create_from_data( |
| 134 | + tex_data.width as i32, |
| 135 | + tex_data.height as i32, |
| 136 | + tex_data.has_mipmaps, |
| 137 | + format, |
| 138 | + tex_data.pixel_data.into() |
| 139 | + ); |
| 140 | + ``` |
| 141 | + |
| 142 | +3. **Apply import settings** |
| 143 | + ```rust |
| 144 | + // Apply sRGB |
| 145 | + if srgb { |
| 146 | + image.srgb_to_linear(); |
| 147 | + } |
| 148 | + |
| 149 | + // Generate mipmaps if needed |
| 150 | + if generate_mipmaps && !image.has_mipmaps() { |
| 151 | + image.generate_mipmaps(); |
| 152 | + } |
| 153 | + |
| 154 | + // Fix alpha border |
| 155 | + if fix_alpha_border { |
| 156 | + image.fix_alpha_edges(); |
| 157 | + } |
| 158 | + |
| 159 | + // Apply compression |
| 160 | + match compress_mode { |
| 161 | + 0 => image.compress(Image::COMPRESS_BPTC), // Lossless |
| 162 | + 1 => image.compress(Image::COMPRESS_S3TC), // Lossy |
| 163 | + 2 => {}, // Uncompressed |
| 164 | + _ => {} |
| 165 | + } |
| 166 | + ``` |
| 167 | + |
| 168 | +4. **Save the texture resource** |
| 169 | + ```rust |
| 170 | + use godot::classes::ResourceSaver; |
| 171 | + |
| 172 | + let mut resource_saver = ResourceSaver::singleton(); |
| 173 | + let save_result = resource_saver.save( |
| 174 | + image.upcast(), |
| 175 | + format!("{}.{}", save_path, get_save_extension()).into() |
| 176 | + ); |
| 177 | + ``` |
| 178 | + |
| 179 | +## Key Godot-Rust Concepts |
| 180 | + |
| 181 | +### Working with Godot File Paths |
| 182 | + |
| 183 | +Godot uses `res://` paths internally. When reading files in the import plugin: |
| 184 | + |
| 185 | +```rust |
| 186 | +use godot::classes::{FileAccess, DirAccess}; |
| 187 | + |
| 188 | +// Convert res:// path to absolute path if needed |
| 189 | +let mut file = FileAccess::open(source_file.clone(), FileAccess::READ); |
| 190 | +let bytes = file.get_buffer(file.get_length() as i32); |
| 191 | +``` |
| 192 | + |
| 193 | +### Error Handling |
| 194 | + |
| 195 | +Always return appropriate `Error` enum values: |
| 196 | +- `Error::OK` - Success |
| 197 | +- `Error::ERR_FILE_NOT_FOUND` - File doesn't exist |
| 198 | +- `Error::ERR_FILE_CORRUPT` - File is invalid/corrupted |
| 199 | +- `Error::ERR_FILE_UNRECOGNIZED` - Unknown format |
| 200 | +- `Error::FAILED` - Generic failure |
| 201 | + |
| 202 | +### Type Conversions |
| 203 | + |
| 204 | +Converting Rust data to Godot types: |
| 205 | + |
| 206 | +```rust |
| 207 | +// Rust Vec to PackedArray |
| 208 | +let rust_vec: Vec<Vector3> = vec![...]; |
| 209 | +let godot_array = PackedVector3Array::from(&rust_vec); |
| 210 | + |
| 211 | +// Godot types to Variant |
| 212 | +let variant = godot_array.to_variant(); |
| 213 | +``` |
| 214 | + |
| 215 | +## Testing |
| 216 | + |
| 217 | +To test your implementation: |
| 218 | + |
| 219 | +1. Build the extension: `cargo build` |
| 220 | +2. Open a Godot project with the extension |
| 221 | +3. Enable the plugin in Project Settings |
| 222 | +4. Drop a League file into the project |
| 223 | +5. Check the console for debug output |
| 224 | +6. Verify the imported resource in the inspector |
| 225 | + |
| 226 | +## Debugging Tips |
| 227 | + |
| 228 | +1. Use `godot_print!()`, `godot_warn!()`, and `godot_error!()` for logging |
| 229 | +2. Check the Godot console for output |
| 230 | +3. Use `RUST_BACKTRACE=1` when running Godot from terminal |
| 231 | +4. Test with small, simple files first |
| 232 | +5. Validate that league-toolkit is parsing files correctly before converting to Godot |
| 233 | + |
| 234 | +## Resources |
| 235 | + |
| 236 | +- [Godot-Rust Book](https://godot-rust.github.io/book/) |
| 237 | +- [godot-rust API Docs](https://godot-rust.github.io/docs/gdext/) |
| 238 | +- [Godot ArrayMesh Documentation](https://docs.godotengine.org/en/stable/classes/class_arraymesh.html) |
| 239 | +- [Godot Image Documentation](https://docs.godotengine.org/en/stable/classes/class_image.html) |
| 240 | +- [League Toolkit Documentation](https://github.com/LeagueToolkit/league-toolkit) |
| 241 | + |
| 242 | +## Next Steps |
| 243 | + |
| 244 | +1. Start with the texture importer as it's generally simpler |
| 245 | +2. Implement basic file reading and parsing |
| 246 | +3. Convert to Godot Image format |
| 247 | +4. Test with various texture files |
| 248 | +5. Move on to mesh importer |
| 249 | +6. Implement vertex/normal/UV conversion |
| 250 | +7. Test with various mesh files |
| 251 | +8. Add error handling and edge cases |
| 252 | +9. Optimize performance for large files |
| 253 | + |
0 commit comments