diff --git a/BIS.Core/Math/Matrix4P.cs b/BIS.Core/Math/Matrix4P.cs index 9d579c3..a392713 100644 --- a/BIS.Core/Math/Matrix4P.cs +++ b/BIS.Core/Math/Matrix4P.cs @@ -53,6 +53,24 @@ public void Write(BinaryWriterEx output) output.Write(matrix.M43); } + public System.Numerics.Matrix4x4 Matrix + { + get { return matrix; } + set { matrix = value; } + } + + public float Altitude + { + get { return matrix.M42; } + set { matrix.M42 = value; } + } + + public float AltitudeScale + { + get { return matrix.M22; } + set { matrix.M22 = value; } + } + public override string ToString() { return matrix.ToString(); diff --git a/BIS.Core/Stream/BinaryReaderEx.cs b/BIS.Core/Stream/BinaryReaderEx.cs index e8a66a0..cd19129 100644 --- a/BIS.Core/Stream/BinaryReaderEx.cs +++ b/BIS.Core/Stream/BinaryReaderEx.cs @@ -3,6 +3,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Numerics; using System.Text; using BIS.Core.Compression; @@ -253,5 +254,23 @@ public T[] ReadCompressed(Func readElement, int nElements, var stream = new BinaryReaderEx(new MemoryStream(ReadCompressed(expectedDataSize))); return stream.ReadArrayBase(readElement, nElements); } + + public Vector3 ReadVector3() + { + return new Vector3(ReadSingle(), ReadSingle(), ReadSingle()); + } + + public Vector3 ReadVector3Compressed() + { + var value = ReadUInt32(); + double scaleFactor = -1.0 / 511; + uint x = value & 0x3FF; + uint y = (value >> 10) & 0x3FF; + uint z = (value >> 20) & 0x3FF; + if (x > 511) { x -= 1024; } + if (y > 511) { y -= 1024; } + if (z > 511) { z -= 1024; } + return new Vector3((float)(x * scaleFactor), (float)(y * scaleFactor), (float)(z * scaleFactor)); + } } } diff --git a/BIS.Core/Stream/BinaryWriterEx.cs b/BIS.Core/Stream/BinaryWriterEx.cs index 77988c4..b722069 100644 --- a/BIS.Core/Stream/BinaryWriterEx.cs +++ b/BIS.Core/Stream/BinaryWriterEx.cs @@ -2,6 +2,7 @@ using System.IO; using System.IO.Compression; using System.Linq; +using System.Numerics; using System.Text; namespace BIS.Core.Streams @@ -53,7 +54,12 @@ public void WriteArray(T[] array, Action write) WriteArrayBase(array, write); } - private void WriteArrayBase(T[] array, Action write) + public void WriteArray(float[] array) + { + WriteArray(array, (w, f) => w.Write(f)); + } + + public void WriteArrayBase(T[] array, Action write) { foreach (var item in array) { @@ -61,6 +67,16 @@ private void WriteArrayBase(T[] array, Action write) } } + public void WriteArrayBase(float[] array) + { + WriteArrayBase(array, (w, f) => w.Write(f)); + } + + public void WriteArrayBase(int[] array) + { + WriteArrayBase(array, (w, f) => w.Write(f)); + } + public void WriteCompressedFloatArray(float[] array) { WriteCompressedArray(array, (w, v) => w.Write(v), 4); @@ -152,5 +168,11 @@ public void WriteUshorts(ushort[] elements) { WriteArrayBase(elements, (r,e) => r.Write(e)); } + public void Write(Vector3 value) + { + Write(value.X); + Write(value.Y); + Write(value.Z); + } } } diff --git a/BIS.P3D.Test/BIS.P3D.Test.csproj b/BIS.P3D.Test/BIS.P3D.Test.csproj new file mode 100644 index 0000000..52a0760 --- /dev/null +++ b/BIS.P3D.Test/BIS.P3D.Test.csproj @@ -0,0 +1,26 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/BIS.P3D.Test/ODOL/ODOLTest.cs b/BIS.P3D.Test/ODOL/ODOLTest.cs new file mode 100644 index 0000000..2d897a9 --- /dev/null +++ b/BIS.P3D.Test/ODOL/ODOLTest.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BIS.Core.Streams; +using Xunit; + +namespace BIS.P3D.Test.ODOL +{ + public class ODOLTest + { + [Fact] + public void Test() + { + + } + + } +} diff --git a/BIS.P3D/IModelInfo.cs b/BIS.P3D/IModelInfo.cs new file mode 100644 index 0000000..deb38c0 --- /dev/null +++ b/BIS.P3D/IModelInfo.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BIS.Core.Math; + +namespace BIS.P3D +{ + public interface IModelInfo + { + /// + /// Minimum coordinates of bounding box + /// + Vector3P BboxMin { get; } + + /// + /// Maximum coordinates of bounding box + /// + Vector3P BboxMax { get; } + } +} diff --git a/BIS.P3D/MLOD/ComputedModelInfo.cs b/BIS.P3D/MLOD/ComputedModelInfo.cs new file mode 100644 index 0000000..df4c8d5 --- /dev/null +++ b/BIS.P3D/MLOD/ComputedModelInfo.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using BIS.Core.Math; + +namespace BIS.P3D.MLOD +{ + class ComputedModelInfo : IModelInfo + { + internal ComputedModelInfo(MLOD mLOD) + { + var points = mLOD.Lods.SelectMany(l => l.Points); + + BboxMin = new Vector3P( + points.Min(p => p.X), + points.Min(p => p.Y), + points.Min(p => p.Z)); + + BboxMax = new Vector3P( + points.Max(p => p.X), + points.Max(p => p.Y), + points.Max(p => p.Z)); + } + + public Vector3P BboxMin { get; } + + public Vector3P BboxMax { get; } + } +} diff --git a/BIS.P3D/MLOD/MLOD.cs b/BIS.P3D/MLOD/MLOD.cs index 20b6b0a..7f1e8fb 100644 --- a/BIS.P3D/MLOD/MLOD.cs +++ b/BIS.P3D/MLOD/MLOD.cs @@ -1,15 +1,21 @@ using BIS.Core.Streams; +using BIS.P3D.ODOL; using System; using System.IO; namespace BIS.P3D.MLOD { - public class MLOD + public class MLOD : IReadObject { public int Version { get; private set; } + public P3DM_LOD[] Lods { get; private set; } - public MLOD(string fileName) : this(File.OpenRead(fileName)) {} + public IModelInfo ModelInfo => new ComputedModelInfo(this); + + public MLOD(string fileName) : this(File.OpenRead(fileName)) + { + } public MLOD(Stream stream) { @@ -22,11 +28,20 @@ public MLOD(P3DM_LOD[] lods) Lods = lods; } - private void Read(BinaryReaderEx input) + internal MLOD() + { + } + + public void Read(BinaryReaderEx input) { if (input.ReadAscii(4) != "MLOD") throw new FormatException("MLOD signature expected"); + ReadContent(input); + } + + internal void ReadContent(BinaryReaderEx input) + { Version = input.ReadInt32(); if (Version != 257) throw new ArgumentException("Unknown MLOD version"); diff --git a/BIS.P3D/ODOL/ModelInfo.cs b/BIS.P3D/ODOL/ModelInfo.cs new file mode 100644 index 0000000..8e90ef4 --- /dev/null +++ b/BIS.P3D/ODOL/ModelInfo.cs @@ -0,0 +1,192 @@ +using BIS.Core; +using BIS.Core.Math; +using BIS.Core.Streams; + +namespace BIS.P3D.ODOL +{ + public class ModelInfo : IModelInfo + { + internal ModelInfo(BinaryReaderEx input, int version, int noOfLods) + { + Special = input.ReadInt32(); + BoundingSphere = input.ReadSingle(); + GeometrySphere = input.ReadSingle(); + Remarks = input.ReadInt32(); + AndHints = input.ReadInt32(); + OrHints = input.ReadInt32(); + AimingCenter = new Vector3P(input); + Color = new PackedColor(input.ReadUInt32()); + ColorType = new PackedColor(input.ReadUInt32()); + ViewDensity = input.ReadSingle(); + BboxMin = new Vector3P(input); + BboxMax = new Vector3P(input); + if (version >= 70) + { + LodDensityCoef = input.ReadSingle(); + } + if (version >= 71) + { + DrawImportance = input.ReadSingle(); + } + if (version >= 52) + { + BboxMinVisual = new Vector3P(input); + BboxMaxVisual = new Vector3P(input); + } + BoundingCenter = new Vector3P(input); + GeometryCenter = new Vector3P(input); + CenterOfMass = new Vector3P(input); + InvInertia = input.ReadArrayBase(i => new Vector3P(input), 3); + AutoCenter = input.ReadBoolean(); + LockAutoCenter = input.ReadBoolean(); + CanOcclude = input.ReadBoolean(); + CanBeOccluded = input.ReadBoolean(); + if (version >= 73) + { + AICovers = input.ReadBoolean(); + } + if ((version >= 42 && version < 10000) || version >= 10042) + { + HtMin = input.ReadSingle(); + HtMax = input.ReadSingle(); + AfMax = input.ReadSingle(); + MfMax = input.ReadSingle(); + } + if ((version >= 43 && version < 10000) || version >= 10043) + { + MFact = input.ReadSingle(); + TBody = input.ReadSingle(); + } + if (version >= 33) + { + ForceNotAlphaModel = input.ReadBoolean(); + } + if (version >= 37) + { + SbSource = input.ReadInt32(); + Prefershadowvolume = input.ReadBoolean(); + } + if (version >= 48) + { + ShadowOffset = input.ReadSingle(); + } + Animated = input.ReadBoolean(); + Skeleton = new Skeleton(input, version, noOfLods); + MapType = input.ReadByte(); + MassArray = input.ReadCompressedFloatArray(); + Mass = input.ReadSingle(); + InvMass = input.ReadSingle(); + Armor = input.ReadSingle(); + InvArmor = input.ReadSingle(); + if (version >= 72) + { + ExplosionShielding = input.ReadSingle(); + } + if (version >= 53) + { + GeometrySimple = input.ReadByte(); + } + if (version >= 54) + { + GeometryPhys = input.ReadByte(); + } + Memory = input.ReadByte(); + Geometry = input.ReadByte(); + GeometryFire = input.ReadByte(); + GeometryView = input.ReadByte(); + GeometryViewPilot = input.ReadByte(); + GeometryViewGunner = input.ReadByte(); + UnknownByte = input.ReadSByte(); + GeometryViewCargo = input.ReadByte(); + LandContact = input.ReadByte(); + Roadway = input.ReadByte(); + Paths = input.ReadByte(); + Hitpoints = input.ReadByte(); + MinShadow = (byte)input.ReadUInt32(); + if (version >= 38) + { + CanBlend = input.ReadBoolean(); + } + Class = input.ReadAsciiz(); + Damage = input.ReadAsciiz(); + Frequent = input.ReadBoolean(); + if (version >= 31) + { + input.ReadUInt32(); + } + if (version >= 57) + { + PreferredShadowVolumeLod = input.ReadArrayBase(i => i.ReadInt32(), noOfLods); + PreferredShadowBufferLod = input.ReadArrayBase(i => i.ReadInt32(), noOfLods); + PreferredShadowBufferLodVis = input.ReadArrayBase(i => i.ReadInt32(), noOfLods); + } + } + + public int Special { get; } + public float BoundingSphere { get; } + public float GeometrySphere { get; } + public int Remarks { get; } + public int AndHints { get; } + public int OrHints { get; } + public Vector3P AimingCenter { get; } + public PackedColor Color { get; } + public PackedColor ColorType { get; } + public float ViewDensity { get; } + public Vector3P BboxMin { get; } + public Vector3P BboxMax { get; } + public float LodDensityCoef { get; } + public float DrawImportance { get; } + public Vector3P BboxMinVisual { get; } + public Vector3P BboxMaxVisual { get; } + public Vector3P BoundingCenter { get; } + public Vector3P GeometryCenter { get; } + public Vector3P CenterOfMass { get; } + public Vector3P[] InvInertia { get; } + public bool AutoCenter { get; } + public bool LockAutoCenter { get; } + public bool CanOcclude { get; } + public bool CanBeOccluded { get; } + public bool AICovers { get; } + public float HtMin { get; } + public float HtMax { get; } + public float AfMax { get; } + public float MfMax { get; } + public float MFact { get; } + public float TBody { get; } + public bool ForceNotAlphaModel { get; } + public int SbSource { get; } + public bool Prefershadowvolume { get; } + public float ShadowOffset { get; } + public bool Animated { get; } + public Skeleton Skeleton { get; } + public byte MapType { get; } + public float[] MassArray { get; } + public float Mass { get; } + public float InvMass { get; } + public float Armor { get; } + public float InvArmor { get; } + public float ExplosionShielding { get; } + public byte GeometrySimple { get; } + public byte GeometryPhys { get; } + public byte Memory { get; } + public byte Geometry { get; } + public byte GeometryFire { get; } + public byte GeometryView { get; } + public byte GeometryViewPilot { get; } + public byte GeometryViewGunner { get; } + public sbyte UnknownByte { get; } + public byte GeometryViewCargo { get; } + public byte LandContact { get; } + public byte Roadway { get; } + public byte Paths { get; } + public byte Hitpoints { get; } + public byte MinShadow { get; } + public bool CanBlend { get; } + public string Class { get; } + public string Damage { get; } + public bool Frequent { get; } + public int[] PreferredShadowVolumeLod { get; } + public int[] PreferredShadowBufferLod { get; } + public int[] PreferredShadowBufferLodVis { get; } + } +} \ No newline at end of file diff --git a/BIS.P3D/ODOL/ODOL.cs b/BIS.P3D/ODOL/ODOL.cs new file mode 100644 index 0000000..1dd2509 --- /dev/null +++ b/BIS.P3D/ODOL/ODOL.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BIS.Core.Streams; + +namespace BIS.P3D.ODOL +{ + public class ODOL : IReadObject + { + public int Version { get; private set; } + public string Prefix { get; private set; } + public int NoOfLods { get; private set; } + public ModelInfo ModelInfo { get; private set; } + public float[] Resolutions { get; private set; } + public uint AppID { get; private set; } + public string MuzzleFlash { get; private set; } + public void Read(BinaryReaderEx input) + { + if (input.ReadAscii(4) != "ODOL") + throw new FormatException("ODOL signature expected"); + + ReadContent(input); + } + + internal void ReadContent(BinaryReaderEx input) + { + Version = input.ReadInt32(); + + if (Version >= 44) + { + input.UseLZOCompression = true; + } + if (Version >= 64) + { + input.UseCompressionFlag = true; + } + if (Version >= 59) + { + AppID = input.ReadUInt32(); + } + if (Version >= 58) + { + MuzzleFlash = input.ReadAsciiz(); + } + + Resolutions = input.ReadFloatArray(); + + NoOfLods = Resolutions.Length; + + ModelInfo = new ModelInfo(input, Version, NoOfLods); + } + } +} diff --git a/BIS.P3D/ODOL/Skeleton.cs b/BIS.P3D/ODOL/Skeleton.cs new file mode 100644 index 0000000..7aa1bac --- /dev/null +++ b/BIS.P3D/ODOL/Skeleton.cs @@ -0,0 +1,35 @@ +using BIS.Core.Streams; + +namespace BIS.P3D.ODOL +{ + public class Skeleton + { + + public Skeleton(BinaryReaderEx input, int version, int noOfLods) + { + SkeletonName = input.ReadAsciiz(); + if (!string.IsNullOrEmpty(SkeletonName)) + { + if (version >= 23) + { + IsDiscrete = input.ReadBoolean(); + } + SkeletonBoneNames = input.ReadArray(i => new SkeletonBoneName(i)); + if (version > 40) + { + PivotsNameObsolete = input.ReadAsciiz(); + } + } + } + + public string SkeletonName { get; } + public bool IsDiscrete { get; } + public SkeletonBoneName[] SkeletonBoneNames { get; } + public string PivotsNameObsolete { get; } + + internal void Write(BinaryWriterEx output, int version) + { + + } + } +} \ No newline at end of file diff --git a/BIS.P3D/ODOL/SkeletonBoneName.cs b/BIS.P3D/ODOL/SkeletonBoneName.cs new file mode 100644 index 0000000..24659ac --- /dev/null +++ b/BIS.P3D/ODOL/SkeletonBoneName.cs @@ -0,0 +1,22 @@ +using BIS.Core.Streams; + +namespace BIS.P3D.ODOL +{ + public class SkeletonBoneName + { + public SkeletonBoneName(BinaryReaderEx input) + { + BoneName = input.ReadAsciiz(); + ParentBoneName = input.ReadAsciiz(); + } + + public string BoneName { get; } + public string ParentBoneName { get; } + + internal void Write(BinaryWriterEx output) + { + output.WriteAsciiz(BoneName); + output.WriteAsciiz(ParentBoneName); + } + } +} \ No newline at end of file diff --git a/BIS.P3D/P3D.cs b/BIS.P3D/P3D.cs index 2564527..c9cf031 100644 --- a/BIS.P3D/P3D.cs +++ b/BIS.P3D/P3D.cs @@ -5,58 +5,13 @@ using System.Linq; namespace BIS.P3D -{ - //public abstract class P3D_LOD - //{ - // public float Resolution { get; protected set; } - - // public string Name - // { - // get { return Resolution.GetLODName(); } - // } - - // public abstract Vector3P[] Points - // { - // get; - // } - - // public abstract Vector3P[] NormalVectors - // { - // get; - // } - - // public abstract string[] Textures - // { - // get; - // } - - // public abstract string[] MaterialNames - // { - // get; - // } - //} - - public static class P3D +{ + public class P3D : IReadObject { - //public int Version { get; protected set; } - - //public static P3D GetInstance(string fileName) - //{ - // return GetInstance(File.OpenRead(fileName)); - //} + private MLOD.MLOD editable; + private ODOL.ODOL binarized; - //public static P3D GetInstance(Stream stream) - //{ - // var binaryReader = new BinaryReaderEx(stream); - // var sig = binaryReader.ReadAscii(4); - // stream.Position -= 4; - // if (sig == "ODOL") - // return new ODOL.ODOL(stream); - // if (sig == "MLOD") - // return new MLOD.MLOD(stream); - // else - // throw new FormatException("Neither MLOD nor ODOL signature detected"); - //} + public IModelInfo ModelInfo => binarized?.ModelInfo ?? editable.ModelInfo; public static bool IsODOL(string filePath) { @@ -95,16 +50,26 @@ public static bool IsMLOD(Stream stream) return result; } - //public abstract P3D_LOD[] LODs { get; } + public void Read(BinaryReaderEx input) + { + var signature = input.ReadAscii(4); + switch (signature) + { + case "ODOL": + binarized = new ODOL.ODOL(); + binarized.ReadContent(input); + editable = null; + break; + case "MLOD": + editable = new MLOD.MLOD(); + editable.ReadContent(input); + binarized = null; + break; + default: + throw new InvalidOperationException($"Unknown P3D format '{signature}'"); + } + } - //public virtual P3D_LOD GetLOD(float resolution) - //{ - // return LODs.FirstOrDefault(lod => lod.Resolution == resolution); - //} - //public abstract float Mass - //{ - // get; - //} } } diff --git a/BIS.WRP/AnyWrp.cs b/BIS.WRP/AnyWrp.cs index 05bc81f..b14606a 100644 --- a/BIS.WRP/AnyWrp.cs +++ b/BIS.WRP/AnyWrp.cs @@ -29,6 +29,8 @@ public class AnyWrp : IReadObject, IWrp public IReadOnlyList MaterialIndex => wrp.MaterialIndex; + public int ObjectsCount => wrp.ObjectsCount; + public void Read(BinaryReaderEx input) { var signature = input.ReadAscii(4); diff --git a/BIS.WRP/EditableWrp.cs b/BIS.WRP/EditableWrp.cs index 169cbf2..5bad3a1 100644 --- a/BIS.WRP/EditableWrp.cs +++ b/BIS.WRP/EditableWrp.cs @@ -100,8 +100,8 @@ public void Write(BinaryWriterEx output) public ushort[] MaterialIndex { get; set; } public string[] MatNames { get; set; } public List Objects { get; set; } = new List(); - IReadOnlyList IWrp.MaterialIndex => MaterialIndex; + public int ObjectsCount => Objects.Count; public IEnumerable GetNonDummyObjects() => Objects.TakeWhile(o => !string.IsNullOrEmpty(o.Model)); } diff --git a/BIS.WRP/IWrp.cs b/BIS.WRP/IWrp.cs index 38966e0..e792f75 100644 --- a/BIS.WRP/IWrp.cs +++ b/BIS.WRP/IWrp.cs @@ -12,5 +12,6 @@ internal interface IWrp float[] Elevation { get; } string[] MatNames { get; } IReadOnlyList MaterialIndex { get; } + int ObjectsCount { get; } } } \ No newline at end of file diff --git a/BIS.WRP/OPRW.cs b/BIS.WRP/OPRW.cs index 89feeda..ee2c517 100644 --- a/BIS.WRP/OPRW.cs +++ b/BIS.WRP/OPRW.cs @@ -38,8 +38,9 @@ public class OPRW : IReadObject, IWrp public RoadLink[][] Roadnet { get; private set; } public Object[] Objects { get; private set; } public byte[] MapInfos { get; private set; } - IReadOnlyList IWrp.MaterialIndex => Materials; + public int ObjectsCount => Objects.Length; + public OPRW() { diff --git a/Utils/WrpUtil/Program.cs b/Utils/WrpUtil/Program.cs index 87e006c..b324860 100644 --- a/Utils/WrpUtil/Program.cs +++ b/Utils/WrpUtil/Program.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; +using System.Numerics; using System.Text.RegularExpressions; +using BIS.Core.Config; using BIS.Core.Streams; +using BIS.P3D; using BIS.PBO; using BIS.WRP; using CommandLine; @@ -59,17 +63,228 @@ class DependenciesOptions public string ModsBasePath { get; set; } } + [Verb("stats", HelpText = "Stats from a WRP file.")] + class StatsOptions + { + [Value(0, MetaName = "source", HelpText = "WRP file.", Required = true)] + public string Source { get; set; } + + [Value(1, MetaName = "target", HelpText = "If specified, target CSV file with object count per model.", Required = false)] + public string Target { get; set; } + } + + [Verb("edit", HelpText = "Mass edit a WRP file.")] + class MassEditOptions + { + [Value(0, MetaName = "source", HelpText = "Source WRP file.", Required = true)] + public string Source { get; set; } + + [Value(2, MetaName = "definition", HelpText = "CSV file with edit to proceed.", Required = true)] + public string Definition { get; set; } + + [Value(3, MetaName = "target", HelpText = "Target WRP file.", Required = true)] + public string Target { get; set; } + + [Option('z', "fix-alt", Required = false, HelpText = "Automaticly fix Z position if models heights are differents.")] + public bool FixAltitude { get; set; } + } + public static int Main(string[] args) { - return CommandLine.Parser.Default.ParseArguments(args) + return CommandLine.Parser.Default.ParseArguments(args) .MapResult( (ConvertOptions opts) => Convert(opts), (MergeOptions opts) => Merge(opts), (StripOptions opts) => Strip(opts), (DependenciesOptions opts) => Dependencies(opts), + (StatsOptions opts) => Stats(opts), + (MassEditOptions opts) => MassEdit(opts), errs => 1); } + private static int MassEdit(MassEditOptions opts) + { + Console.WriteLine($"Read '{opts.Source}'"); + var source = StreamHelper.Read(opts.Source); + var editable = source.GetEditableWrp(); + var editedMetarial = new Dictionary(); + + Console.WriteLine($"Process '{opts.Definition}'"); + foreach (var line in File.ReadAllLines(opts.Definition)) + { + var items = line.Split(';'); + switch(items[0].ToUpperInvariant()) + { + case "REPLACE": + Replace(editable, items[1], items[2], items.Length > 3 ? items[3] : null, opts.FixAltitude); + break; + case "REDUCE": + Reduce(editable, items[1], double.Parse(items[2], CultureInfo.InvariantCulture)); + break; + case "RE-MATERIAL": + ReMaterial(editable, editedMetarial, items[1], items[2]); + break; + case "REPLACE-SCALED": + ReplaceScaled(editable, items[1], items[2]); + break; + } + } + + editable.Objects = editable.Objects.Where(o => o != null).ToList(); + + if (editedMetarial.Count > 0) + { + var target = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(opts.Target), "data", "layers")); + Directory.CreateDirectory(target); + Console.WriteLine($"Generate new materials in {target}"); + foreach (var pair in editedMetarial) + { + File.WriteAllText(Path.Combine(target, Path.GetFileName(pair.Key)), pair.Value); + } + for(int i = 0; i < editable.MatNames.Length; ++i) + { + if (editable.MatNames[i] != null) + { + editable.MatNames[i] = Path.Combine(target, Path.GetFileName(editable.MatNames[i])).Replace("P:\\", "", StringComparison.OrdinalIgnoreCase); + } + } + } + + Console.WriteLine($"Write '{opts.Target}'"); + editable.Write(opts.Target); + + Console.WriteLine("Done"); + return 0; + } + + private static void ReplaceScaled(EditableWrp editable, string initial, string replacement) + { + var oldModel = StreamHelper.Read(Path.Combine("P:", initial)).ModelInfo; + var newModel = StreamHelper.Read(Path.Combine("P:", replacement)).ModelInfo; + + var xScale = oldModel.BboxMin.X / newModel.BboxMin.X; + var yScale = oldModel.BboxMin.Y / newModel.BboxMin.Y; + var zScale = oldModel.BboxMin.Z / newModel.BboxMin.Z; + + var changes = 0; + foreach (var obj in editable.Objects) + { + if (string.Equals(obj.Model, initial, StringComparison.OrdinalIgnoreCase)) + { + obj.Model = replacement; + var matrix = obj.Transform.Matrix; + matrix.M11 *= xScale; + matrix.M22 *= yScale; + matrix.M33 *= zScale; + obj.Transform.Matrix = matrix; + changes++; + } + } + Console.WriteLine($" -> {changes} changes"); + Console.WriteLine(); + } + + private static void ReMaterial(EditableWrp editable, Dictionary editedMetarial, string initial, string replacement) + { + if (editedMetarial.Count == 0) + { + foreach (var mat in editable.MatNames.Where(m => m != null)) + { + editedMetarial[mat] = ReadMaterial(mat); + } + } + foreach(var mat in editable.MatNames) + { + if (mat != null && editedMetarial.ContainsKey(mat)) + { + editedMetarial[mat] = editedMetarial[mat].Replace(initial, replacement, StringComparison.OrdinalIgnoreCase); + } + } + } + + private static string ReadMaterial(string m) + { + var physical = Path.Combine("P:", m); + try + { + return StreamHelper.Read(physical).ToString(); + } + catch(ArgumentException) // if ParamFile is not binarized/rapped + { + return File.ReadAllText(physical); + } + } + + private static void Reduce(EditableWrp editable, string model, double removeRatio) + { + var rnd = new Random(model.GetHashCode()); + for (int i = 0; i < editable.Objects.Count; ++i) + { + var obj = editable.Objects[i]; + if (obj != null && + string.Equals(model, model, StringComparison.OrdinalIgnoreCase) && + (removeRatio == 1 || rnd.NextDouble() <= removeRatio)) + { + editable.Objects[i] = null; + } + } + } + + private static void Replace(EditableWrp editable, string initial, string replacement, string altShiftString, bool autoFixAltitude) + { + float altShift = 0; + if ((string.IsNullOrEmpty(altShiftString) || !float.TryParse(altShiftString, NumberStyles.Any, CultureInfo.InvariantCulture, out altShift)) && autoFixAltitude) + { + var oldModel = StreamHelper.Read(Path.Combine("P:", initial)).ModelInfo; + var newModel = StreamHelper.Read(Path.Combine("P:", replacement)).ModelInfo; + altShift = oldModel.BboxMin.Y - newModel.BboxMin.Y; + Console.WriteLine($" '{initial}'->'{replacement}' altShift={altShift:0.00} (computed)"); + } + else + { + Console.WriteLine($" '{initial}'->'{replacement}' altShift={altShift:0.00}"); + } + var changes = 0; + foreach (var obj in editable.Objects) + { + if (string.Equals(obj.Model, initial, StringComparison.OrdinalIgnoreCase)) + { + obj.Model = replacement; + if (altShift != 0) + { + if (obj.Transform.AltitudeScale != 1f) + { + obj.Transform.Altitude += altShift * obj.Transform.AltitudeScale; + } + else + { + obj.Transform.Altitude += altShift; + } + } + changes++; + } + } + Console.WriteLine($" -> {changes} changes"); + Console.WriteLine(); + } + + private static int Stats(StatsOptions opts) + { + Console.WriteLine($"WRP '{opts.Source}'"); + var source = StreamHelper.Read(opts.Source); + Console.WriteLine($"CellSize = {source.CellSize}"); + Console.WriteLine($"LandRange = {source.LandRangeX}x{source.LandRangeY}"); + Console.WriteLine($"TerrainRange = {source.TerrainRangeX}x{source.TerrainRangeY}"); + Console.WriteLine($"ObjectsCount = {source.ObjectsCount}"); + Console.WriteLine($"MaterialNames = {source.MatNames.Length}"); + Console.WriteLine($"MaterialIndex = {source.MaterialIndex.Count}"); + if (!string.IsNullOrEmpty(opts.Target)) + { + var data = source.GetEditableWrp().GetNonDummyObjects().GroupBy(o => o.Model).Select(g => new { Object = g.Key, Count = g.Count() }).ToList(); + File.WriteAllLines(opts.Target, data.OrderByDescending(d => d.Count).Select(d => $"{d.Object};{d.Count}")); + } + return 0; + } private static int Convert(ConvertOptions opts) { diff --git a/Utils/WrpUtil/WrpUtil.csproj b/Utils/WrpUtil/WrpUtil.csproj index bc5f555..77fd706 100644 --- a/Utils/WrpUtil/WrpUtil.csproj +++ b/Utils/WrpUtil/WrpUtil.csproj @@ -18,6 +18,7 @@ Forbidden by APL-ND, Bohemia Interactive EULA. + diff --git a/Utils/WrpUtil/winter.csv b/Utils/WrpUtil/winter.csv new file mode 100644 index 0000000..4909762 --- /dev/null +++ b/Utils/WrpUtil/winter.csv @@ -0,0 +1,70 @@ +;Sample of 'wrputil edit' +RE-MATERIAL;xCam\xcam_taunus\Data\xcam_;z\xcam_taunus_winter\data\xcam_winter_ +RE-MATERIAL;xcam\xcam_taunus\data\layers\s_;z\xcam_taunus_winter\data\layers\s_ +REPLACE;ca\plants_e2\bush\b_canina2s_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_canina2sW.p3d; +REPLACE;ca\plants2\bush\b_canina2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_canina2sW.p3d;0 +REPLACE;ca\plants_e2\bush\b_corylus2s_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_corylus2sW.p3d; +REPLACE;ca\plants_e2\bush\b_craet1_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_craet1W.p3d;0 +REPLACE;ca\plants2\bush\b_craet1.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_craet1W.p3d;0 +REPLACE;ca\plants2\bush\b_salix2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_salix2sW.p3d;0 +REPLACE;ca\plants_e2\bush\b_sambucus_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_sambucusW.p3d;0 +REPLACE;ca\plants2\bush\b_sambucus.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_sambucusW.p3d;0 +REPLACE;ca\plants2\plant\p_urtica.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_p_urticaW.p3d;0 +REPLACE;ca\plants2\bush\b_betulahumilis.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_b_betulaHumilisW.p3d;0 +REPLACE;ca\plants2\bush\b_corylus.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_b_corylusW.p3d;0 +REPLACE;ca\plants2\bush\b_pmugo.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_b_pmugoW.p3d;0 +REPLACE;ca\plants2\tree\t_betula1f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_betula1fW.p3d;0 +REPLACE;ca\plants2\tree\t_betula2f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_betula2fW.p3d;0 +REPLACE;ca\plants_e2\tree\t_betula2s_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_betula2sW.p3d; +REPLACE;ca\plants_e2\bush\b_betula2w_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_betula2wW.p3d; +REPLACE;ca\plants2\tree\t_fagus2f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_fagus2fW.p3d;0 +REPLACE;ca\plants_e2\tree\t_fagus2f_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_fagus2fW.p3d; +REPLACE;ca\plants_e2\tree\t_malus1s_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d; +REPLACE;ca\plants2\tree\t_malus1s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d;0 +REPLACE;ca\plants2\tree\t_picea1s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea1sW.p3d;0 +REPLACE;ca\plants2\tree\t_picea2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea2sW.p3d;0 +REPLACE;ca\plants2\tree\t_picea3f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea3fW.p3d;0 +REPLACE;ca\plants2\tree\t_pinusn1s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN1sW.p3d;0 +REPLACE;ca\plants2\tree\t_pinusn2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN2sW.p3d;0 +REPLACE;ca\plants2\tree\t_pinuss2f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusS2fW.p3d;0 +REPLACE;ca\plants2\tree\t_pyrus2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pyrus2sW.p3d;0 +REPLACE;ca\plants_e2\tree\t_quercus2f_summer.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_quercus2fW.p3d; +REPLACE;ca\plants2\tree\t_quercus2f.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_quercus2fW.p3d;0 +REPLACE;ca\plants2\tree\t_quercus3s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_quercus3sW.p3d;0 +REPLACE;ca\plants2\tree\t_salix2s.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_salix2sW.p3d;0 +REPLACE;a3\plants_f\bush\b_ficusc1s_f.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_corylus2sW.p3d; +REPLACE;a3\plants_f\bush\b_neriumo2d_f.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_craet1W.p3d; +REPLACE;ca\plants\les_singlestrom_b.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea3fW.p3d;1 +REPLACE;ca\plants_e\Tree\t_AmygdalusC2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d; +REPLACE;ca\plants_e\Tree\t_FicusB2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pyrus2sW.p3d;1 +REPLACE;a3\plants_f\tree\t_PinusS1s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN1sW.p3d; +REPLACE;a3\plants_f\tree\t_PinusS2s_b_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN2sW.p3d;0 +REPLACE;ca\plants\krovi_bigest.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_sambucusW.p3d; +REPLACE;ca\plants_e\Tree\t_PinusS3s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusS2fW.p3d; +REPLACE;ca\plants_e\Tree\t_PinusE2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN2sW.p3d;0 +REPLACE;a3\plants_f\Bush\b_Thistle_Thorn_Green.p3d;CUP\Terrains\cup_terrains_winter_plants\Clutter\cup_piceaW.p3d; +REPLACE;a3\plants_f\tree\t_PinusS2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pinusN2sW.p3d;0 +REPLACE;a3\plants_f\tree\t_FraxinusAV2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_fraxinus2sW.p3d;-2.5 +REPLACE;a3\plants_f\tree\t_FicusB1s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d; +REPLACE;ca\plants\smrk_maly.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea1sW.p3d; +REPLACE;ca\plants\krovi2.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_b_corylusW.p3d; +REPLACE;ca\plants\str_liskac.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_corylus2sW.p3d; +REPLACE;ca\plants_e\Bush\b_PistaciaL1s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_b_pmugoW.p3d; +REPLACE;a3\plants_f\tree\t_FicusB2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_pyrus2sW.p3d;1 +REPLACE;ca\plants\smrk_siroky.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea2sW.p3d; +REPLACE;ca\plants\krovi_long.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_prunusW.p3d; +REPLACE;ca\plants\parez.p3d;CUP\Terrains\cup_terrains_winter_plants\dead\cup_misc_stub1W.p3d; +REPLACE;ca\plants_e\Tree\t_PopulusF2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_fagus2sW.p3d; +REPLACE;ca\plants\smrk_velky.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_picea2sW.p3d; +REPLACE;a3\plants_f\Bush\b_NeriumO2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_prunusW.p3d; +REPLACE;a3\plants_f\tree\t_QuercusIR2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_quercus3sW.p3d;1 +REPLACE;a3\plants_f\clutter\c_StrThornGreen.p3d;CUP\Terrains\cup_terrains_winter_plants\Clutter\cup_piceaW.p3d; +REPLACE;a3\plants_f\clutter\c_thistle_thorn_green.p3d;CUP\Terrains\cup_terrains_winter_plants\Clutter\cup_piceaW.p3d; +REPLACE;ca\plants_e\Tree\t_PopulusB2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_quercus2fW.p3d; +REPLACE;a3\plants_f\Bush\b_FicusC2s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_corylus2sW.p3d; +REPLACE;ca\plants\les_dub_jiny.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_sorbus2sW.p3d;-0.5 +REPLACE;a3\plants_f\Bush\b_NeriumO2s_white_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_prunusW.p3d; +REPLACE;a3\plants_f\tree\t_PopulusN3s_F.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_populus3sW.p3d;0 +REPLACE;ca\plants\jablon.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d;0.5 +REPLACE;ca\plants_e\Bush\b_AmygdalusN1s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Bushes\cup_b_sambucusW.p3d; +REPLACE;ca\plants_e\Tree\t_PistaciaL2s_EP1.p3d;CUP\Terrains\cup_terrains_winter_plants\Trees\cup_t_malus1sW.p3d;0 \ No newline at end of file diff --git a/bis-file-formats.sln b/bis-file-formats.sln index e2ddba5..8608cd0 100644 --- a/bis-file-formats.sln +++ b/bis-file-formats.sln @@ -19,6 +19,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.WRP", "BIS.WRP\BIS.WRP. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BIS.Core.Test", "BIS.Core.Test\BIS.Core.Test.csproj", "{6A91F1B2-5C8A-495A-824B-1BC7825B1939}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BIS.P3D.Test", "BIS.P3D.Test\BIS.P3D.Test.csproj", "{B49FDF6F-5F9E-424D-B2D7-A41B9BC9E1D3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A91F1B2-5C8A-495A-824B-1BC7825B1939}.Release|Any CPU.Build.0 = Release|Any CPU + {B49FDF6F-5F9E-424D-B2D7-A41B9BC9E1D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B49FDF6F-5F9E-424D-B2D7-A41B9BC9E1D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B49FDF6F-5F9E-424D-B2D7-A41B9BC9E1D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B49FDF6F-5F9E-424D-B2D7-A41B9BC9E1D3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE