Compare commits
	
		
			6 Commits
		
	
	
		
			440d0b91b4
			...
			7dc1912390
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | 7dc1912390 | |
|  | a35bdb8ce3 | |
|  | d83e020de0 | |
|  | 59582bdfb8 | |
|  | beae881f37 | |
|  | 3ee172f16c | 
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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(), | ||||
|  |  | |||
|  | @ -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; | ||||
|             } | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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,31 +300,36 @@ public partial class Mission : Node3D | |||
| 				} | ||||
| 
 | ||||
| 				var repls = new PropString[] { txtRepl0, txtRepl1, txtRepl2, txtRepl3 }; | ||||
| 				for (var i = 0; i < 4; i++) | ||||
| 				foreach (var meshInstance in meshes) | ||||
| 				{ | ||||
| 					if (GetTextReplPath(repls[i], out var path)) | ||||
| 					for (var i = 0; i < 4; i++) | ||||
| 					{ | ||||
| 						var overrideMat = new StandardMaterial3D | ||||
| 						if (GetTextReplPath(repls[i], out var path)) | ||||
| 						{ | ||||
| 							AlbedoTexture = TextureLoader.LoadTexture(path), | ||||
| 							Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass, | ||||
| 						}; | ||||
| 
 | ||||
| 						var surfaceCount = model.Mesh.GetSurfaceCount(); | ||||
| 						for (var idx = 0; idx < surfaceCount; idx++) | ||||
| 						{ | ||||
| 							var surfaceMat = model.Mesh.SurfaceGetMaterial(idx); | ||||
| 							if (surfaceMat.HasMeta($"TxtRepl{i}")) | ||||
| 							var overrideMat = new StandardMaterial3D | ||||
| 							{ | ||||
| 								model.SetSurfaceOverrideMaterial(idx, overrideMat); | ||||
| 								AlbedoTexture = TextureLoader.LoadTexture(path), | ||||
| 								Transparency = BaseMaterial3D.TransparencyEnum.AlphaDepthPrePass, | ||||
| 							}; | ||||
| 
 | ||||
| 							var surfaceCount = meshInstance.Mesh.GetSurfaceCount(); | ||||
| 							for (var idx = 0; idx < surfaceCount; idx++) | ||||
| 							{ | ||||
| 								var surfaceMat = meshInstance.Mesh.SurfaceGetMaterial(idx); | ||||
| 								if (surfaceMat.HasMeta($"TxtRepl{i}")) | ||||
| 								{ | ||||
| 									meshInstance.SetSurfaceOverrideMaterial(idx, overrideMat); | ||||
| 								} | ||||
| 							} | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				if (renderAlpha != null) | ||||
| 				{ | ||||
| 					model.Transparency = 1.0f - renderAlpha.value; | ||||
| 					if (renderAlpha != null) | ||||
| 					{ | ||||
| 						meshInstance.Transparency = 1.0f - renderAlpha.value; | ||||
| 					} | ||||
| 					 | ||||
| 					model.AddChild(meshInstance); | ||||
| 				} | ||||
| 				 | ||||
| 				model.AddToGroup(OBJECT_MODELS_GROUP); | ||||
|  |  | |||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -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,54 +125,70 @@ public class ModelLoader | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var surfaceDataMap = new Dictionary<int, MeshSurfaceData>(); | ||||
|         foreach (var poly in modelFile.Polygons) | ||||
|         var objCount = modelFile.Objects.Length; | ||||
|         var meshDetails = new MeshDetails[objCount]; | ||||
|         for (var i = 0; i < objCount; i++) | ||||
|         { | ||||
|             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++) | ||||
|             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 vertex = modelFile.Vertices[poly.VertexIndices[i]]; | ||||
|                 vertices.Add(vertex.ToGodotVec3()); | ||||
|                 if (i < poly.UvIndices.Length) | ||||
|                 var v0 = poly.VertexIndices[0]; | ||||
|                 if (v0 < subObj.PointIdx || v0 >= subObj.PointIdx + subObj.PointCount) | ||||
|                 { | ||||
|                     var uv = modelFile.Uvs[poly.UvIndices[i]]; | ||||
|                     uvs.Add(new Vector2(uv.X, uv.Y)); | ||||
|                     continue; | ||||
|                 } | ||||
|                 else | ||||
|                  | ||||
|                 var vertices = new List<Vector3>(); | ||||
|                 var normal = modelFile.Normals[poly.Normal].ToGodotVec3(); | ||||
|                 var uvs = new List<Vector2>(); | ||||
|                 for (var j = 0; j < poly.VertexCount; j++) | ||||
|                 { | ||||
|                     uvs.Add(Vector2.Zero); | ||||
|                     var vertex = modelFile.Vertices[poly.VertexIndices[j]]; | ||||
|                     vertices.Add(vertex.ToGodotVec3()); | ||||
|                     if (j < poly.UvIndices.Length) | ||||
|                     { | ||||
|                         var uv = modelFile.Uvs[poly.UvIndices[j]]; | ||||
|                         uvs.Add(new Vector2(uv.X, uv.Y)); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         uvs.Add(Vector2.Zero); | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (!surfaceDataMap.ContainsKey(poly.Data)) | ||||
|                 { | ||||
|                     surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); | ||||
|                 } | ||||
| 
 | ||||
|                 surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs); | ||||
|             } | ||||
| 
 | ||||
|             var mesh = new ArrayMesh(); | ||||
|             foreach (var (materialId, surfaceData) in surfaceDataMap) | ||||
|             { | ||||
|                 var array = surfaceData.BuildSurfaceArray(); | ||||
|                 var surfaceIdx = mesh.GetSurfaceCount(); | ||||
|                 mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); | ||||
|                 for (var j = 0; j < materials.Count; j++) | ||||
|                 { | ||||
|                     var m = modelFile.Materials[j]; | ||||
|                     if (m.Slot == materialId) | ||||
|                     { | ||||
|                         mesh.SurfaceSetMaterial(surfaceIdx, materials[j]); | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (!surfaceDataMap.ContainsKey(poly.Data)) | ||||
|             { | ||||
|                 surfaceDataMap.Add(poly.Data, new MeshSurfaceData()); | ||||
|             } | ||||
| 
 | ||||
|             surfaceDataMap[poly.Data].AddPolygon(vertices, normal, uvs, uvs); | ||||
|             var pos = -modelFile.Header.Center.ToGodotVec3(); | ||||
|             var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos }; | ||||
|             meshDetails[i] = new MeshDetails(jointIdx, transform, meshInstance); | ||||
|         } | ||||
| 
 | ||||
|         var mesh = new ArrayMesh(); | ||||
|         foreach (var (materialId, surfaceData) in surfaceDataMap) | ||||
|         { | ||||
|             var array = surfaceData.BuildSurfaceArray(); | ||||
|             var surfaceIdx = mesh.GetSurfaceCount(); | ||||
|             mesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, array); | ||||
|             for (var i = 0; i < materials.Count; i++) | ||||
|             { | ||||
|                 var m = modelFile.Materials[i]; | ||||
|                 if (m.Slot == materialId) | ||||
|                 { | ||||
|                     mesh.SurfaceSetMaterial(surfaceIdx, materials[i]); | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         var pos = -modelFile.Header.Center.ToGodotVec3(); | ||||
|         var meshInstance = new MeshInstance3D { Mesh = mesh, Position = pos }; | ||||
|         return meshInstance; | ||||
|         return meshDetails; | ||||
|     } | ||||
| } | ||||
|  | @ -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); | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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") | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue