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

MeltyPlayer / MeltyTool / 23249685920

16 Mar 2026 06:44PM UTC coverage: 41.42% (-0.01%) from 41.432%
23249685920

push

github

MeltyPlayer
Set up better support for The Labyrinth materials.

7010 of 19007 branches covered (36.88%)

Branch coverage included in aggregate %.

0 of 53 new or added lines in 4 files covered. (0.0%)

29943 of 70208 relevant lines covered (42.65%)

62184.33 hits per line

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

0.0
/FinModelUtility/Games/TheLabyrinthPlusEdition/TheLabyrinthPlusEdition/src/api/ScbModelImporter.cs
1
using System.Drawing;
2
using System.Numerics;
3

4
using fin.color;
5
using fin.data.dictionaries;
6
using fin.image;
7
using fin.io;
8
using fin.math.transform;
9
using fin.model;
10
using fin.model.impl;
11
using fin.model.io;
12
using fin.model.io.importers;
13
using fin.model.util;
14
using fin.util.sets;
15
using fin.util.strings;
16

17
using schema.text.reader;
18

19
using tlpe.scb;
20

21
namespace tlpe.api;
22

23
public sealed record ScbModelFileBundle(
×
24
    IReadOnlyTreeFile ScbFile,
×
25
    IReadOnlyTreeFile BallsFile,
×
26
    IReadOnlyTreeDirectory TexturesDir)
×
27
    : IModelFileBundle {
28
  public IReadOnlyTreeFile MainFile => this.ScbFile;
×
29
}
30

31
public sealed class ScbModelImporter : IModelImporter<ScbModelFileBundle> {
32
  public IModel Import(ScbModelFileBundle fileBundle) {
×
33
    var scb = fileBundle.ScbFile.ReadNew<Scb>();
×
34

35
    var files = fileBundle.ScbFile.AsFileSet();
×
36
    var finModel = new ModelImpl {
×
37
        FileBundle = fileBundle,
×
38
        Files = files
×
39
    };
×
40

41
    var allBallAttributes = ReadAllBallAttributes_(fileBundle.BallsFile);
×
42
    var ballAttributesByGeometry
×
43
        = allBallAttributes
×
44
            .ToListDictionary(a => a.Geometry?.ToLower() ?? "ball.scb");
×
45

NEW
46
    var material1TextureFiles = new List<IReadOnlyTreeFile>();
×
47
    if (ballAttributesByGeometry.TryGetList(
×
48
            fileBundle.ScbFile.Name.ToString().ToLower(),
×
49
            out var matchingBallAttributes)) {
×
50
      foreach (var ballAttributes in matchingBallAttributes) {
×
51
        if (ballAttributes.Texture != null) {
×
52
          var textureFile = fileBundle.TexturesDir.AssertGetExistingFile(
×
53
              ballAttributes.Texture);
×
NEW
54
          material1TextureFiles.Add(textureFile);
×
55
        }
×
56
      }
×
57
    }
×
58

NEW
59
    var materialById = new Dictionary<uint, IReadOnlyMaterial>();
×
60

61
    foreach (var scbSection in scb.Sections) {
×
NEW
62
      if (scbSection is MaterialSection materialSection) {
×
NEW
63
        var id = materialSection.Id;
×
64

NEW
65
        var textureFiles = new List<IReadOnlyTreeFile>();
×
NEW
66
        var textureName = materialSection.TextureName;
×
67
        if (textureName.Length > 0) {
×
68
          var textureFile = fileBundle.ScbFile.AssertGetParent()
×
69
                                      .AssertGetExistingFile(textureName);
×
70
          textureFiles.Add(textureFile);
×
71
        }
×
72

NEW
73
        if (id == 1) {
×
NEW
74
          textureFiles.AddRange(material1TextureFiles);
×
NEW
75
        }
×
76

NEW
77
        var textures = textureFiles
×
NEW
78
                       .Select(textureFile => {
×
NEW
79
                         var image = FinImage.FromFile(textureFile);
×
NEW
80
                         var finTexture
×
NEW
81
                             = finModel.MaterialManager.CreateTexture(image);
×
NEW
82
                         finTexture.Name
×
NEW
83
                             = textureFile.NameWithoutExtension.ToString();
×
NEW
84
                         finTexture.WrapModeU
×
NEW
85
                             = finTexture.WrapModeV = WrapMode.REPEAT;
×
NEW
86
                         return finTexture;
×
NEW
87
                       })
×
NEW
88
                       .ToArray();
×
89

NEW
90
        var finMaterial = finModel.MaterialManager.AddStandardMaterial();
×
NEW
91
        finMaterial.Name = materialSection.Name;
×
NEW
92
        finMaterial.DiffuseTexture = textures.FirstOrDefault();
×
93

NEW
94
        materialById[id] = finMaterial;
×
NEW
95
      }
×
NEW
96
    }
×
97

98
    var finSkin = finModel.Skin;
×
99

100
    IBone? currentBone = null;
×
101
    IReadOnlyBoneWeights? currentBoneWeights = null;
×
102

103
    var finBoneByName = new CaseInvariantStringDictionary<IBone>();
×
104

105
    foreach (var scbSection in scb.Sections) {
×
106
      switch (scbSection) {
×
107
        case JointSection joint: {
×
NEW
108
          if (!finBoneByName.TryGetValue(joint.ParentName,
×
NEW
109
                                         out var parentBone)) {
×
110
            parentBone = finModel.Skeleton.Root;
×
111
          }
×
112

113
          currentBone = parentBone.AddChild(AdjustVector3_(joint.Translation));
×
114
          currentBone.Transform.SetRotationRadians(
×
115
              AdjustVector3_(joint.Rotation));
×
116
          currentBone.Transform.SetScale(AdjustVector3_(joint.Scale));
×
117
          currentBone.Name = joint.Name;
×
118

119
          finBoneByName[currentBone.Name] = currentBone;
×
120

121
          currentBoneWeights
×
122
              = finModel.Skin.GetOrCreateBoneWeights(
×
123
                  VertexSpace.RELATIVE_TO_BONE,
×
124
                  currentBone);
×
125

126
          break;
×
127
        }
128
        case MeshSection meshSection: {
×
129
          var finMesh = finSkin.AddMesh();
×
130
          finMesh.Name = currentBone?.Name;
×
131

132

133
          var scbVertices = meshSection.Vertices;
×
134
          var finVertices = new IReadOnlyVertex[scbVertices.Length];
×
135
          for (var i = 0; i < scbVertices.Length; ++i) {
×
136
            var scbVertex = scbVertices[i];
×
137

138
            var finVertex
×
139
                = finSkin.AddVertex(AdjustVector3_(scbVertex.Position));
×
140
            finVertex.SetLocalNormal(AdjustVector3_(scbVertex.Normal));
×
141
            finVertex.SetUv(0, AdjustVector2_(scbVertex.Uv0));
×
142
            finVertex.SetUv(1, AdjustVector2_(scbVertex.Uv1));
×
143

144
            if (currentBoneWeights != null) {
×
145
              finVertex.SetBoneWeights(currentBoneWeights);
×
146
            }
×
147

148
            finVertices[scbVertices.Length - 1 - i] = finVertex;
×
149
          }
×
150

NEW
151
          var trianglesByMaterial
×
NEW
152
              = meshSection.Faces
×
NEW
153
                           .ToListDictionary(
×
NEW
154
                               f => f.MaterialId,
×
NEW
155
                               f => {
×
NEW
156
                                 var v0 = finVertices[f.Vertex0];
×
NEW
157
                                 var v1 = finVertices[f.Vertex1];
×
NEW
158
                                 var v2 = finVertices[f.Vertex2];
×
NEW
159
                                 return (v0, v1, v2);
×
NEW
160
                               });
×
161

NEW
162
          foreach (var materialId in trianglesByMaterial.Keys) {
×
NEW
163
            var finPrimitive = finMesh.AddTriangles(
×
NEW
164
                (IReadOnlyList<(IReadOnlyVertex, IReadOnlyVertex,
×
NEW
165
                    IReadOnlyVertex)>) trianglesByMaterial[materialId]);
×
NEW
166
            finPrimitive.SetMaterial(materialById[materialId]);
×
NEW
167
          }
×
168

169
          break;
×
170
        }
171
      }
172
    }
×
173

174
    return finModel;
×
175
  }
×
176

177
  private class BallAttributes {
178
    public string Id { get; set; }
×
179
    public string? Geometry { get; set; }
×
180
    public string? Texture { get; set; }
×
181
    public bool MapFromFile { get; set; }
×
182
    public bool NoEnvMap { get; set; }
×
183
  }
184

185
  private static Vector2 AdjustVector2_(Vector2 input)
186
    => input with { Y = 1 - input.Y };
×
187

188
  private static Vector3 AdjustVector3_(Vector3 input)
189
    => new(input.X, input.Z, input.Y);
×
190

191
  private static IList<BallAttributes> ReadAllBallAttributes_(
192
      IReadOnlyTreeFile file) {
×
193
    using var tr = new SchemaTextReader(file.OpenRead());
×
194

195
    var allAttributes = new List<BallAttributes>();
×
196

197
    BallAttributes currentAttributes = default!;
×
198
    while (!tr.Eof) {
×
199
      var line = tr.ReadLine().Trim();
×
200

201
      if (line.StartsWith('[')) {
×
202
        var id = line[1..].SubstringUpTo(']');
×
203
        currentAttributes = new BallAttributes { Id = id };
×
204
        allAttributes.Add(currentAttributes);
×
205
      } else {
×
206
        var equalsParts = line.Split('=');
×
207
        if (equalsParts.Length > 1) {
×
208
          var before = equalsParts[0].Trim();
×
209
          var after = equalsParts[1].Trim();
×
210

211
          switch (before) {
×
212
            case "Geometry": {
×
213
              currentAttributes.Geometry = after;
×
214
              break;
×
215
            }
216
            case "MapFromFile": {
×
217
              currentAttributes.MapFromFile = after == "1";
×
218
              break;
×
219
            }
220
            case "NoEnvMap": {
×
221
              currentAttributes.NoEnvMap = after == "1";
×
222
              break;
×
223
            }
224
            case "Texture": {
×
225
              currentAttributes.Texture = after;
×
226
              break;
×
227
            }
228
          }
229
        }
×
230
      }
×
231
    }
×
232

233
    return allAttributes;
×
234
  }
×
235
}
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