Compare commits

..

6 Commits

18 changed files with 1260 additions and 170 deletions

View File

@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
namespace KeepersCompound.LGS.Database.Chunks;
@ -46,6 +45,15 @@ public class BrList : IChunk
x = reader.ReadUInt16();
y = reader.ReadUInt16();
}
public void Write(BinaryWriter writer)
{
writer.Write(id);
writer.Write(rot);
writer.Write(scale);
writer.Write(x);
writer.Write(y);
}
};
public short id;
@ -104,6 +112,34 @@ public class BrList : IChunk
txs = Array.Empty<TexInfo>();
}
}
public void Write(BinaryWriter writer)
{
writer.Write(id);
writer.Write(time);
writer.Write(brushInfo);
writer.Write(textureId);
writer.Write((byte)media);
writer.Write(flags);
writer.WriteVec3(position);
writer.WriteVec3(size);
writer.WriteRotation(angle);
writer.Write(currentFaceIndex);
writer.Write(gridLineSpacing);
writer.WriteVec3(gridPhaseShift);
writer.WriteRotation(gridOrientation);
writer.Write(gridEnabled);
writer.Write(numFaces);
writer.Write(edgeSelected);
writer.Write(pointSelected);
writer.Write(useFlag);
writer.Write(groupId);
writer.Write(new byte[4]);
foreach (var info in txs)
{
info.Write(writer);
}
}
}
public ChunkHeader Header { get; set; }
@ -120,6 +156,9 @@ public class BrList : IChunk
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
foreach (var brush in Brushes)
{
brush.Write(writer);
}
}
}

View File

@ -1,6 +1,4 @@
using System;
using System.IO;
using System.Text;
namespace KeepersCompound.LGS.Database.Chunks;
@ -16,6 +14,6 @@ public class GamFile : IChunk
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
writer.WriteNullString(fileName, 256);
}
}

View File

@ -1,4 +1,3 @@
using System.Collections.Generic;
using System.IO;
@ -32,6 +31,11 @@ public record LinkId
{
return _data;
}
public void Write(BinaryWriter writer)
{
writer.Write(_data);
}
}
public class LinkChunk : IChunk, IMergable
@ -50,6 +54,14 @@ public class LinkChunk : IChunk, IMergable
destination = reader.ReadInt32();
relation = reader.ReadUInt16();
}
public void Write(BinaryWriter writer)
{
linkId.Write(writer);
writer.Write(source);
writer.Write(destination);
writer.Write(relation);
}
}
public ChunkHeader Header { get; set; }
@ -66,7 +78,10 @@ public class LinkChunk : IChunk, IMergable
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
foreach (var link in links)
{
link.Write(writer);
}
}
public void Merge(IMergable other)
@ -88,6 +103,12 @@ public class LinkDataMetaProp : IChunk, IMergable
linkId = new LinkId(reader.ReadUInt32());
priority = reader.ReadInt32();
}
public void Write(BinaryWriter writer)
{
linkId.Write(writer);
writer.Write(priority);
}
}
public ChunkHeader Header { get; set; }
@ -106,7 +127,11 @@ public class LinkDataMetaProp : IChunk, IMergable
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
writer.Write(DataSize);
foreach (var data in linkData)
{
data.Write(writer);
}
}
public void Merge(IMergable other)

View File

@ -0,0 +1,75 @@
using System.IO;
namespace KeepersCompound.LGS.Database.Chunks;
public enum SoftnessMode
{
Standard,
HighFourPoint,
HighFivePoint,
HighNinePoint,
MediumFourPoint,
MediumFivePoint,
MediumNinePoint,
LowFourPoint,
}
public class LmParams : IChunk
{
public enum LightingMode
{
Quick,
Raycast,
Objcast,
}
public enum DepthMode
{
Lm16,
Lm32,
Lm32x,
}
public ChunkHeader Header { get; set; }
public float Attenuation { get; set; }
public float Saturation { get; set; }
public LightingMode ShadowType { get; set; }
public SoftnessMode ShadowSoftness { get; set; }
public float CenterWeight { get; set; }
public DepthMode ShadowDepth { get; set; }
public bool LightmappedWater { get; set; }
public int LightmapScale { get; set; }
private int _dataSize;
private uint _unknown;
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
{
_dataSize = reader.ReadInt32();
Attenuation = reader.ReadSingle();
Saturation = reader.ReadSingle();
ShadowType = (LightingMode)reader.ReadUInt32();
ShadowSoftness = (SoftnessMode)reader.ReadUInt32();
CenterWeight = reader.ReadSingle();
ShadowDepth = (DepthMode)reader.ReadUInt32();
LightmappedWater = reader.ReadBoolean();
reader.ReadBytes(3);
LightmapScale = reader.ReadInt32();
_unknown = reader.ReadUInt32();
}
public void WriteData(BinaryWriter writer)
{
writer.Write(_dataSize);
writer.Write(Attenuation);
writer.Write(Saturation);
writer.Write((uint)ShadowType);
writer.Write((uint)ShadowSoftness);
writer.Write(CenterWeight);
writer.Write((uint)ShadowDepth);
writer.Write(LightmappedWater);
writer.Write(new byte[3]);
writer.Write(LightmapScale);
writer.Write(_unknown);
}
}

View File

@ -14,6 +14,12 @@ public class Property
objectId = reader.ReadInt32();
length = (int)reader.ReadUInt32();
}
public virtual void Write(BinaryWriter writer)
{
writer.Write(objectId);
writer.Write((uint)length);
}
}
public class PropertyChunk<T> : IChunk, IMergable where T : Property, new()
@ -34,7 +40,10 @@ public class PropertyChunk<T> : IChunk, IMergable where T : Property, new()
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
foreach (var prop in properties)
{
prop.Write(writer);
}
}
public void Merge(IMergable other)
@ -52,6 +61,12 @@ public class PropGeneric : Property
base.Read(reader);
data = reader.ReadBytes(length);
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(data);
}
}
public class PropBool : Property
@ -63,6 +78,12 @@ public class PropBool : Property
base.Read(reader);
value = reader.ReadInt32() != 0;
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(value ? 1 : 0);
}
}
public class PropInt : Property
@ -74,6 +95,12 @@ public class PropInt : Property
base.Read(reader);
value = reader.ReadInt32();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(value);
}
}
public class PropLabel : Property
@ -85,6 +112,12 @@ public class PropLabel : Property
base.Read(reader);
value = reader.ReadNullString(length);
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.WriteNullString(value, length);
}
}
public class PropString : Property
@ -98,6 +131,13 @@ public class PropString : Property
stringLength = reader.ReadInt32();
value = reader.ReadNullString(stringLength);
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(stringLength);
writer.WriteNullString(value, stringLength);
}
}
public class PropFloat : Property
@ -109,6 +149,12 @@ public class PropFloat : Property
base.Read(reader);
value = reader.ReadSingle();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(value);
}
}
public class PropVector : Property
@ -120,6 +166,12 @@ public class PropVector : Property
base.Read(reader);
value = reader.ReadVec3();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.WriteVec3(value);
}
}
public class PropRenderType : Property
@ -140,6 +192,12 @@ public class PropRenderType : Property
base.Read(reader);
mode = (Mode)reader.ReadUInt32();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write((uint)mode);
}
}
public class PropSlayResult : Property
@ -156,6 +214,12 @@ public class PropSlayResult : Property
base.Read(reader);
effect = (Effect)reader.ReadUInt32();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write((uint)effect);
}
}
public class PropInventoryType : Property
@ -172,6 +236,12 @@ public class PropInventoryType : Property
base.Read(reader);
type = (Slot)reader.ReadUInt32();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write((uint)type);
}
}
public class PropCollisionType : Property
@ -194,6 +264,19 @@ public class PropCollisionType : Property
NoResult = (flags & (0x1 << 4)) != 0;
FullCollisionSound = (flags & (0x1 << 5)) != 0;
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
var flags = 0u;
if (Bounce) flags += 1;
if (DestroyOnImpact) flags += 2;
if (SlayOnImpact) flags += 4;
if (NoCollisionSound) flags += 8;
if (NoResult) flags += 16;
if (FullCollisionSound) flags += 32;
writer.Write(flags);
}
}
public class PropPosition : Property
@ -208,6 +291,14 @@ public class PropPosition : Property
reader.ReadBytes(4); // Runtime Cell/Hint in editor
Rotation = reader.ReadRotation();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.WriteVec3(Location);
writer.Write(new byte[4]);
writer.WriteRotation(Rotation);
}
}
public class PropLight : Property
@ -228,6 +319,123 @@ public class PropLight : Property
reader.ReadBytes(3);
InnerRadius = reader.ReadSingle();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(Brightness);
writer.WriteVec3(Offset);
writer.Write(Radius);
writer.Write(QuadLit);
writer.Write(new byte[3]);
writer.Write(InnerRadius);
}
}
public class PropAnimLight : Property
{
public enum AnimMode
{
FlipMinMax,
SlideSmoothly,
Random,
MinBrightness,
MaxBrightness,
ZeroBrightness,
SmoothlyBrighten,
SmoothlyDim,
RandomButCoherent,
FlickerMinMax,
}
// Standard light props
public float Brightness;
public Vector3 Offset;
public float Radius;
public float InnerRadius;
public bool QuadLit;
public bool Dynamic;
// Animation
public AnimMode Mode;
public int MsToBrighten;
public int MsToDim;
public float MinBrightness;
public float MaxBrightness;
// Start state
public float CurrentBrightness;
public bool Rising;
public int Timer;
public bool Inactive;
// World rep info
public bool Refresh; // Not relevant to us. It's used to tell dromed it needs to relight
public ushort LightTableMapIndex;
public ushort LightTableLightIndex;
public ushort CellsReached;
private int _unknown;
public override void Read(BinaryReader reader)
{
base.Read(reader);
Brightness = reader.ReadSingle();
Offset = reader.ReadVec3();
Refresh = reader.ReadBoolean();
reader.ReadBytes(3);
LightTableMapIndex = reader.ReadUInt16();
CellsReached = reader.ReadUInt16();
LightTableLightIndex = reader.ReadUInt16();
Mode = (AnimMode)reader.ReadUInt16();
MsToBrighten = reader.ReadInt32();
MsToDim = reader.ReadInt32();
MinBrightness = reader.ReadSingle();
MaxBrightness = reader.ReadSingle();
CurrentBrightness = reader.ReadSingle();
Rising = reader.ReadBoolean();
reader.ReadBytes(3);
Timer = reader.ReadInt32();
Inactive = reader.ReadBoolean();
reader.ReadBytes(3);
Radius = reader.ReadSingle();
_unknown = reader.ReadInt32();
QuadLit = reader.ReadBoolean();
reader.ReadBytes(3);
InnerRadius = reader.ReadSingle();
Dynamic = reader.ReadBoolean();
reader.ReadBytes(3);
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(Brightness);
writer.WriteVec3(Offset);
writer.Write(Refresh);
writer.Write(new byte[3]);
writer.Write(LightTableMapIndex);
writer.Write(CellsReached);
writer.Write(LightTableLightIndex);
writer.Write((ushort)Mode);
writer.Write(MsToBrighten);
writer.Write(MsToDim);
writer.Write(MinBrightness);
writer.Write(MaxBrightness);
writer.Write(CurrentBrightness);
writer.Write(Rising);
writer.Write(new byte[3]);
writer.Write(Timer);
writer.Write(Inactive);
writer.Write(new byte[3]);
writer.Write(Radius);
writer.Write(_unknown);
writer.Write(QuadLit);
writer.Write(new byte[3]);
writer.Write(InnerRadius);
writer.Write(Dynamic);
writer.Write(new byte[3]);
}
}
public class PropLightColor : Property
@ -241,6 +449,13 @@ public class PropLightColor : Property
Hue = reader.ReadSingle();
Saturation = reader.ReadSingle();
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(Hue);
writer.Write(Saturation);
}
}
public class PropSpotlight : Property
@ -255,6 +470,14 @@ public class PropSpotlight : Property
OuterAngle = reader.ReadSingle();
reader.ReadBytes(4); // Z is unused
}
public override void Write(BinaryWriter writer)
{
base.Write(writer);
writer.Write(InnerAngle);
writer.Write(OuterAngle);
writer.Write(new byte[4]);
}
}
public class PropSpotlightAndAmbient : Property
@ -270,21 +493,12 @@ public class PropSpotlightAndAmbient : Property
OuterAngle = reader.ReadSingle();
SpotBrightness = reader.ReadSingle();
}
}
// TODO: Work out what this property actually does
public class PropLightBasedAlpha : Property
{
public bool Enabled;
public Vector2 AlphaRange;
public Vector2 LightRange;
public override void Read(BinaryReader reader)
public override void Write(BinaryWriter writer)
{
base.Read(reader);
Enabled = reader.ReadBoolean();
reader.ReadBytes(3);
AlphaRange = reader.ReadVec2();
LightRange = new Vector2(reader.ReadInt32(), reader.ReadInt32());
base.Write(writer);
writer.Write(InnerAngle);
writer.Write(OuterAngle);
writer.Write(SpotBrightness);
}
}

View File

@ -0,0 +1,80 @@
using System.IO;
using System.Numerics;
namespace KeepersCompound.LGS.Database.Chunks;
public class RendParams : IChunk
{
public enum SunlightMode
{
SingleUnshadowed,
QuadObjcastShadows,
QuadUnshadowed,
SingleObjcastShadows,
}
public ChunkHeader Header { get; set; }
public string palette;
public Vector3 ambientLight;
public int useSunlight;
public SunlightMode sunlightMode;
public Vector3 sunlightDirection;
public float sunlightHue;
public float sunlightSaturation;
public float sunlightBrightness;
public float viewDistance;
public Vector3[] ambientLightZones;
public float globalAiVisBias;
public float[] ambientZoneAiVisBiases;
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
{
palette = reader.ReadNullString(16);
ambientLight = reader.ReadVec3();
useSunlight = reader.ReadInt32();
sunlightMode = (SunlightMode)reader.ReadUInt32();
sunlightDirection = reader.ReadVec3();
sunlightHue = reader.ReadSingle();
sunlightSaturation = reader.ReadSingle();
sunlightBrightness = reader.ReadSingle();
reader.ReadBytes(24);
viewDistance = reader.ReadSingle();
reader.ReadBytes(12);
ambientLightZones = new Vector3[8];
for (var i = 0; i < ambientLightZones.Length; i++)
{
ambientLightZones[i] = reader.ReadVec3();
}
globalAiVisBias = reader.ReadSingle();
ambientZoneAiVisBiases = new float[8];
for (var i = 0; i < ambientZoneAiVisBiases.Length; i++)
{
ambientZoneAiVisBiases[i] = reader.ReadSingle();
}
}
public void WriteData(BinaryWriter writer)
{
writer.WriteNullString(palette, 16);
writer.WriteVec3(ambientLight);
writer.Write(useSunlight);
writer.Write((uint)sunlightMode);
writer.WriteVec3(sunlightDirection);
writer.Write(sunlightHue);
writer.Write(sunlightSaturation);
writer.Write(sunlightBrightness);
writer.Write(new byte[24]);
writer.Write(viewDistance);
writer.Write(new byte[12]);
foreach (var lightZone in ambientLightZones)
{
writer.WriteVec3(lightZone);
}
writer.Write(globalAiVisBias);
foreach (var visBias in ambientZoneAiVisBiases)
{
writer.Write(visBias);
}
}
}

View File

@ -1,6 +1,4 @@
using System;
using System.IO;
using System.Text;
namespace KeepersCompound.LGS.Database.Chunks;
@ -16,6 +14,12 @@ public class TxList : IChunk
Tokens = reader.ReadBytes(4);
Name = reader.ReadNullString(16);
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(Tokens);
writer.WriteNullString(Name, 16);
}
}
@ -27,7 +31,6 @@ public class TxList : IChunk
public string[] Tokens { get; set; }
public Item[] Items { get; set; }
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
{
BlockSize = reader.ReadInt32();
@ -47,6 +50,16 @@ public class TxList : IChunk
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
writer.Write(BlockSize);
writer.Write(ItemCount);
writer.Write(TokenCount);
foreach (var token in Tokens)
{
writer.WriteNullString(token, 16);
}
foreach (var item in Items)
{
item.Write(writer);
}
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
@ -39,9 +40,20 @@ public class WorldRep : IChunk
_ => 1.0f,
};
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(Size);
writer.Write(Version);
writer.Write(Flags);
writer.Write(LightmapFormat);
writer.Write(LightmapScale);
writer.Write(DataSize);
writer.Write(CellCount);
}
}
public struct Cell
public class Cell
{
public struct Poly
{
@ -62,6 +74,17 @@ public class WorldRep : IChunk
MotionIndex = reader.ReadByte();
reader.ReadByte();
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(Flags);
writer.Write(VertexCount);
writer.Write(PlaneId);
writer.Write(ClutId);
writer.Write(Destination);
writer.Write(MotionIndex);
writer.Write((byte)0);
}
}
public struct RenderPoly
@ -82,9 +105,21 @@ public class WorldRep : IChunk
TextureMagnitude = reader.ReadSingle();
Center = reader.ReadVec3();
}
public readonly void Write(BinaryWriter writer)
{
writer.WriteVec3(TextureVectors.Item1);
writer.WriteVec3(TextureVectors.Item2);
writer.Write(TextureBases.Item1);
writer.Write(TextureBases.Item2);
writer.Write(TextureId);
writer.Write(CachedSurface);
writer.Write(TextureMagnitude);
writer.WriteVec3(Center);
}
}
public struct LightmapInfo
public class LightmapInfo
{
public (short, short) Bases { get; set; }
public short PaddedWidth { get; set; }
@ -104,11 +139,24 @@ public class WorldRep : IChunk
DynamicLightPtr = reader.ReadUInt32();
AnimLightBitmask = reader.ReadUInt32();
}
public void Write(BinaryWriter writer)
{
writer.Write(Bases.Item1);
writer.Write(Bases.Item2);
writer.Write(PaddedWidth);
writer.Write(Height);
writer.Write(Width);
writer.Write(DataPtr);
writer.Write(DynamicLightPtr);
writer.Write(AnimLightBitmask);
}
}
public struct Lightmap
public class Lightmap
{
public byte[] Pixels { get; set; }
private readonly bool[] _litLayers;
public List<byte[]> Pixels { get; set; }
public int Layers;
public int Width;
@ -117,44 +165,52 @@ public class WorldRep : IChunk
public Lightmap(BinaryReader reader, byte width, byte height, uint bitmask, int bytesPerPixel)
{
var count = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height * count;
Pixels = reader.ReadBytes(length);
Layers = count;
var layers = 1 + BitOperations.PopCount(bitmask);
var length = bytesPerPixel * width * height;
_litLayers = new bool[33];
Pixels = new List<byte[]>();
for (var i = 0; i < layers; i++)
{
Pixels.Add(reader.ReadBytes(length));
_litLayers[i] = true;
}
Layers = layers;
Width = width;
Height = height;
Bpp = bytesPerPixel;
}
public readonly Vector4 GetPixel(uint layer, uint x, uint y)
public Vector4 GetPixel(uint layer, uint x, uint y)
{
if (layer >= Layers || x >= Width || y >= Height)
{
return Vector4.Zero;
}
var idx = 0 + x * Bpp + y * Bpp * Width + layer * Bpp * Width * Height;
var pLayer = Pixels[(int)layer];
var idx = x * Bpp + y * Bpp * Width;
switch (Bpp)
{
case 1:
var raw1 = Pixels[idx];
var raw1 = pLayer[idx];
return new Vector4(raw1, raw1, raw1, 255) / 255.0f;
case 2:
var raw2 = Pixels[idx] + (Pixels[idx + 1] << 8);
var raw2 = pLayer[idx] + (pLayer[idx + 1] << 8);
return new Vector4(raw2 & 31, (raw2 >> 5) & 31, (raw2 >> 10) & 31, 31) / 31.0f;
case 4:
return new Vector4(Pixels[idx + 2], Pixels[idx + 1], Pixels[idx], Pixels[idx + 3]) / 255.0f;
return new Vector4(pLayer[idx + 2], pLayer[idx + 1], pLayer[idx], pLayer[idx + 3]) / 255.0f;
default:
return Vector4.Zero;
}
}
public readonly byte[] AsBytesRgba(int layer)
public byte[] AsBytesRgba(int layer)
{
ArgumentOutOfRangeException.ThrowIfLessThan(layer, 0, nameof(layer));
ArgumentOutOfRangeException.ThrowIfGreaterThan(layer, Layers, nameof(layer));
var pIdx = layer * Bpp * Width * Height;
var pLayer = Pixels[layer];
var pIdx = 0;
var length = 4 * Width * Height;
var bytes = new byte[length];
for (var i = 0; i < length; i += 4, pIdx += Bpp)
@ -162,30 +218,99 @@ public class WorldRep : IChunk
switch (Bpp)
{
case 1:
var raw1 = Pixels[pIdx];
var raw1 = pLayer[pIdx];
bytes[i] = raw1;
bytes[i + 1] = raw1;
bytes[i + 2] = raw1;
bytes[i + 3] = 255;
break;
case 2:
var raw2 = Pixels[pIdx] + (Pixels[pIdx + 1] << 8);
var raw2 = pLayer[pIdx] + (pLayer[pIdx + 1] << 8);
bytes[i] = (byte)(255 * (raw2 & 31) / 31.0f);
bytes[i + 1] = (byte)(255 * ((raw2 >> 5) & 31) / 31.0f);
bytes[i + 2] = (byte)(255 * ((raw2 >> 10) & 31) / 31.0f);
bytes[i + 3] = 255;
break;
case 4:
bytes[i] = Pixels[pIdx + 2];
bytes[i + 1] = Pixels[pIdx + 1];
bytes[i + 2] = Pixels[pIdx];
bytes[i + 3] = Pixels[pIdx + 3];
bytes[i] = pLayer[pIdx + 2];
bytes[i + 1] = pLayer[pIdx + 1];
bytes[i + 2] = pLayer[pIdx];
bytes[i + 3] = pLayer[pIdx + 3];
break;
}
}
return bytes;
}
// TODO: This ONLY works for rgba (bpp = 4)!!!
public void AddLight(int layer, int x, int y, float r, float g, float b)
{
var idx = (x + y * Width) * Bpp;
var pLayer = Pixels[layer];
pLayer[idx] = (byte)Math.Clamp(pLayer[idx] + r, 0, 255);
pLayer[idx + 1] = (byte)Math.Clamp(pLayer[idx + 1] + g, 0, 255);
pLayer[idx + 2] = (byte)Math.Clamp(pLayer[idx + 2] + b, 0, 255);
pLayer[idx + 3] = 255;
_litLayers[layer] = true;
}
public void AddLight(int layer, int x, int y, Vector3 color, float strength, bool hdr)
{
if (hdr)
{
strength /= 2.0f;
}
// We need to make sure we don't go over (255, 255, 255).
// If we just do Max(color, (255, 255, 255)) then we change
// the hue/saturation of coloured lights. Got to make sure we
// maintain the colour ratios.
var c = color * strength;
var ratio = Math.Max(Math.Max(Math.Max(0.0f, c.X), c.Y), c.Z) / 255.0f;
if (ratio > 1.0f)
{
c /= ratio;
}
AddLight(layer, x, y, c.Z, c.Y, c.X);
}
public void Reset(Vector3 ambientLight, bool hdr)
{
Layers = 33;
Pixels.Clear();
var bytesPerLayer = Width * Height * Bpp;
for (var i = 0; i < Layers; i++)
{
Pixels.Add(new byte[bytesPerLayer]);
for (var j = 0; j < bytesPerLayer; j++)
{
Pixels[i][j] = 0;
}
_litLayers[i] = false;
}
for (var y = 0; y < Height; y++)
{
for (var x = 0; x < Width; x++)
{
AddLight(0, x, y, ambientLight, 1.0f, hdr);
}
}
}
public void Write(BinaryWriter writer)
{
for (var i = 0; i < Layers; i++)
{
if (_litLayers[i])
{
writer.Write(Pixels[i]);
}
}
}
}
public byte VertexCount { get; set; }
@ -207,11 +332,11 @@ public class WorldRep : IChunk
public uint IndexCount { get; set; }
public byte[] Indices { get; set; }
public Plane[] Planes { get; set; }
public ushort[] AnimLights { get; set; }
public List<ushort> AnimLights { get; set; }
public LightmapInfo[] LightList { get; set; }
public Lightmap[] Lightmaps { get; set; }
public int LightIndexCount { get; set; }
public ushort[] LightIndices { get; set; }
public List<ushort> LightIndices { get; set; }
public Cell(BinaryReader reader, int bpp)
{
@ -254,10 +379,10 @@ public class WorldRep : IChunk
{
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
}
AnimLights = new ushort[AnimLightCount];
AnimLights = new List<ushort>(AnimLightCount);
for (var i = 0; i < AnimLightCount; i++)
{
AnimLights[i] = reader.ReadUInt16();
AnimLights.Add(reader.ReadUInt16());
}
LightList = new LightmapInfo[RenderPolyCount];
for (var i = 0; i < RenderPolyCount; i++)
@ -271,17 +396,269 @@ public class WorldRep : IChunk
Lightmaps[i] = new Lightmap(reader, info.Width, info.Height, info.AnimLightBitmask, bpp);
}
LightIndexCount = reader.ReadInt32();
LightIndices = new ushort[LightIndexCount];
LightIndices = new List<ushort>(LightIndexCount);
for (var i = 0; i < LightIndexCount; i++)
{
LightIndices[i] = reader.ReadUInt16();
LightIndices.Add(reader.ReadUInt16());
}
}
public void Write(BinaryWriter writer)
{
writer.Write(VertexCount);
writer.Write(PolyCount);
writer.Write(RenderPolyCount);
writer.Write(PortalPolyCount);
writer.Write(PlaneCount);
writer.Write(Medium);
writer.Write(Flags);
writer.Write(PortalVertices);
writer.Write(NumVList);
writer.Write(AnimLightCount);
writer.Write(MotionIndex);
writer.WriteVec3(SphereCenter);
writer.Write(SphereRadius);
foreach (var vertex in Vertices)
{
writer.WriteVec3(vertex);
}
foreach (var poly in Polys)
{
poly.Write(writer);
}
foreach (var renderPoly in RenderPolys)
{
renderPoly.Write(writer);
}
writer.Write(IndexCount);
writer.Write(Indices);
foreach (var plane in Planes)
{
writer.WriteVec3(plane.Normal);
writer.Write(plane.D);
}
foreach (var animLight in AnimLights)
{
writer.Write(animLight);
}
foreach (var lightmapInfo in LightList)
{
lightmapInfo.Write(writer);
}
foreach (var lightmap in Lightmaps)
{
lightmap.Write(writer);
}
writer.Write(LightIndexCount);
foreach (var lightIndex in LightIndices)
{
writer.Write(lightIndex);
}
}
}
public struct BspTree
{
public struct Node
{
int parentIndex; // TODO: Split the flags out of this
int cellId;
int planeId;
uint insideIndex;
uint outsideIndex;
public Node(BinaryReader reader)
{
parentIndex = reader.ReadInt32();
cellId = reader.ReadInt32();
planeId = reader.ReadInt32();
insideIndex = reader.ReadUInt32();
outsideIndex = reader.ReadUInt32();
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(parentIndex);
writer.Write(cellId);
writer.Write(planeId);
writer.Write(insideIndex);
writer.Write(outsideIndex);
}
}
public uint PlaneCount;
public uint NodeCount;
public Plane[] Planes;
public Node[] Nodes;
public BspTree(BinaryReader reader)
{
PlaneCount = reader.ReadUInt32();
Planes = new Plane[PlaneCount];
for (var i = 0; i < PlaneCount; i++)
{
Planes[i] = new Plane(reader.ReadVec3(), reader.ReadSingle());
}
NodeCount = reader.ReadUInt32();
Nodes = new Node[NodeCount];
for (var i = 0; i < NodeCount; i++)
{
Nodes[i] = new Node(reader);
}
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(PlaneCount);
foreach (var plane in Planes)
{
writer.WriteVec3(plane.Normal);
writer.Write(plane.D);
}
writer.Write(NodeCount);
foreach (var node in Nodes)
{
node.Write(writer);
}
}
}
public class LightTable
{
public struct LightData
{
public Vector3 Location;
public Vector3 Direction;
public Vector3 Color;
public float InnerAngle; // I'm pretty sure these are the spotlight angles
public float OuterAngle;
public float Radius;
public LightData(BinaryReader reader)
{
Location = reader.ReadVec3();
Direction = reader.ReadVec3();
Color = reader.ReadVec3();
InnerAngle = reader.ReadSingle();
OuterAngle = reader.ReadSingle();
Radius = reader.ReadSingle();
}
public readonly void Write(BinaryWriter writer)
{
writer.WriteVec3(Location);
writer.WriteVec3(Direction);
writer.WriteVec3(Color);
writer.Write(InnerAngle);
writer.Write(OuterAngle);
writer.Write(Radius);
}
}
public struct AnimCellMap
{
public ushort CellIndex;
public ushort LightIndex;
public AnimCellMap(BinaryReader reader)
{
CellIndex = reader.ReadUInt16();
LightIndex = reader.ReadUInt16();
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(CellIndex);
writer.Write(LightIndex);
}
}
public int LightCount;
public int DynamicLightCount;
public int AnimMapCount;
public List<LightData> Lights;
public LightData[] ScratchpadLights;
public List<AnimCellMap> AnimCellMaps;
// TODO: Support olddark
public LightTable(BinaryReader reader)
{
LightCount = reader.ReadInt32();
DynamicLightCount = reader.ReadInt32();
var totalLightCount = LightCount + DynamicLightCount;
Lights = new List<LightData>(totalLightCount);
for (var i = 0; i < totalLightCount; i++)
{
Lights.Add(new LightData(reader));
}
ScratchpadLights = new LightData[32];
for (var i = 0; i < 32; i++)
{
ScratchpadLights[i] = new LightData(reader);
}
AnimMapCount = reader.ReadInt32();
AnimCellMaps = new List<AnimCellMap>(AnimMapCount);
for (var i = 0; i < AnimMapCount; i++)
{
AnimCellMaps.Add(new AnimCellMap(reader));
}
}
public void Write(BinaryWriter writer)
{
writer.Write(LightCount);
writer.Write(DynamicLightCount);
foreach (var light in Lights)
{
light.Write(writer);
}
foreach (var light in ScratchpadLights)
{
light.Write(writer);
}
writer.Write(AnimMapCount);
foreach (var map in AnimCellMaps)
{
map.Write(writer);
}
}
public void Reset()
{
// Seems light we store something for sunlight at index 0 even if sunlight isn't being used
// TODO: Work out what to actually *put* here
LightCount = 1;
Lights.Clear();
Lights.Add(new LightData());
DynamicLightCount = 0;
AnimMapCount = 0;
AnimCellMaps.Clear();
}
public void AddLight(LightData data, bool dynamicLight = false)
{
if (dynamicLight)
{
DynamicLightCount++;
}
else
{
LightCount++;
}
Lights.Add(data);
}
}
public ChunkHeader Header { get; set; }
public WrHeader DataHeader { get; set; }
public Cell[] Cells { get; set; }
public BspTree Bsp { get; set; }
public LightTable LightingTable { get; set; }
private byte[] _unknown;
private byte[] _unreadData;
public void ReadData(BinaryReader reader, DbFile.TableOfContents.Entry entry)
{
@ -294,11 +671,27 @@ public class WorldRep : IChunk
Cells[i] = new Cell(reader, bpp);
}
Bsp = new BspTree(reader);
// TODO: Work out what this is
_unknown = reader.ReadBytes(Cells.Length);
LightingTable = new LightTable(reader);
// TODO: All the other info lol
var length = entry.Offset + entry.Size + 24 - reader.BaseStream.Position;
_unreadData = reader.ReadBytes((int)length);
}
public void WriteData(BinaryWriter writer)
{
throw new System.NotImplementedException();
DataHeader.Write(writer);
foreach (var cell in Cells)
{
cell.Write(writer);
}
Bsp.Write(writer);
writer.Write(_unknown);
LightingTable.Write(writer);
writer.Write(_unreadData);
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
@ -32,13 +31,13 @@ public class DbFile
}
}
public readonly struct TableOfContents
public struct TableOfContents
{
public readonly struct Entry
public struct Entry
{
public string Name { get; }
public uint Offset { get; }
public uint Size { get; }
public string Name;
public uint Offset;
public uint Size;
public Entry(BinaryReader reader)
{
@ -47,10 +46,16 @@ public class DbFile
Size = reader.ReadUInt32();
}
public override string ToString()
public override readonly string ToString()
{
// return $"Name: {Name}, Offset: {O}"
return base.ToString();
return $"Name: {Name}, Offset: {Offset}, Size: {Size}";
}
public readonly void Write(BinaryWriter writer)
{
writer.WriteNullString(Name, 12);
writer.Write(Offset);
writer.Write(Size);
}
}
@ -65,6 +70,15 @@ public class DbFile
Items.Add(new Entry(reader));
Items.Sort((a, b) => a.Offset.CompareTo(b.Offset));
}
public readonly void Write(BinaryWriter writer)
{
writer.Write(ItemCount);
foreach (var entry in Items)
{
entry.Write(writer);
}
}
}
public FHeader Header { get; private set; }
@ -73,6 +87,7 @@ public class DbFile
public DbFile(string filename)
{
// TODO: Throw rather than return
if (!File.Exists(filename)) return;
using MemoryStream stream = new(File.ReadAllBytes(filename));
@ -91,6 +106,43 @@ public class DbFile
}
}
public void Save(string filename)
{
using var stream = File.Open(filename, FileMode.Create);
using var writer = new BinaryWriter(stream, Encoding.UTF8, false);
Header.Write(writer);
for (var i = 0; i < Toc.ItemCount; i++)
{
var item = Toc.Items[i];
var pos = stream.Position;
var chunk = Chunks[item.Name];
chunk.Write(writer);
var size = stream.Position - pos - 24;
item.Offset = (uint)pos;
item.Size = (uint)size;
Toc.Items[i] = item;
}
var tocOffset = (uint)stream.Position;
Toc.Write(writer);
stream.Seek(0, SeekOrigin.Begin);
writer.Write(tocOffset);
}
public bool TryGetChunk<T>(string name, out T chunk)
{
if (Chunks.TryGetValue(name, out var rawChunk))
{
chunk = (T)rawChunk;
return true;
}
chunk = default;
return false;
}
private static IChunk NewChunk(string entryName)
{
return entryName switch
@ -101,6 +153,8 @@ public class DbFile
"TXLIST" => new TxList(),
"WREXT" => new WorldRep(),
"BRLIST" => new BrList(),
"LM_PARAM" => new LmParams(),
"RENDPARAMS" => new RendParams(),
"P$ModelName" => new PropertyChunk<PropLabel>(),
"P$Scale" => new PropertyChunk<PropVector>(),
"P$RenderTyp" => new PropertyChunk<PropRenderType>(),
@ -108,6 +162,10 @@ public class DbFile
"P$OTxtRepr1" => new PropertyChunk<PropString>(),
"P$OTxtRepr2" => new PropertyChunk<PropString>(),
"P$OTxtRepr3" => new PropertyChunk<PropString>(),
"P$Light" => new PropertyChunk<PropLight>(),
"P$AnimLight" => new PropertyChunk<PropAnimLight>(),
"P$LightColo" => new PropertyChunk<PropLightColor>(),
"P$Spotlight" => new PropertyChunk<PropSpotlight>(),
"P$RenderAlp" => new PropertyChunk<PropFloat>(),
"LD$MetaProp" => new LinkDataMetaProp(),
_ when entryName.StartsWith("L$") => new LinkChunk(),

View File

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using KeepersCompound.LGS.Database.Chunks;
@ -7,7 +6,7 @@ namespace KeepersCompound.LGS.Database;
public class ObjectHierarchy
{
public class DarkObject
private class DarkObject
{
public int objectId;
public int parentId;
@ -20,7 +19,7 @@ public class ObjectHierarchy
properties = new Dictionary<string, Property>();
}
public T GetProperty<T>(string propName) where T : Property
public T? GetProperty<T>(string propName) where T : Property
{
if (properties.TryGetValue(propName, out var prop))
{
@ -38,18 +37,17 @@ public class ObjectHierarchy
T GetMergedChunk<T>(string name) where T : IMergable
{
if (db.Chunks.TryGetValue(name, out var rawChunk))
if (!db.TryGetChunk<T>(name, out var chunk))
{
var chunk = (T)rawChunk;
if (gam != null && gam.Chunks.TryGetValue(name, out var rawGamChunk))
{
var gamChunk = (T)rawGamChunk;
chunk.Merge(gamChunk);
}
return chunk;
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
}
throw new ArgumentException($"No chunk with name ({name}) found", nameof(name));
if (gam != null && gam.TryGetChunk<T>(name, out var gamChunk))
{
gamChunk.Merge(chunk);
return gamChunk;
}
return chunk;
}
// Add parentages
@ -83,11 +81,12 @@ public class ObjectHierarchy
foreach (var prop in chunk.properties)
{
var id = prop.objectId;
if (!_objects.ContainsKey(id))
if (!_objects.TryGetValue(id, out var value))
{
_objects.Add(id, new DarkObject(id));
value = new DarkObject(id);
_objects.Add(id, value);
}
_objects[id].properties.TryAdd(name, prop);
value.properties.TryAdd(name, prop);
}
}
@ -99,9 +98,14 @@ public class ObjectHierarchy
AddProp<PropString>("P$OTxtRepr2");
AddProp<PropString>("P$OTxtRepr3");
AddProp<PropFloat>("P$RenderAlp");
AddProp<PropLight>("P$Light");
AddProp<PropAnimLight>("P$AnimLight");
AddProp<PropLightColor>("P$LightColo");
AddProp<PropSpotlight>("P$Spotlight");
}
public T GetProperty<T>(int objectId, string propName) where T : Property
// TODO: Work out if there's some nice way to automatically decide if we inherit
public T? GetProperty<T>(int objectId, string propName, bool inherit = true) where T : Property
{
if (!_objects.ContainsKey(objectId))
{
@ -117,7 +121,7 @@ public class ObjectHierarchy
}
var prop = obj.GetProperty<T>(propName);
if (prop != null)
if (prop != null || !inherit)
{
return prop;
}

View File

@ -1,3 +1,4 @@
using System;
using System.IO;
using System.Numerics;
using System.Text;
@ -12,16 +13,37 @@ public static class Extensions
return raw * 360 / (ushort.MaxValue + 1);
}
public static void WriteRotation(this BinaryWriter writer, Vector3 rotation)
{
var raw = rotation * (ushort.MaxValue + 1) / 360;
writer.Write((ushort)raw.X);
writer.Write((ushort)raw.Y);
writer.Write((ushort)raw.Z);
}
public static Vector3 ReadVec3(this BinaryReader reader)
{
return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle());
}
public static void WriteVec3(this BinaryWriter writer, Vector3 vec)
{
writer.Write(vec.X);
writer.Write(vec.Y);
writer.Write(vec.Z);
}
public static Vector2 ReadVec2(this BinaryReader reader)
{
return new Vector2(reader.ReadSingle(), reader.ReadSingle());
}
public static void WriteVec2(this BinaryWriter writer, Vector2 vec)
{
writer.Write(vec.X);
writer.Write(vec.Y);
}
public static string ReadNullString(this BinaryReader reader, int length)
{
var tmpName = Encoding.UTF8.GetString(reader.ReadBytes(length));
@ -29,4 +51,12 @@ public static class Extensions
if (idx >= 0) tmpName = tmpName[..idx];
return tmpName;
}
public static void WriteNullString(this BinaryWriter writer, string nullString, int length)
{
var writeBytes = new byte[length];
var stringBytes = Encoding.UTF8.GetBytes(nullString);
stringBytes[..Math.Min(length, stringBytes.Length)].CopyTo(writeBytes, 0);
writer.Write(writeBytes);
}
}

View File

@ -1,4 +1,3 @@
using System;
using System.IO;
using System.Numerics;
using System.Text;
@ -94,6 +93,54 @@ public class ModelFile
}
}
public struct SubObject
{
public string Name;
public byte Type;
public int Joint;
public float MinJointValue;
public float MaxJointValue;
public Matrix4x4 Transform;
public short Child;
public short Next;
public ushort VhotIdx;
public ushort VhotCount;
public ushort PointIdx;
public ushort PointCount;
public ushort LightIdx;
public ushort LightCount;
public ushort NormalIdx;
public ushort NormalCount;
public ushort NodeIdx;
public ushort NodeCount;
public SubObject(BinaryReader reader)
{
Name = reader.ReadNullString(8);
Type = reader.ReadByte();
Joint = reader.ReadInt32();
MinJointValue = reader.ReadSingle();
MaxJointValue = reader.ReadSingle();
var v1 = reader.ReadVec3();
var v2 = reader.ReadVec3();
var v3 = reader.ReadVec3();
var v4 = reader.ReadVec3();
Transform = new Matrix4x4(v1.X, v1.Y, v1.Z, 0, v2.X, v2.Y, v2.Z, 0, v3.X, v3.Y, v3.Z, 0, v4.X, v4.Y, v4.Z, 1);
Child = reader.ReadInt16();
Next = reader.ReadInt16();
VhotIdx = reader.ReadUInt16();
VhotCount = reader.ReadUInt16();
PointIdx = reader.ReadUInt16();
PointCount = reader.ReadUInt16();
LightIdx = reader.ReadUInt16();
LightCount = reader.ReadUInt16();
NormalIdx = reader.ReadUInt16();
NormalCount = reader.ReadUInt16();
NodeIdx = reader.ReadUInt16();
NodeCount = reader.ReadUInt16();
}
}
public struct Polygon
{
public ushort Index;
@ -153,6 +200,30 @@ public class ModelFile
}
}
public enum VhotId
{
LightPosition = 1,
LightDirection = 8,
Anchor = 2,
Particle1 = 3,
Particle2 = 4,
Particle3 = 5,
Particle4 = 6,
Particle5 = 7,
}
public struct VHot
{
public int Id;
public Vector3 Position;
public VHot(BinaryReader reader)
{
Id = reader.ReadInt32();
Position = reader.ReadVec3();
}
}
public BHeader BinHeader { get; set; }
public MHeader Header { get; set; }
public Vector3[] Vertices { get; }
@ -160,6 +231,8 @@ public class ModelFile
public Vector3[] Normals { get; }
public Polygon[] Polygons { get; }
public Material[] Materials { get; }
public VHot[] VHots { get; }
public SubObject[] Objects { get; }
public ModelFile(string filename)
{
@ -202,5 +275,31 @@ public class ModelFile
{
Materials[i] = new Material(reader);
}
stream.Seek(Header.VHotOffset, SeekOrigin.Begin);
VHots = new VHot[Header.VHotCount];
for (var i = 0; i < VHots.Length; i++)
{
VHots[i] = new VHot(reader);
}
stream.Seek(Header.ObjectOffset, SeekOrigin.Begin);
Objects = new SubObject[Header.ObjectCount];
for (var i = 0; i < Objects.Length; i++)
{
Objects[i] = new SubObject(reader);
}
}
public bool TryGetVhot(VhotId id, out VHot vhot)
{
foreach (var v in VHots)
{
if (v.Id == (int)id)
{
vhot = v;
return true;
}
}
vhot = new VHot();
return false;
}
}

View File

@ -261,16 +261,17 @@ public partial class Mission : Node3D
var pos = brush.position.ToGodotVec3();
var rot = brush.angle.ToGodotVec3(false);
var scale = scaleProp == null ? Vector3.One : scaleProp.value.ToGodotVec3(false);
var model = Timing.TimeStage("Get Models", () =>
{
return Context.Instance.ModelLoader.Load(modelName);
});
if (model != null)
var meshDetails = Timing.TimeStage("Get Models", () => Context.Instance.ModelLoader.Load(modelName));
if (meshDetails.Length != 0)
{
var model = new Node3D();
model.Position = pos;
model.RotationDegrees = rot;
model.Scale = scale;
// TODO: Apply real joints
var meshes = ModelLoader.TransformMeshes([45, 180, 0, 0, 0, 0], meshDetails);
bool GetTextReplPath(PropString prop, out string path)
{
path = "";
@ -299,6 +300,8 @@ public partial class Mission : Node3D
}
var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 };
foreach (var meshInstance in meshes)
{
for (var i = 0; i < 4; i++)
{
if (GetTextReplPath(repls[i], out var path))
@ -309,13 +312,13 @@ public partial class Mission : Node3D
Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass,
};
var surfaceCount = model.Mesh.GetSurfaceCount();
var surfaceCount = meshInstance.Mesh.GetSurfaceCount();
for (var idx = 0; idx < surfaceCount; idx++)
{
var surfaceMat = model.Mesh.SurfaceGetMaterial(idx);
var surfaceMat = meshInstance.Mesh.SurfaceGetMaterial(idx);
if (surfaceMat.HasMeta($"TxtRepl{i}"))
{
model.SetSurfaceOverrideMaterial(idx, overrideMat);
meshInstance.SetSurfaceOverrideMaterial(idx, overrideMat);
}
}
}
@ -323,7 +326,10 @@ public partial class Mission : Node3D
if (renderAlpha != null)
{
model.Transparency = 1.0f - renderAlpha.value;
meshInstance.Transparency = 1.0f - renderAlpha.value;
}
model.AddChild(meshInstance);
}
model.AddToGroup(OBJECT_MODELS_GROUP);

View File

@ -19,7 +19,13 @@ public partial class Model : Node3D
}
Context.Instance.SetCampaign(campaignName);
var model = Context.Instance.ModelLoader.Load(modelPath);
var model = new Node3D();
var meshDetails = Context.Instance.ModelLoader.Load(modelPath);
var meshes = ModelLoader.TransformMeshes([0, 0, 0, 0, 0, 0], meshDetails);
foreach (var meshInstance in meshes)
{
model.AddChild(meshInstance);
}
AddChild(model);
}
}

View File

@ -8,9 +8,16 @@ namespace KeepersCompound.TMV;
// TODO: Work out a way to share base game models again in the cache
public class ModelLoader
{
private readonly Dictionary<(string, string), MeshInstance3D> _cache = new();
public struct MeshDetails(int jointIdx, Transform3D transform, MeshInstance3D mesh)
{
public readonly int JointIdx = jointIdx;
public readonly Transform3D Transform = transform;
public readonly MeshInstance3D Mesh = mesh;
}
public MeshInstance3D Load(string modelName, bool forceLoad = false)
private readonly Dictionary<(string, string), MeshDetails[]> _cache = new();
public MeshDetails[] Load(string modelName, bool forceLoad = false)
{
var campaignResources = Context.Instance.CampaignResources;
var campaignName = campaignResources.name;
@ -18,36 +25,60 @@ public class ModelLoader
if (!forceLoad)
{
if (_cache.TryGetValue((campaignName, modelName), out var fmModel))
if (_cache.TryGetValue((campaignName, modelName), out var fmDetails))
{
return fmModel?.Duplicate() as MeshInstance3D;
return fmDetails;
}
else if (_cache.TryGetValue(("", modelName), out var omModel))
if (_cache.TryGetValue(("", modelName), out var omDetails))
{
return omModel?.Duplicate() as MeshInstance3D;
return omDetails;
}
}
// We don't care if this is null actually, we'll still cache that it's null lol
var model = Timing.TimeStage("Load Models", () => { return LoadModel(modelName); });
_cache[(campaignName, modelName)] = model;
return model?.Duplicate() as MeshInstance3D;
var details = Timing.TimeStage("Load Models", () => LoadModel(modelName));
_cache[(campaignName, modelName)] = details;
return details;
}
public static MeshInstance3D LoadModel(string modelName)
public static MeshInstance3D[] TransformMeshes(float[] joints, MeshDetails[] meshDetails)
{
var meshes = new List<MeshInstance3D>();
foreach (var details in meshDetails)
{
var mesh = details.Mesh.Duplicate() as MeshInstance3D;
if (details.JointIdx != -1)
{
var ang = float.DegreesToRadians(joints[details.JointIdx]);
var r1 = new Quaternion(new Vector3(0, 0, 1), ang);
var r2 = details.Transform.Basis.GetRotationQuaternion();
var basis = new Basis(r2 * r1);
mesh.SetTransform(new Transform3D(basis, details.Transform.Origin));
}
else
{
mesh.SetTransform(details.Transform);
}
meshes.Add(mesh);
}
return [..meshes];
}
public static MeshDetails[] LoadModel(string modelName)
{
var campaignResources = Context.Instance.CampaignResources;
var modelPath = campaignResources.GetResourcePath(ResourceType.Object, modelName);
if (modelPath == null)
{
return null;
return [];
}
var modelFile = new ModelFile(modelPath);
if (modelFile == null)
{
GD.Print($"Failed to load model file: {modelPath}");
return null;
return [];
}
var materials = new List<StandardMaterial3D>();
@ -94,19 +125,32 @@ public class ModelLoader
}
}
var objCount = modelFile.Objects.Length;
var meshDetails = new MeshDetails[objCount];
for (var i = 0; i < objCount; i++)
{
var subObj = modelFile.Objects[i];
var jointIdx = subObj.Joint;
var transform = subObj.Type == 0 ? Transform3D.Identity : subObj.Transform.ToGodotTransform3D();
var surfaceDataMap = new Dictionary<int, MeshSurfaceData>();
foreach (var poly in modelFile.Polygons)
{
var v0 = poly.VertexIndices[0];
if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount)
{
continue;
}
var vertices = new List<Vector3>();
var normal = modelFile.Normals[poly.Normal].ToGodotVec3();
var uvs = new List<Vector2>();
for (var i = 0; i < poly.VertexCount; i++)
for (var j = 0; j < poly.VertexCount; j++)
{
var vertex = modelFile.Vertices[poly.VertexIndices[i]];
var vertex = modelFile.Vertices[poly.VertexIndices[j]];
vertices.Add(vertex.ToGodotVec3());
if (i < poly.UvIndices.Length)
if (j < poly.UvIndices.Length)
{
var uv = modelFile.Uvs[poly.UvIndices[i]];
var uv = modelFile.Uvs[poly.UvIndices[j]];
uvs.Add(new Vector2(uv.X, uv.Y));
}
else
@ -129,12 +173,12 @@ public class ModelLoader
var array = surfaceData.BuildSurfaceArray();
var surfaceIdx = mesh.GetSurfaceCount();
mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array);
for (var i = 0; i < materials.Count; i++)
for (var j = 0; j < materials.Count; j++)
{
var m = modelFile.Materials[i];
var m = modelFile.Materials[j];
if (m.Slot == materialId)
{
mesh.SurfaceSetMaterial(surfaceIdx, materials[i]);
mesh.SurfaceSetMaterial(surfaceIdx, materials[j]);
break;
}
}
@ -142,6 +186,9 @@ public class ModelLoader
var pos = -modelFile.Header.Center.ToGodotVec3();
var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos };
return meshInstance;
meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance);
}
return meshDetails;
}
}

View File

@ -9,4 +9,10 @@ public static class Utils
{
return new Godot.Vector3(vec.Y, vec.Z, vec.X) / (scale ? InverseScale : 1.0f);
}
public static Godot.Transform3D ToGodotTransform3D(this Matrix4x4 mat, bool scale = true)
{
var t = mat.Translation / (scale ? InverseScale : 1.0f);
return new Godot.Transform3D(mat.M22, mat.M21, mat.M23, mat.M31, mat.M33, mat.M32, mat.M12, mat.M13, mat.M11, t.Y, t.Z, t.X);
}
}

View File

@ -10,7 +10,6 @@
[ext_resource type="Texture2D" uid="uid://bfswg75r148mr" path="res://project/assets/icons/ActionCopy.svg" id="7_2pq2g"]
[node name="AssetBrowser" type="Control"]
visible = false
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
@ -22,9 +21,10 @@ size_flags_vertical = 3
script = ExtResource("1_5rr8c")
[node name="TabContainer" type="TabContainer" parent="."]
layout_mode = 2
offset_right = 1152.0
offset_bottom = 648.0
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
current_tab = 0

View File

@ -1,9 +1,8 @@
[gd_scene load_steps=7 format=3 uid="uid://boxi211q3kx6c"]
[gd_scene load_steps=6 format=3 uid="uid://boxi211q3kx6c"]
[ext_resource type="Script" path="res://project/code/TMV/Mission.cs" id="1_3gnqe"]
[ext_resource type="Script" path="res://project/code/camera.gd" id="2_w5otl"]
[ext_resource type="PackedScene" uid="uid://bfxdpxkcgwlkx" path="res://project/scenes/ui/resource_selector.tscn" id="3_kdn7u"]
[ext_resource type="PackedScene" uid="uid://byknmqac1a5vn" path="res://project/scenes/asset_browser/asset_browser.tscn" id="3_noiti"]
[ext_resource type="PackedScene" uid="uid://0h2w7w84vbea" path="res://project/scenes/ui/lightmap_layer_toggler.tscn" id="4_naip8"]
[sub_resource type="Environment" id="Environment_cckyk"]
@ -29,7 +28,5 @@ unique_name_in_owner = true
unique_name_in_owner = true
visible = false
[node name="AssetBrowser" parent="UI" instance=ExtResource("3_noiti")]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_cckyk")