• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MeltyPlayer / MeltyTool / 21805539193

08 Feb 2026 09:17PM UTC coverage: 41.556% (-0.1%) from 41.651%
21805539193

push

github

MeltyPlayer
Added support for loading PMDC character models.

6860 of 18652 branches covered (36.78%)

Branch coverage included in aggregate %.

6 of 184 new or added lines in 9 files covered. (3.26%)

4 existing lines in 4 files now uncovered.

29423 of 68660 relevant lines covered (42.85%)

63357.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/FinModelUtility/Games/PaperMarioDirectorsCut/PaperMarioDirectorsCut/src/api/LvlSceneImporter.cs
1
using System.Numerics;
2

3
using fin.animation.keyframes;
4
using fin.color;
5
using fin.data.lazy;
6
using fin.image;
7
using fin.io;
8
using fin.math;
9
using fin.math.transform;
10
using fin.model;
11
using fin.model.util;
12
using fin.scene;
13
using fin.ui.rendering.gl.scene;
14
using fin.util.asserts;
15
using fin.util.enums;
16
using fin.util.sets;
17

18
using gm.api;
19
using gm.schema.d3d;
20

21
using pmdc.schema.lvl;
22

23
namespace pmdc.api;
24

25
public sealed class LvlSceneFileBundle : ISceneFileBundle {
26
  public IReadOnlyTreeFile MainFile => this.LvlFile;
×
27

28
  public required IReadOnlyTreeFile LvlFile { get; init; }
×
29
  public required IReadOnlyTreeDirectory RootDirectory { get; init; }
×
30
}
31

32
public sealed class LvlSceneImporter : ISceneImporter<LvlSceneFileBundle> {
33
  public IScene Import(LvlSceneFileBundle sceneFileBundle) {
×
34
    var lvlFile = sceneFileBundle.LvlFile;
×
35
    var lvl = lvlFile.ReadNewFromText<Lvl>();
×
36

37
    var files = sceneFileBundle.LvlFile.AsFileSet();
×
38
    var finScene = new SceneImpl {
×
39
        FileBundle = sceneFileBundle,
×
40
        Files = files
×
41
    };
×
42

43
    var finArea = finScene.AddArea();
×
44

45
    if (lvl.HasRoomModel) {
×
46
      var modelFile = lvlFile.AssertGetParent()
×
47
                             .AssertGetExistingFile("model.omd");
×
48
      files.Add(modelFile);
×
49

50
      var finModel
×
51
          = new OmdModelImporter().Import(new OmdModelFileBundle {
×
52
              OmdFile = modelFile
×
53
          });
×
54
      finArea.AddRootNode().AddSceneModel(finModel);
×
55
    }
×
56

57
    var textureDirectory
×
58
        = sceneFileBundle.RootDirectory.AssertGetExistingSubdir("Textures");
×
59
    var lazyImageMap = new LazyDictionary<string?, IImage?>(
×
60
        imageName => imageName != null &&
×
61
                     textureDirectory.TryToGetExistingFile(
×
62
                         $"{imageName}.png",
×
63
                         out var textureFile)
×
64
            ? FinImage.FromFile(textureFile)
×
65
            : null);
×
66

67
    if (lvl.FloorBlocks.Count > 0) {
×
68
      foreach (var floorBlockParams in lvl.FloorBlocks) {
×
69
        var (start, end, textureName, type, flags) = floorBlockParams;
×
70

71
        var (floorBlockModel, floorBlockRootBone)
×
72
            = D3dModelImporter.CreateModel();
×
73
        var floorBlockSkin = floorBlockModel.Skin;
×
74
        var floorBlockMesh = floorBlockSkin.AddMesh();
×
75

76
        var shouldRepeat = !flags.CheckFlag(FloorBlockFlags.NO_REPEAT);
×
77
        var floorBlockMaterialManager = floorBlockModel.MaterialManager;
×
78
        IMaterial? floorBlockMaterial = null;
×
79
        if (flags.CheckFlag(FloorBlockFlags.INVISIBLE)) {
×
80
          floorBlockMaterial = floorBlockMaterialManager.AddHiddenMaterial();
×
81
        } else {
×
82
          var image = lazyImageMap[textureName];
×
83
          if (image != null) {
×
84
            (floorBlockMaterial, var floorBlockTexture)
×
85
                = floorBlockMaterialManager.AddSimpleTextureMaterialFromImage(
×
86
                    image,
×
87
                    textureName);
×
88

89
            if (shouldRepeat) {
×
90
              floorBlockTexture.WrapModeU = WrapMode.REPEAT;
×
91
              floorBlockTexture.WrapModeV = WrapMode.REPEAT;
×
92
            }
×
93
          }
×
94
        }
×
95

96
        switch (type) {
×
97
          case FloorBlockType.WALL: {
×
98
            (float, float)? repeat = shouldRepeat
×
99
                ? ((end.Xy() - start.Xy()).Length() / 64,
×
100
                   Math.Abs(end.Z - start.Z) / 64)
×
101
                : null;
×
102
            floorBlockMesh.AddSimpleWall(floorBlockSkin,
×
103
                                         start,
×
104
                                         end,
×
105
                                         floorBlockMaterial,
×
106
                                         floorBlockRootBone,
×
107
                                         repeat);
×
108
            break;
×
109
          }
110
          case FloorBlockType.FLOOR: {
×
111
            (float, float, float)? repeat = shouldRepeat
×
112
                ? (Math.Abs(end.X - start.X) / 64,
×
113
                   Math.Abs(end.Y - start.Y) / 64,
×
114
                   Math.Abs(end.Z - start.Z) / 64)
×
115
                : null;
×
116

117
            floorBlockMesh.AddSimpleCube(floorBlockSkin,
×
118
                                         start,
×
119
                                         end,
×
120
                                         floorBlockMaterial,
×
121
                                         floorBlockRootBone,
×
122
                                         repeat);
×
123
            break;
×
124
          }
125
        }
126

127
        finArea.AddRootNode().AddSceneModel(floorBlockModel);
×
128
      }
×
129
    }
×
130

NEW
131
    var characterDirectory
×
NEW
132
        = sceneFileBundle.RootDirectory.AssertGetExistingSubdir("Characters");
×
133

NEW
134
    if (lvl.Npcs.Count > 0) {
×
NEW
135
      var lazyNpcModels
×
NEW
136
          = new LazyCaseInvariantStringDictionary<IReadOnlyModel>(characterType
×
NEW
137
              => new PmdcCharacterModelImporter().Import(
×
NEW
138
                  new PmdcCharacterModelFileBundle {
×
NEW
139
                      AnimationImageFiles = characterDirectory
×
NEW
140
                                            .AssertGetExistingSubdir(
×
NEW
141
                                                characterType)
×
NEW
142
                                            .GetFilesWithFileType(".gif")
×
NEW
143
                                            .ToArray(),
×
NEW
144
                  }));
×
145

NEW
146
      foreach (var (npcPosition, npcName, npcCharacterType) in lvl.Npcs) {
×
NEW
147
        var npcNode
×
NEW
148
            = finArea.AddRootNode()
×
NEW
149
                     .SetPosition(npcPosition.X, npcPosition.Z, npcPosition.Y)
×
NEW
150
                     .AddComponent(
×
NEW
151
                         new SimpleModelRenderComponent(
×
NEW
152
                             lazyNpcModels[npcCharacterType]));
×
NEW
153
        npcNode.Name = npcName;
×
NEW
154
      }
×
NEW
155
    }
×
156

NEW
157
    if (lvl.SaveBlocks.Count > 0) {
×
NEW
158
      var saveBlockModel = CreateSaveBlockModel_(sceneFileBundle.RootDirectory);
×
159

NEW
160
      foreach (var saveBlockPosition in lvl.SaveBlocks) {
×
161
        finArea.AddRootNode()
×
NEW
162
               .SetPosition(saveBlockPosition.X, saveBlockPosition.Z, saveBlockPosition.Y)
×
NEW
163
               .AddSceneModel(saveBlockModel);
×
164
      }
×
165
    }
×
166

167
    if (lvl.SaveBlocks.Count > 0) {
×
168
      var saveBlockModel = CreateSaveBlockModel_(sceneFileBundle.RootDirectory);
×
169

170
      foreach (var saveBlockPosition in lvl.SaveBlocks) {
×
171
        finArea.AddRootNode()
×
172
               .SetPosition(saveBlockPosition.X, saveBlockPosition.Z, saveBlockPosition.Y)
×
173
               .AddSceneModel(saveBlockModel);
×
174
      }
×
175
    }
×
176

NEW
177
    if (lvl.Trees.Count > 0) {
×
NEW
178
      var treeModel = CreateTreeModel_(sceneFileBundle.RootDirectory);
×
179

NEW
180
      foreach (var treePosition in lvl.Trees) {
×
NEW
181
        finArea.AddRootNode()
×
NEW
182
               .SetPosition(treePosition.X, treePosition.Z, treePosition.Y)
×
NEW
183
               .AddSceneModel(treeModel);
×
NEW
184
      }
×
NEW
185
    }
×
186

187
    if (sceneFileBundle.LvlFile.Name is "battle.lvl") {
×
188
      var battleWallModel = CreateBattleWallModel_(lazyImageMap);
×
189

190
      finArea.AddRootNode()
×
191
             .SetPosition(176, 0, 176)
×
192
             .AddSceneModel(battleWallModel);
×
193
      finArea.AddRootNode()
×
194
             .SetPosition(176, 0, 464)
×
195
             .AddSceneModel(battleWallModel);
×
196

197
      var (battleFloorModel, battleFloorRootBone)
×
198
          = D3dModelImporter.CreateModel();
×
199

200
      var bfSkin = battleFloorModel.Skin;
×
201
      var bfMesh = bfSkin.AddMesh();
×
202
      var bfMaterialManager = battleFloorModel.MaterialManager;
×
203

204
      var frontOfFloorImage = lazyImageMap["bacFrontOfFloor"].AssertNonnull();
×
205
      var frontOfFloorTexture
×
206
          = bfMaterialManager.CreateTexture(frontOfFloorImage);
×
207
      frontOfFloorTexture.WrapModeV = WrapMode.REPEAT;
×
208
      var frontOfFloorMaterial
×
209
          = bfMaterialManager.AddTextureMaterial(frontOfFloorTexture);
×
210

211
      bfMesh.AddSimpleFloor(bfSkin,
×
212
                            new Vector3(0, 16, -64),
×
213
                            new Vector3(64, 640, 0),
×
214
                            frontOfFloorMaterial,
×
215
                            battleFloorRootBone,
×
216
                            (1, 10));
×
217
      finArea.AddRootNode()
×
218
             .AddSceneModel(battleFloorModel);
×
219
    }
×
220

221
    if (lvl.BackgroundName != null) {
×
222
      var backgroundImageFile
×
223
          = sceneFileBundle
×
224
            .RootDirectory.AssertGetExistingSubdir("Backgrounds")
×
225
            .AssertGetExistingFile($"{lvl.BackgroundName}.png");
×
226
      finArea.BackgroundImage = FinImage.FromFile(backgroundImageFile);
×
227
      finArea.CreateCustomSkyboxNode();
×
228
    }
×
229

230
    finScene.CreateDefaultLighting(finArea.AddRootNode());
×
231

232
    return finScene;
×
233
  }
×
234

235
  private static IModel CreateBattleWallModel_(
236
      ILazyDictionary<string?, IImage?> lazyImageMap) {
×
237
    var (battleWallModel, battleWallRootBone)
×
238
        = D3dModelImporter.CreateModel();
×
239

240
    var battleSkin = battleWallModel.Skin;
×
241
    var battleWallMesh = battleSkin.AddMesh();
×
242
    var battleMaterialManager = battleWallModel.MaterialManager;
×
243

244
    var frontOfFloorImage = lazyImageMap["bacFrontOfFloor"].AssertNonnull();
×
245
    var frontOfFloorTexture
×
246
        = battleMaterialManager.CreateTexture(frontOfFloorImage);
×
247
    frontOfFloorTexture.WrapModeU = WrapMode.REPEAT;
×
248
    var frontOfFloorMaterial
×
249
        = battleMaterialManager.AddTextureMaterial(frontOfFloorTexture);
×
250

251
    battleWallMesh.AddSimpleWall(battleSkin,
×
252
                                 new Vector3(-32, -12, 160),
×
253
                                 new Vector3(32, -12, 0),
×
254
                                 frontOfFloorMaterial,
×
255
                                 battleWallRootBone,
×
256
                                 (-1, 1));
×
257
    battleWallMesh.AddSimpleWall(battleSkin,
×
258
                                 new Vector3(-32, 12, 160),
×
259
                                 new Vector3(32, 12, 0),
×
260
                                 frontOfFloorMaterial,
×
261
                                 battleWallRootBone,
×
262
                                 (-1, 1));
×
263

264
    var battleWallImage = lazyImageMap["bacBattleWall"].AssertNonnull();
×
265
    var battleWallTexture
×
266
        = battleMaterialManager.CreateTexture(battleWallImage);
×
267
    battleWallTexture.WrapModeV = WrapMode.REPEAT;
×
268
    var battleWallMaterial
×
269
        = battleMaterialManager.AddTextureMaterial(battleWallTexture);
×
270

271
    battleWallMesh.AddSimpleWall(battleSkin,
×
272
                                 new Vector3(-32, -12, 160),
×
273
                                 new Vector3(-32, 12, 0),
×
274
                                 battleWallMaterial,
×
275
                                 battleWallRootBone,
×
276
                                 (1, 6));
×
277

278
    return battleWallModel;
×
279
  }
×
280

281
  private static IModel CreateTreeModel_(
282
      IReadOnlyTreeDirectory rootDirectory) {
×
283
    var treeDirectory
×
284
        = rootDirectory.AssertGetExistingSubdir(
×
285
            "Models/Tree");
×
286

287
    var (treeModel, treeRootBone) = D3dModelImporter.CreateModel();
×
288
    var treeSkin = treeModel.Skin;
×
289
    var treeMaterialManager = treeModel.MaterialManager;
×
290

291
    // Bark
292
    {
×
293
      var barkRootBone = treeRootBone.AddChild(0, 0, 12);
×
294
      barkRootBone.Transform.SetRotationDegrees(0, 0, 45);
×
295
      barkRootBone.Transform.SetScale(1.5f, 1.5f, 2);
×
296

297
      var barkTexture = treeMaterialManager.CreateTexture(
×
298
          FinImage.FromFile(
×
299
              treeDirectory.AssertGetExistingFile("bacTree.png")));
×
300
      var barkMaterial = treeMaterialManager.AddTextureMaterial(barkTexture);
×
301
      D3dModelImporter.AddToModel(
×
302
          treeDirectory
×
303
              .AssertGetExistingFile("treemodel1.mod")
×
304
              .ReadNewFromText<D3d>(),
×
305
          treeModel,
×
306
          barkRootBone,
×
307
          out _,
×
308
          barkMaterial);
×
309
    }
×
310

311
    // Leaves
312
    {
×
313
      var leavesMesh = treeSkin.AddMesh();
×
314

315
      var leavesTexture = treeMaterialManager.CreateTexture(
×
316
          FinImage.FromFile(
×
317
              treeDirectory.AssertGetExistingFile("bacTreeLeaves1.png")));
×
318
      leavesTexture.WrapModeU = WrapMode.REPEAT;
×
319
      var leavesMaterial
×
320
          = treeMaterialManager.AddTextureMaterial(leavesTexture);
×
321

322
      var leavesRootBone = treeRootBone.AddChild(Vector3.Zero);
×
323

324
      var leavesBone1 = leavesRootBone.AddChild(new Vector3(0, 0, 12));
×
325
      leavesMesh.AddSimpleCylinder(treeSkin,
×
326
                                   new Vector3(-60, -60, 20),
×
327
                                   new Vector3(60, 60, 160),
×
328
                                   8,
×
329
                                   leavesMaterial,
×
330
                                   leavesBone1,
×
331
                                   (2, 1));
×
332

333
      var leavesBone2 = leavesRootBone.AddChild(new Vector3(0, 0, 12));
×
334
      leavesBone2.Transform.LocalEulerRadians = new Vector3(0, 0, MathF.PI / 2);
×
335
      leavesMesh.AddSimpleCylinder(treeSkin,
×
336
                                   new Vector3(-80, -80, 20),
×
337
                                   new Vector3(80, 80, 180),
×
338
                                   8,
×
339
                                   leavesMaterial,
×
340
                                   leavesBone2,
×
341
                                   (2, 1));
×
342

343
      var leavesBone3 = leavesRootBone.AddChild(new Vector3(0, 0, 30));
×
344
      leavesMesh.AddSimpleCylinder(treeSkin,
×
345
                                   new Vector3(-70, -70, 20),
×
346
                                   new Vector3(70, 70, 180),
×
347
                                   8,
×
348
                                   leavesMaterial,
×
349
                                   leavesBone3,
×
350
                                   (2, 1));
×
351
    }
×
352

353
    // Shadow
354
    {
×
355
      var shadowBone = treeRootBone.AddChild(0, 0, 0.05f);
×
356

357
      var shadowTexture = treeMaterialManager.CreateTexture(
×
358
          FinImage.FromFile(
×
359
              treeDirectory.AssertGetExistingFile("bacShadowXL.png")));
×
360
      var shadowMaterial
×
361
          = treeMaterialManager.AddTextureMaterial(shadowTexture);
×
362

363
      var shadowSize = 96;
×
364
      var shadowMesh = treeSkin.AddMesh();
×
365
      shadowMesh.AddSimpleFloor(
×
366
          treeSkin,
×
367
          new Vector3(-shadowSize, -shadowSize, 0),
×
368
          new Vector3(shadowSize, shadowSize, 0),
×
369
          shadowMaterial,
×
370
          shadowBone);
×
371
    }
×
372

373
    return treeModel;
×
374
  }
×
375

376
  private static IModel CreateSaveBlockModel_(
377
      IReadOnlyTreeDirectory rootDirectory) {
×
378
    var saveBlockDirectory
×
379
        = rootDirectory.AssertGetExistingSubdir(
×
380
            "Models/SaveBlock");
×
381

382
    var (saveBlockModel, saveBlockRootBone) = D3dModelImporter.CreateModel();
×
383
    var saveBlockSkin = saveBlockModel.Skin;
×
384
    var saveBlockMaterialManager = saveBlockModel.MaterialManager;
×
385

386
    var saveBlockFloatingBone = saveBlockRootBone.AddChild(0, 0, 39);
×
387

388
    // Star
389
    {
×
390
      var starBone = saveBlockFloatingBone.AddChild(0, 0, 0);
×
391

392
      var starAnimation = saveBlockModel.AnimationManager.AddAnimation();
×
393
      starAnimation.FrameRate = 30;
×
394

395
      var frameCount = 64;
×
396
      starAnimation.FrameCount = frameCount;
×
397

398
      var starBoneTracks = starAnimation.GetOrCreateBoneTracks(starBone);
×
399
      var starBoneRotations = starBoneTracks.UseSeparateEulerRadiansKeyframes();
×
400
      starBoneRotations.Axes[2].Add(new Keyframe<float>(0, 0));
×
401
      starBoneRotations.Axes[2]
×
402
                       .Add(new Keyframe<float>(frameCount / 2, MathF.PI));
×
403
      starBoneRotations.Axes[2]
×
404
                       .Add(new Keyframe<float>(frameCount, 2 * MathF.PI));
×
405

406
      var (starMaterial, _)
×
407
          = saveBlockMaterialManager.AddSimpleTextureMaterialFromFile(
×
408
              saveBlockDirectory.AssertGetExistingFile("bacSaveBlockStar.png"));
×
409

410
      starMaterial.DiffuseColor = FinColor.FromAlphaFloat(.25f).ToSystemColor();
×
411

412
      saveBlockSkin
×
413
          .AddMesh()
×
414
          .AddSimpleWall(saveBlockSkin,
×
415
                         new Vector3(-8, 0, 16),
×
416
                         new Vector3(8, 0, 0),
×
417
                         starMaterial,
×
418
                         starBone);
×
419
    }
×
420

421
    // Block
422
    {
×
423
      var (saveBlockSideMaterial, _)
×
424
          = saveBlockMaterialManager.AddSimpleTextureMaterialFromFile(
×
425
              saveBlockDirectory.AssertGetExistingFile("bacSaveBlock.png"));
×
426
      var (saveBlockTopBottomMaterial, _)
×
427
          = saveBlockMaterialManager.AddSimpleTextureMaterialFromFile(
×
428
              saveBlockDirectory.AssertGetExistingFile(
×
429
                  "bacSaveBlockEmpty.png"));
×
430

431
      saveBlockSideMaterial.DiffuseColor
×
432
          = saveBlockTopBottomMaterial.DiffuseColor
×
433
              = FinColor.FromAlphaFloat(.7f).ToSystemColor();
×
434

435
      saveBlockSkin
×
436
          .AddMesh()
×
437
          .AddSimpleCube(saveBlockSkin,
×
438
                         new Vector3(-8, -8, 16),
×
439
                         new Vector3(8, 8, 0),
×
440
                         saveBlockTopBottomMaterial,
×
441
                         saveBlockSideMaterial,
×
442
                         saveBlockFloatingBone);
×
443
    }
×
444

445
    // Shadow
446
    {
×
447
      var shadowBone = saveBlockRootBone.AddChild(0, 0, 1);
×
448

449
      var shadowTexture = saveBlockMaterialManager.CreateTexture(
×
450
          FinImage.FromFile(rootDirectory.AssertGetExistingFile(
×
451
                                "Textures/bacSquareShadow.png")));
×
452
      var shadowMaterial
×
453
          = saveBlockMaterialManager.AddTextureMaterial(shadowTexture);
×
454

455
      var shadowSize = 9;
×
456
      var shadowMesh = saveBlockSkin.AddMesh();
×
457
      shadowMesh.AddSimpleFloor(
×
458
          saveBlockSkin,
×
459
          new Vector3(-shadowSize, -shadowSize, 0),
×
460
          new Vector3(shadowSize, shadowSize, 0),
×
461
          shadowMaterial,
×
462
          shadowBone);
×
463
    }
×
464

465
    return saveBlockModel;
×
466
  }
×
467
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc