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

MeltyPlayer / MeltyTool / 19622977055

24 Nov 2025 04:05AM UTC coverage: 41.989% (+2.1%) from 39.89%
19622977055

push

github

MeltyPlayer
Switched float precision to fix broken tests.

6747 of 18131 branches covered (37.21%)

Branch coverage included in aggregate %.

28639 of 66144 relevant lines covered (43.3%)

65384.8 hits per line

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

88.41
/FinModelUtility/Libraries/Sysdolphin/Sysdolphin/src/api/DatModelImporter.cs
1
using System.Numerics;
2

3
using CommunityToolkit.HighPerformance;
4

5
using fin.data.lazy;
6
using fin.image;
7
using fin.image.formats;
8
using fin.io;
9
using fin.language.equations.fixedFunction;
10
using fin.language.equations.fixedFunction.impl;
11
using fin.math.matrix.four;
12
using fin.math.rotations;
13
using fin.math.transform;
14
using fin.model;
15
using fin.model.impl;
16
using fin.model.io.importers;
17
using fin.model.util;
18
using fin.util.enums;
19
using fin.util.hex;
20

21
using gx;
22
using gx.displayList;
23

24
using schema.binary;
25

26
using SixLabors.ImageSharp.PixelFormats;
27

28
using sysdolphin.schema;
29
using sysdolphin.schema.material;
30
using sysdolphin.schema.melee;
31
using sysdolphin.schema.mesh;
32
using sysdolphin.schema.texture;
33

34
namespace sysdolphin.api;
35

36
public sealed class DatModelImporter : IModelImporter<DatModelFileBundle> {
37
  public IModel Import(DatModelFileBundle modelFileBundle)
38
    => this.Import(modelFileBundle, out _, out _, out _, out _, out _);
3✔
39

40
  public unsafe IModel Import(
41
      DatModelFileBundle modelFileBundle,
42
      out HashSet<IReadOnlyGenericFile> files,
43
      out DatSubfile datSubfile,
44
      out IReadOnlyDictionary<JObj, IReadOnlyBone> outFinBoneByJObj,
45
      out IReadOnlyList<(JObj jObj,
46
          byte jObjIndex,
47
          DObj dObj,
48
          byte dObjIndex)> sortedJObjsAndDObjs,
49
      out IReadOnlyDictionary<DObj, IMesh> outFinMeshByDObj) {
14✔
50
    var dat = modelFileBundle.DatFile.ReadNew<Dat>(Endianness.BigEndian);
14✔
51
    datSubfile = dat.Subfiles.Single();
14✔
52

53
    files = modelFileBundle.Files.ToHashSet();
14✔
54
    var finModel = new ModelImpl {
14✔
55
        FileBundle = modelFileBundle,
14✔
56
        Files = files
14✔
57
    };
14✔
58
    var finSkin = finModel.Skin;
14✔
59

60
    // Adds skeleton
61
    var jObjByOffset = datSubfile.JObjByOffset;
14✔
62
    var finBoneByJObj = new Dictionary<JObj, IReadOnlyBone>();
14✔
63
    outFinBoneByJObj = finBoneByJObj;
14✔
64
    var boneWeightsByJObj = new Dictionary<JObj, IBoneWeights>();
14✔
65
    var inverseBindMatrixByJObj =
14✔
66
        new Dictionary<JObj, IReadOnlyFinMatrix4x4>();
14✔
67
    var boneQueue = new Queue<(IBone finParentBone, JObj datBone)>();
14✔
68
    foreach (var datRootBone in datSubfile.RootJObjs) {
84✔
69
      boneQueue.Enqueue((finModel.Skeleton.Root, datRootBone));
14✔
70
    }
14✔
71

72
    Span<Matrix4x4> inverseBindMatrixBuffer = stackalloc Matrix4x4[1];
14✔
73
    Span<float> inverseBindMatrixFloatBuffer
14✔
74
        = inverseBindMatrixBuffer.Cast<Matrix4x4, float>();
14✔
75
    while (boneQueue.Count > 0) {
884✔
76
      var (finParentBone, jObj) = boneQueue.Dequeue();
435✔
77

78
      var finBone = finParentBone.AddChild(jObj.Position);
435✔
79
      finBone.LocalTransform.SetRotationRadians(jObj.RotationRadians);
435✔
80
      finBone.LocalTransform.SetScale(jObj.Scale);
435✔
81
      finBone.Name = jObj.Name;
435✔
82

83
      finBoneByJObj[jObj] = finBone;
435✔
84
      boneWeightsByJObj[jObj] =
435✔
85
          finSkin.GetOrCreateBoneWeights(VertexSpace.RELATIVE_TO_BONE,
435✔
86
                                         finBone);
435✔
87

88
      var inverseBindMatrixValues = jObj.InverseBindMatrixValues;
435✔
89
      if (inverseBindMatrixValues != null) {
870!
90
        inverseBindMatrixValues.CopyTo(inverseBindMatrixFloatBuffer);
435✔
91
        inverseBindMatrixFloatBuffer[15] = 1;
435✔
92
      } else {
435✔
93
        inverseBindMatrixBuffer[0] = Matrix4x4.Identity;
×
94
      }
×
95

96
      inverseBindMatrixByJObj[jObj]
435✔
97
          = new FinMatrix4x4(inverseBindMatrixFloatBuffer).TransposeInPlace();
435✔
98

99
      foreach (var datChildBone in jObj.GetChildren()) {
2,568✔
100
        boneQueue.Enqueue((finBone, datChildBone));
421✔
101
      }
421✔
102
    }
435✔
103

104
    // Adds animations
105
    {
14✔
106
      var sObjAnimations
14✔
107
          = datSubfile
14✔
108
            .GetRootNodesOfType<SObj>()
14✔
109
            .SelectMany(sObj => sObj.JObjDescs?.Values ?? [])
3!
110
            .SelectMany(jObjDesc => jObjDesc.JointAnimations?.Values.Select(
17✔
111
                                        a => (jObjDesc.RootJObj, a)) ??
414✔
112
                                    []);
17✔
113
      var gObjAnimations
14✔
114
          = datSubfile
14✔
115
            .GetRootNodesOfType<GrMapHead>()
14✔
116
            .SelectMany(mapHead => mapHead.ModelGroups ?? [])
×
117
            .SelectMany(gObj => gObj.JointAnimations?.Values.Select(
14!
118
                                    a => (gObj.RootJObj, a)) ??
×
119
                                []);
14✔
120
      var rootJointAnims
14✔
121
          = sObjAnimations.Concat(gObjAnimations);
14✔
122

123
      var i = 0;
14✔
124
      foreach (var (rootJObj, rootJointAnim) in rootJointAnims) {
1,284✔
125
        var finAnimation = finModel.AnimationManager.AddAnimation();
414✔
126
        finAnimation.Name = $"animation {i++}";
414✔
127
        finAnimation.FrameRate = 30;
414✔
128

129
        foreach (var (jObj, jointAnim)
26,721✔
130
                 in rootJObj
27,135✔
131
                    .GetSelfAndChildrenAndSiblings()
414✔
132
                    .Zip(rootJointAnim.GetSelfAndChildrenAndSiblings())) {
26,721✔
133
          var aObj = jointAnim.AObj;
26,307✔
134
          if (aObj == null) {
42,097✔
135
            continue;
15,790✔
136
          }
137

138
          finAnimation.FrameCount
10,517✔
139
              = Math.Max(finAnimation.FrameCount, (int) aObj.EndFrame);
10,517✔
140

141
          var finBone = finBoneByJObj[jObj];
10,517✔
142
          var boneTracks = finAnimation.GetOrCreateBoneTracks(finBone);
10,517✔
143

144
          DatBoneTracksHelper.AddDatKeyframesToBoneTracks(
10,517✔
145
              aObj.FObjs,
10,517✔
146
              boneTracks);
10,517✔
147
        }
10,517✔
148
      }
414✔
149
    }
14✔
150

151
    // Adds mesh and materials
152
    var mObjByOffset = new Dictionary<uint, MObj>();
14✔
153
    var tObjByOffset = new Dictionary<uint, TObj>();
14✔
154
    foreach (var jObj in datSubfile.JObjs) {
1,347✔
155
      foreach (var dObj in jObj.DObjs) {
3,093✔
156
        var mObj = dObj.MObj;
596✔
157
        if (mObj != null) {
1,192✔
158
          mObjByOffset[dObj.MObjOffset] = mObj;
596✔
159
          foreach (var (tObjOffset, tObj) in mObj.TObjsAndOffsets) {
3,405✔
160
            tObjByOffset[tObjOffset] = tObj;
539✔
161
          }
539✔
162
        }
596✔
163
      }
596✔
164
    }
435✔
165

166
    var finMaterialManager = finModel.MaterialManager;
14✔
167
    var finTexturesByTObjOffset =
14✔
168
        new LazyDictionary<uint, ITexture>(tObjOffset => {
512✔
169
          var tObj = tObjByOffset[tObjOffset];
512✔
170

14✔
171
          IImage image = tObj.Image;
512✔
172
          if (tObj.WrapT == GxWrapMode.GX_MIRROR) {
537✔
173
            var width = image.Width;
25✔
174
            var height = image.Height;
25✔
175

14✔
176
            var flippedImage =
25✔
177
                new Rgba32Image(image.PixelFormat, width, height);
25✔
178
            image.Access(getHandler => {
50✔
179
              using var flippedImageLock = flippedImage.Lock();
25✔
180
              var flippedImageScan0 = flippedImageLock.Pixels;
25✔
181
              for (var y = 0; y < height; ++y) {
2,642✔
182
                for (var x = 0; x < width; ++x) {
147,648✔
183
                  getHandler(x,
48,640✔
184
                             height - 1 - y,
48,640✔
185
                             out var r,
48,640✔
186
                             out var g,
48,640✔
187
                             out var b,
48,640✔
188
                             out var a);
48,640✔
189

25✔
190
                  flippedImageScan0[y * width + x] = new Rgba32(r, g, b, a);
48,640✔
191
                }
48,640✔
192
              }
864✔
193
            });
75✔
194

14✔
195
            image = flippedImage;
25✔
196
          }
25✔
197

14✔
198
          var finTexture = finMaterialManager.CreateTexture(image);
512✔
199
          finTexture.Name = tObj.Name ?? tObjOffset.ToHex();
512✔
200

14✔
201
          finTexture.MagFilter = tObj.MagFilter.ToFinMagFilter();
512✔
202

14✔
203
          var lod = tObj.Lod;
512✔
204
          if (lod != null) {
512!
205
            finTexture.MinFilter = lod.MinFilter.ToFinMinFilter();
×
206
            finTexture.LodBias = lod.Bias;
×
207
          } else {
512✔
208
            finTexture.MinFilter = TextureMinFilter.LINEAR;
512✔
209
          }
512✔
210

14✔
211
          finTexture.WrapModeU = tObj.WrapS.ToFinWrapMode();
512✔
212
          finTexture.WrapModeV = tObj.WrapT.ToFinWrapMode();
512✔
213

14✔
214
          finTexture.UvIndex = tObj.TexGenSrc switch {
512!
215
              >= GxTexGenSrc.Tex0 and <= GxTexGenSrc.Tex7
512✔
216
                  => tObj.TexGenSrc - GxTexGenSrc.Tex0
512✔
217
          };
512✔
218
          finTexture.UvType = tObj.Flags.GetCoord() switch {
512!
219
              Coord.UV         => UvType.STANDARD,
452✔
220
              Coord.REFLECTION => UvType.SPHERICAL,
60✔
221
              _                => UvType.STANDARD
×
222
          };
512✔
223

14✔
224
          FinMatrix4x4Util.FromTrs(tObj.Translation,
512✔
225
                                   tObj.RotationRadians.CreateZyxRadians(),
512✔
226
                                   tObj.Scale)
512✔
227
                          .InvertInPlace()
512✔
228
                          .Decompose(out var tObjTranslation,
512✔
229
                                     out var tObjRotation,
512✔
230
                                     out var tObjScale);
512✔
231
          finTexture.TextureTransform
512✔
232
                    .SetTranslation3d(tObjTranslation)
512✔
233
                    .SetRotationRadians3d(tObjRotation.ToEulerRadians())
512✔
234
                    .SetScale3d(tObjScale *
512✔
235
                                new Vector3(
512✔
236
                                    tObj.RepeatS,
512✔
237
                                    tObj.RepeatT,
512✔
238
                                    1));
512✔
239

14✔
240
          return finTexture;
512✔
241
        });
526✔
242
    var finMaterialsAndTextureMatricesByMObjOffset =
14✔
243
        new LazyDictionary<(uint, CullingMode), IMaterial?>(
14✔
244
            (mObjOffsetAndCullingMode => {
578✔
245
              var (mObjOffset, cullingMode) = mObjOffsetAndCullingMode;
578✔
246
              if (mObjOffset == 0) {
578!
247
                return null;
×
248
              }
14✔
249

14✔
250
              var mObj = mObjByOffset[mObjOffset];
578✔
251
              var tObjsAndOffsets = mObj.TObjsAndOffsets.ToArray();
578✔
252

14✔
253
              var tObjsAndFinTextures =
578✔
254
                  new (TObj, ITexture)[tObjsAndOffsets.Length];
578✔
255
              for (var i = 0; i < tObjsAndOffsets.Length; i++) {
2,716✔
256
                var (tObjOffset, tObj) = tObjsAndOffsets[i];
520✔
257
                tObjsAndFinTextures[i] = (
520✔
258
                    tObj, finTexturesByTObjOffset[tObjOffset]);
520✔
259
              }
520✔
260

14✔
261
              var fixedFunctionMaterial =
578✔
262
                  finMaterialManager.AddFixedFunctionMaterial();
578✔
263
              fixedFunctionMaterial.Name = mObj.Name ?? mObjOffset.ToHex();
578✔
264
              fixedFunctionMaterial.CullingMode = cullingMode;
578✔
265

14✔
266
              var mObjMaterial = mObj.Material;
578✔
267
              fixedFunctionMaterial.Shininess = mObjMaterial.Shininess;
578✔
268
              // TODO: This results in some issues with sorting
14✔
269
              if (mObj.RenderMode.CheckFlag(RenderMode.NO_ZUPDATE)) {
631✔
270
                fixedFunctionMaterial.DepthMode = DepthMode.READ_ONLY;
53✔
271
              }
53✔
272

14✔
273
              this.PopulateFixedFunctionMaterial_(mObj,
578✔
274
                                                  tObjsAndFinTextures,
578✔
275
                                                  fixedFunctionMaterial);
578✔
276

14✔
277
              var peDesc = mObj.PeDesc;
578✔
278
              if (peDesc == null) {
1,154✔
279
                fixedFunctionMaterial.SetAlphaCompare(
576✔
280
                    AlphaCompareType.Greater,
576✔
281
                    0);
576✔
282
              } else {
578✔
283
                fixedFunctionMaterial.DepthCompareType =
2✔
284
                    peDesc.DepthFunction.ToFinDepthCompareType();
2✔
285

14✔
286
                new GxFixedFunctionBlending().ApplyBlending(
2✔
287
                    fixedFunctionMaterial,
2✔
288
                    peDesc.BlendMode,
2✔
289
                    peDesc.SrcFactor,
2✔
290
                    peDesc.DstFactor,
2✔
291
                    peDesc.BlendOp);
2✔
292
                fixedFunctionMaterial.SetAlphaCompare(
2✔
293
                    peDesc.AlphaOp.ToFinAlphaOp(),
2✔
294
                    peDesc.AlphaComp0.ToFinAlphaCompareType(),
2✔
295
                    peDesc.AlphaRef0,
2✔
296
                    peDesc.AlphaComp1.ToFinAlphaCompareType(),
2✔
297
                    peDesc.AlphaRef1);
2✔
298
              }
2✔
299

14✔
300
              return fixedFunctionMaterial;
578✔
301
            }));
592✔
302

303
    // Sorts all dObjs so that the opaque ones are rendered first, and then the translucent (XLU) ones
304
    LinkedList<(JObj jObj, byte jObjIndex, DObj dObj, byte dObjIndex)>
14✔
305
        allJObjsAndDObjs = [];
14✔
306
    {
14✔
307
      byte jObjIndex = 0;
14✔
308
      foreach (var rootJObj in datSubfile.RootJObjs) {
84✔
309
        byte dObjIndex = 0;
14✔
310
        foreach (var jObj in rootJObj.GetSelfAndChildrenAndSiblings()) {
1,347✔
311
          foreach (var dObj in jObj.DObjs) {
3,093✔
312
            allJObjsAndDObjs.AddLast((jObj, jObjIndex, dObj, dObjIndex++));
596✔
313
          }
596✔
314
        }
435✔
315

316
        jObjIndex++;
14✔
317
      }
14✔
318
    }
14✔
319

320
    sortedJObjsAndDObjs
14✔
321
        = allJObjsAndDObjs
14✔
322
          .OrderBy(
14✔
323
              tuple => tuple.dObj.MObj?.RenderMode.CheckFlag(RenderMode.XLU) ??
596!
324
                       false)
596✔
325
          .ToArray();
14✔
326

327
    var finMeshByDObj = new Dictionary<DObj, IMesh>();
14✔
328
    outFinMeshByDObj = finMeshByDObj;
14✔
329

330
    foreach (var (jObj, _, dObj, _) in sortedJObjsAndDObjs) {
1,830✔
331
      var defaultBoneWeights = boneWeightsByJObj[jObj];
596✔
332
      var mObjOffset = dObj.MObjOffset;
596✔
333

334
      var finMesh = finSkin.AddMesh();
596✔
335
      finMeshByDObj[dObj] = finMesh;
596✔
336

337
      // Adds polygons
338
      foreach (var pObj in dObj.PObjs) {
3,738✔
339
        var pObjFlags = pObj.Header.Flags;
650✔
340
        var cullingMode = (pObjFlags.CheckFlag(PObjFlags.CULLFRONT),
650✔
341
                           pObjFlags.CheckFlag(PObjFlags.CULLBACK))
650✔
342
            switch {
650!
343
                (true, true)  => CullingMode.SHOW_BOTH,
×
344
                (true, false) => CullingMode.SHOW_FRONT_ONLY,
560✔
345
                (false, true) => CullingMode.SHOW_BACK_ONLY,
×
346
                _             => CullingMode.SHOW_BOTH
90✔
347
            };
650✔
348

349
        var finMaterial =
650✔
350
            finMaterialsAndTextureMatricesByMObjOffset[
650✔
351
                (mObjOffset, cullingMode)];
650✔
352

353
        var vertexSpace = pObj.VertexSpace;
650✔
354
        var finWeights =
650✔
355
            pObj.Weights
650✔
356
                ?.Select(
650✔
357
                    pObjWeights => finSkin.GetOrCreateBoneWeights(
963✔
358
                        vertexSpace,
963✔
359
                        pObjWeights
963✔
360
                            .Select(
963✔
361
                                pObjWeight => {
1,357✔
362
                                  var jObj =
1,357✔
363
                                      jObjByOffset[pObjWeight.JObjOffset];
1,357✔
364
                                  return new BoneWeight(
1,357✔
365
                                      finBoneByJObj[jObj],
1,357✔
366
                                      inverseBindMatrixByJObj[jObj],
1,357✔
367
                                      pObjWeight.Weight
1,357✔
368
                                  );
1,357✔
369
                                })
1,357✔
370
                            .ToArray()))
963✔
371
                .ToArray();
650✔
372

373
        foreach (var datPrimitive in pObj.Primitives) {
11,457✔
374
          var finVertices =
3,169✔
375
              datPrimitive
3,169✔
376
                  .Vertices
3,169✔
377
                  .Select(datVertex => {
110,572✔
378
                    var finVertex = finSkin.AddVertex(datVertex.Position);
110,572✔
379
                    finVertex.SetLocalNormal(datVertex.Normal);
110,572✔
380
                    // TODO: Add support for binormal/tangents for bump-mapping
3,169✔
381

3,169✔
382
                    finVertex.SetColor(datVertex.Color);
110,572✔
383

3,169✔
384
                    if (datVertex.Uv0 != null) {
199,601✔
385
                      var uv0 = datVertex.Uv0.Value;
89,029✔
386
                      finVertex.SetUv(0, uv0.X, uv0.Y);
89,029✔
387
                    }
89,029✔
388

3,169✔
389
                    if (datVertex.Uv1 != null) {
121,562✔
390
                      var uv1 = datVertex.Uv1.Value;
10,990✔
391
                      finVertex.SetUv(1, uv1.X, uv1.Y);
10,990✔
392
                    }
10,990✔
393

3,169✔
394
                    // TODO: Is this right???
3,169✔
395
                    if (datVertex.WeightId != null) {
131,969✔
396
                      if (finWeights != null) {
42,794✔
397
                        finVertex.SetBoneWeights(
21,397✔
398
                            finWeights[datVertex.WeightId.Value]);
21,397✔
399
                      }
21,397✔
400
                    } else {
110,572✔
401
                      finVertex.SetBoneWeights(defaultBoneWeights);
89,175✔
402
                    }
89,175✔
403

3,169✔
404
                    return finVertex;
110,572✔
405
                  })
110,572✔
406
                  .ToArray();
3,169✔
407

408
          var finPrimitive = datPrimitive.Type switch {
3,169!
409
              GxPrimitiveType.GX_TRIANGLES =>
3,169✔
410
                  finMesh.AddTriangles(finVertices),
130✔
411
              GxPrimitiveType.GX_QUADS => finMesh.AddQuads(finVertices),
104✔
412
              GxPrimitiveType.GX_TRIANGLE_STRIP => finMesh.AddTriangleStrip(
2,935✔
413
                  finVertices),
2,935✔
414
              GxPrimitiveType.GX_POINTS => finMesh.AddPoints(finVertices),
×
415
              _ => throw new ArgumentOutOfRangeException()
×
416
          };
3,169✔
417

418
          if (finMaterial != null) {
6,338✔
419
            finPrimitive.SetMaterial(finMaterial);
3,169✔
420
          }
3,169✔
421
        }
3,169✔
422
      }
650✔
423
    }
596✔
424

425
    return finModel;
14✔
426
  }
14✔
427

428
  /// <summary>
429
  ///   Shamelessly stolen from:
430
  ///   https://github.com/Ploaj/HSDLib/blob/93a906444f34951c6eed4d8c6172bba43d4ada98/HSDRawViewer/Shader/gx_lightmap.frag
431
  /// </summary>
432
  private void PopulateFixedFunctionMaterial_(
433
      MObj mObj,
434
      IReadOnlyList<(TObj, ITexture)> tObjsAndFinTextures,
435
      IFixedFunctionMaterial fixedFunctionMaterial) {
578✔
436
    var equations = fixedFunctionMaterial.Equations;
578✔
437

438
    var colorOps = equations.ColorOps;
578✔
439
    var scalarOps = equations.ScalarOps;
578✔
440

441
    for (var i = 0; i < tObjsAndFinTextures.Count; ++i) {
2,716✔
442
      var (_, finTexture) = tObjsAndFinTextures[i];
520✔
443
      fixedFunctionMaterial.SetTextureSource(i, finTexture);
520✔
444
    }
520✔
445

446
    var renderMode = mObj.RenderMode;
578✔
447
    var material = mObj.Material;
578✔
448

449
    var hasConstantRenderMode = renderMode.CheckFlag(RenderMode.CONSTANT);
578✔
450
    var hasDiffuseRenderMode = renderMode.CheckFlag(RenderMode.DIFFUSE);
578✔
451
    var hasSpecularRenderMode = renderMode.CheckFlag(RenderMode.SPECULAR);
578✔
452

453
    // Diffuse
454
    var diffuseRgba = hasConstantRenderMode || hasDiffuseRenderMode
578✔
455
        ? material.DiffuseColor
578✔
456
        : fin.schema.color.Rgba32.WHITE;
578✔
457
    var hasVertexColorAlpha = renderMode.CheckFlag(RenderMode.VERTEX);
578✔
458
    var (diffuseSurfaceColor, diffuseSurfaceAlpha)
578✔
459
        = fixedFunctionMaterial.GenerateDiffuse(
578✔
460
            (equations.CreateColorConstant(diffuseRgba.Rf, diffuseRgba.Gf, diffuseRgba.Bf),
578✔
461
             equations.CreateScalarConstant(
578✔
462
                 material.DiffuseColor.Af * material!.Alpha)),
578✔
463
            null,
578✔
464
            (hasVertexColorAlpha, hasVertexColorAlpha));
578✔
465

466
    // Ambient
467
    IColorValue? ambientSurfaceColor = equations.CreateColorConstant(
578✔
468
        material.AmbientColor.Rf,
578✔
469
        material.AmbientColor.Gf,
578✔
470
        material.AmbientColor.Bf);
578✔
471

472
    IScalarValue? ambientSurfaceAlpha = equations.CreateScalarConstant(
578✔
473
        material.AmbientColor.Af);
578✔
474

475
    // Specular
476
    IColorValue? specularSurfaceColor = equations.CreateColorConstant(
578✔
477
        material.SpecularColor.Rf,
578✔
478
        material.SpecularColor.Gf,
578✔
479
        material.SpecularColor.Bf);
578✔
480

481
    IScalarValue? specularSurfaceAlpha = equations.CreateScalarConstant(
578✔
482
        material.SpecularColor.Af);
578✔
483

484
    // Lighting passes
485
    IColorValue ambientLightColor = ColorConstant.ZERO;
578✔
486
    IColorValue diffuseLightColor = ColorConstant.ONE;
578✔
487
    IColorValue specularLightColor = ColorConstant.ZERO;
578✔
488

489
    var lightingPasses = new LinkedList<TObjFlags>();
578✔
490
    lightingPasses.AddLast(TObjFlags.LIGHTMAP_DIFFUSE);
578✔
491

492
    // Shamelessly stolen from:
493
    // https://github.com/Ploaj/HSDLib/blob/93a906444f34951c6eed4d8c6172bba43d4ada98/HSDRawViewer/Shader/gx_material.frag#L81
494
    if (!(hasConstantRenderMode && !hasDiffuseRenderMode)) {
1,107✔
495
      lightingPasses.AddFirst(TObjFlags.LIGHTMAP_AMBIENT);
529✔
496
      ambientLightColor = equations.CreateOrGetColorInput(
529✔
497
          FixedFunctionSource.LIGHT_AMBIENT_COLOR);
529✔
498

499
      if (hasDiffuseRenderMode) {
1,014✔
500
        diffuseLightColor = equations.GetMergedLightDiffuseColor();
485✔
501
      }
485✔
502

503
      if (hasSpecularRenderMode) {
784✔
504
        lightingPasses.AddLast(TObjFlags.LIGHTMAP_SPECULAR);
255✔
505
        specularLightColor = equations.GetMergedLightSpecularColor();
255✔
506
      }
255✔
507
    }
529✔
508

509
    foreach (var lightingPass in lightingPasses) {
5,820✔
510
      IColorValue? color;
511
      IScalarValue? alpha;
512

513
      switch (lightingPass) {
1,362!
514
        case TObjFlags.LIGHTMAP_DIFFUSE: {
578✔
515
          color = diffuseSurfaceColor;
578✔
516
          alpha = diffuseSurfaceAlpha;
578✔
517
          break;
578✔
518
        }
519
        case TObjFlags.LIGHTMAP_AMBIENT: {
529✔
520
          color = ambientSurfaceColor;
529✔
521
          alpha = ambientSurfaceAlpha;
529✔
522
          break;
529✔
523
        }
524
        case TObjFlags.LIGHTMAP_SPECULAR: {
255✔
525
          color = specularSurfaceColor;
255✔
526
          alpha = specularSurfaceAlpha;
255✔
527
          break;
255✔
528
        }
529
        default: throw new NotImplementedException();
×
530
      }
531

532
      for (var i = 0; i < tObjsAndFinTextures.Count; ++i) {
6,495✔
533
        var (tObj, _) = tObjsAndFinTextures[i];
1,257✔
534
        if (!tObj.Flags.CheckFlag(lightingPass)) {
2,056✔
535
          continue;
799✔
536
        }
537

538
        this.PerformTextureLightingPass_(
458✔
539
            tObj,
458✔
540
            i,
458✔
541
            equations,
458✔
542
            colorOps,
458✔
543
            scalarOps,
458✔
544
            ref color,
458✔
545
            ref alpha);
458✔
546

547
        switch (lightingPass) {
458!
548
          case TObjFlags.LIGHTMAP_DIFFUSE: {
441✔
549
            diffuseSurfaceColor = color;
441✔
550
            diffuseSurfaceAlpha = alpha;
441✔
551
            break;
441✔
552
          }
553
          case TObjFlags.LIGHTMAP_AMBIENT: {
×
554
            ambientSurfaceColor = color;
×
555
            ambientSurfaceAlpha = alpha;
×
556
            break;
×
557
          }
558
          case TObjFlags.LIGHTMAP_SPECULAR: {
17✔
559
            specularSurfaceColor = color;
17✔
560
            specularSurfaceAlpha = alpha;
17✔
561
            break;
17✔
562
          }
563
        }
564
      }
458✔
565
    }
1,362✔
566

567
    var ambientAndDiffuseLightingColor = colorOps.Add(
578✔
568
        colorOps.Multiply(ambientSurfaceColor, ambientLightColor),
578✔
569
        diffuseLightColor);
578✔
570

571
    var ambientAndDiffuseComponent = colorOps.Multiply(
578✔
572
        ambientAndDiffuseLightingColor,
578✔
573
        diffuseSurfaceColor);
578✔
574

575
    var specularComponent =
578✔
576
        colorOps.Multiply(specularSurfaceColor, specularLightColor);
578✔
577

578
    // Performs ext lighting pass
579
    var extLightingColor = colorOps.Add(
578✔
580
        ambientAndDiffuseComponent,
578✔
581
        specularComponent);
578✔
582
    var extLightingAlpha = diffuseSurfaceAlpha;
578✔
583

584
    for (var i = 0; i < tObjsAndFinTextures.Count; ++i) {
2,716✔
585
      var (tObj, _) = tObjsAndFinTextures[i];
520✔
586
      if (!tObj.Flags.CheckFlag(TObjFlags.LIGHTMAP_EXT)) {
980✔
587
        continue;
460✔
588
      }
589

590
      this.PerformTextureLightingPass_(
60✔
591
          tObj,
60✔
592
          i,
60✔
593
          equations,
60✔
594
          colorOps,
60✔
595
          scalarOps,
60✔
596
          ref extLightingColor,
60✔
597
          ref extLightingAlpha);
60✔
598
    }
60✔
599

600
    // Sets up output colors
601
    var outputColor = extLightingColor;
578✔
602
    var outputAlpha = diffuseSurfaceAlpha;
578✔
603

604
    equations.SetOutputColorAlpha((outputColor, outputAlpha));
578✔
605
  }
578✔
606

607
  private void PerformTextureLightingPass_(
608
      TObj tObj,
609
      int textureIndex,
610
      IFixedFunctionEquations<FixedFunctionSource> equations,
611
      IColorOps colorOps,
612
      IScalarOps scalarOps,
613
      ref IColorValue color,
614
      ref IScalarValue alpha
615
  ) {
518✔
616
    var textureColor = equations.CreateOrGetColorInput(
518✔
617
        FixedFunctionSource.TEXTURE_COLOR_0 + textureIndex);
518✔
618
    var textureAlpha = equations.CreateOrGetScalarInput(
518✔
619
        FixedFunctionSource.TEXTURE_ALPHA_0 + textureIndex);
518✔
620

621
    switch (tObj.Flags.GetColorMap()) {
518!
622
      case ColorMap.NONE:
623
      case ColorMap.PASS: {
×
624
        // As you might guess from the name, does nothing.
625
        break;
×
626
      }
627
      case ColorMap.ALPHA_MASK: {
9✔
628
        // TODO: Is this right?
629
        color = colorOps.MixWithScalar(color, textureColor, textureAlpha);
9✔
630
        break;
9✔
631
      }
632
      case ColorMap.RGB_MASK: {
×
633
        // TODO: What should this do?
634
        break;
×
635
      }
636
      case ColorMap.BLEND: {
229✔
637
        color = colorOps.MixWithConstant(color,
229✔
638
                                         textureColor,
229✔
639
                                         tObj.Blending);
229✔
640
        break;
229✔
641
      }
642
      case ColorMap.MODULATE: {
94✔
643
        color = colorOps.Multiply(color, textureColor);
94✔
644
        break;
94✔
645
      }
646
      case ColorMap.REPLACE: {
186✔
647
        color = textureColor;
186✔
648
        break;
186✔
649
      }
650
      case ColorMap.ADD: {
×
651
        color = colorOps.Add(
×
652
            color,
×
653
            colorOps.MultiplyWithScalar(textureColor, textureAlpha));
×
654
        break;
×
655
      }
656
      case ColorMap.SUB: {
×
657
        color = colorOps.Subtract(
×
658
            color,
×
659
            colorOps.MultiplyWithScalar(textureColor, textureAlpha));
×
660
        break;
×
661
      }
662
    }
663

664
    switch (tObj.Flags.GetAlphaMap()) {
518!
665
      case AlphaMap.NONE:
666
      case AlphaMap.PASS: {
497✔
667
        // As you might guess from the name, does nothing.
668
        break;
497✔
669
      }
670
      case AlphaMap.ALPHA_MASK: {
×
671
        // TODO: What should this do?
672
        break;
×
673
      }
674
      case AlphaMap.BLEND: {
19✔
675
        alpha = scalarOps.MixWithConstant(
19✔
676
            alpha,
19✔
677
            textureAlpha,
19✔
678
            tObj.Blending);
19✔
679
        break;
19✔
680
      }
681
      case AlphaMap.MODULATE: {
×
682
        alpha = scalarOps.Multiply(alpha, textureAlpha);
×
683
        break;
×
684
      }
685
      case AlphaMap.REPLACE: {
2✔
686
        alpha = textureAlpha;
2✔
687
        break;
2✔
688
      }
689
      case AlphaMap.ADD: {
×
690
        alpha = scalarOps.Add(alpha, textureAlpha);
×
691
        break;
×
692
      }
693
      case AlphaMap.SUB: {
×
694
        alpha = scalarOps.Subtract(alpha, textureAlpha);
×
695
        break;
×
696
      }
697
    }
698
  }
518✔
699
}
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