From aa02309f2d5c987b8ee16be84f112f4d2ad85b1d Mon Sep 17 00:00:00 2001 From: bird_egop Date: Wed, 13 May 2026 02:53:42 +0300 Subject: [PATCH] BAS file parsing --- ParkanPlayground/BasParser.cs | 86 +++++++++++++++++++++++++++++++++++ ParkanPlayground/Program.cs | 62 +------------------------ README.md | 82 +++++++++++---------------------- 3 files changed, 114 insertions(+), 116 deletions(-) create mode 100644 ParkanPlayground/BasParser.cs diff --git a/ParkanPlayground/BasParser.cs b/ParkanPlayground/BasParser.cs new file mode 100644 index 0000000..b7295e8 --- /dev/null +++ b/ParkanPlayground/BasParser.cs @@ -0,0 +1,86 @@ +using Common; + +namespace ParkanPlayground; + +public record BasContours(InnerContour[] Inner, OuterContour[] Outer); +public record InnerContour(Vector3[] Points, uint[] Nums1, uint[] Nums2); +public record OuterContour(Vector3[] Points); + +public static class BasParser +{ + public static BasContours ReadFile(string path) + { + using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + + return ReadFile(fs); + } + + public static BasContours ReadFile(Stream fs) + { + var innerContourCount = fs.ReadUInt32LittleEndian(); + + InnerContour[] innerContours = new InnerContour[innerContourCount]; + + for (var i = 0; i < innerContourCount; i++) + { + var pointCount = fs.ReadUInt32LittleEndian(); + + Vector3[] points = new Vector3[pointCount + 1]; + + for (var j = 0; j < pointCount + 1; j++) + { + var x = fs.ReadFloatLittleEndian(); + var y = fs.ReadFloatLittleEndian(); + var z = fs.ReadFloatLittleEndian(); + + points[j] = new Vector3(x, y, z); + } + + uint[] nums1 = new uint[pointCount]; + + // batch 1 + for (var j = 0; j < pointCount; j++) + { + var num = fs.ReadUInt32LittleEndian(); + nums1[j] = num; + } + + uint[] nums2 = new uint[pointCount]; + + // batch 2 + for (var j = 0; j < pointCount; j++) + { + var num = fs.ReadUInt32LittleEndian(); + nums2[j] = num; + } + + innerContours[i] = new InnerContour(points, nums1, nums2); + } + + + var outerContourCount = fs.ReadUInt32LittleEndian(); + + OuterContour[] outerContours = new OuterContour[outerContourCount]; + + for (var i = 0; i < outerContourCount; i++) + { + var pointCount = fs.ReadUInt32LittleEndian(); + + Vector3[] points = new Vector3[pointCount + 1]; + for (var j = 0; j < pointCount + 1; j++) + { + var x = fs.ReadFloatLittleEndian(); + var y = fs.ReadFloatLittleEndian(); + var z = fs.ReadFloatLittleEndian(); + + points[j] = new Vector3(x, y, z); + } + + outerContours[i] = new OuterContour(points); + } + + BasContours contours = new BasContours(innerContours, outerContours); + + return contours; + } +} diff --git a/ParkanPlayground/Program.cs b/ParkanPlayground/Program.cs index fddfe86..7c14dd9 100644 --- a/ParkanPlayground/Program.cs +++ b/ParkanPlayground/Program.cs @@ -1,63 +1,3 @@ -using ResTreeLib; +using Common; System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); -var trfPath = Directory.EnumerateFiles(@"C:\Program Files (x86)\Nikita\Iron Strategy\MISSIONS\SCRIPTS", "*.trf").First(); -var nodes = ResTreeParser.Parse(trfPath); - -var active = nodes.Where(n => !string.IsNullOrWhiteSpace(n.ShortName) && (n.Node.ResearchCost > 0 || n.Node.ResearchTime > 0)).ToList(); -Console.WriteLine($"active={active.Count}"); - -Analyze("X from Cost", active.Select(n => (A:n.Node.ResearchCost, B:n.Node.ViewPosX, Name:n.ShortName, Idx:n.Index)).ToList()); -Analyze("Y from Time", active.Select(n => (A:n.Node.ResearchTime, B:n.Node.ViewPosY, Name:n.ShortName, Idx:n.Index)).ToList()); - -Console.WriteLine(); -Console.WriteLine("Largest |Y-Time| (active):"); -foreach (var n in active.OrderByDescending(n => Math.Abs(n.Node.ViewPosY - n.Node.ResearchTime)).Take(20)) -{ - Console.WriteLine($"idx={n.Index,3} {n.ShortName,-12} T={n.Node.ResearchTime,6:0.##} Y={n.Node.ViewPosY,6:0.##} d={n.Node.ViewPosY-n.Node.ResearchTime,6:0.##}"); -} - -Console.WriteLine(); -Console.WriteLine("Largest |X-Cost| (active):"); -foreach (var n in active.OrderByDescending(n => Math.Abs(n.Node.ViewPosX - n.Node.ResearchCost)).Take(20)) -{ - Console.WriteLine($"idx={n.Index,3} {n.ShortName,-12} C={n.Node.ResearchCost,6:0.##} X={n.Node.ViewPosX,6:0.##} d={n.Node.ViewPosX-n.Node.ResearchCost,6:0.##}"); -} - -static void Analyze(string title, List<(float A, float B, string Name, int Idx)> data) -{ - float meanA = data.Average(x => x.A); - float meanB = data.Average(x => x.B); - float cov=0, varA=0; - foreach (var d in data) - { - var da = d.A - meanA; - var db = d.B - meanB; - cov += da*db; - varA += da*da; - } - float slope = varA == 0 ? 0 : cov / varA; - float intercept = meanB - slope*meanA; - - float mae = data.Average(d => Math.Abs((slope*d.A + intercept) - d.B)); - float rmse = MathF.Sqrt(data.Average(d => MathF.Pow((slope*d.A + intercept) - d.B,2))); - - float corr = Corr(data.Select(x=>x.A).ToArray(), data.Select(x=>x.B).ToArray()); - - Console.WriteLine($"{title}: corr={corr:0.###}, slope={slope:0.###}, intercept={intercept:0.###}, MAE={mae:0.###}, RMSE={rmse:0.###}"); -} - -static float Corr(float[] a, float[] b) -{ - if (a.Length != b.Length || a.Length == 0) return 0; - var ma = a.Average(); - var mb = b.Average(); - float top=0, aa=0, bb=0; - for(int i=0;i IC +{ + (4 байта) Кол-во сегментов контура -> N + Vector3[N+1] Точки контура + int[N] - непонятно + int[N] - тоже непонятно, но выглядит как порядок вершин (e.g. [0 2 1]) +}[IC] +(4 байта) Кол-во внешних контуров -> OC +{ + (4 байта) Кол-во сегментов контура -> N + Vector3[N+1] Точки контура +}[OC] +``` + ## Lightmap (TEXM) У текстур использующихся как карты освещения специальные имена. @@ -309,10 +333,6 @@ IComponent ** LoadSomething(undefined4, undefined4, undefined4, undefined4) All lightmaps named whatever.0 → they all end up with no DirectDraw palette attached. ``` -## Люблю разработчиков - -У них получилось сделать LightSource и MaterialDescriptor чётко одинаковыми по размеру (0x5c) из-за чего я потерял 3 дня прыгая по указателям - ## `FXID` - файл эффектов По сути представляет собой последовательный список саб-эффектов идущих друг за другом. @@ -355,12 +375,6 @@ enum EffectTemplateFlags : uint32_t # ЕСЛИ ГДЕ-ТО ВИДИШЬ PTR_func_ret1_no_args, ТО ЭТО ShaderConfig -В рендеринге порядок дата-стримов такой - -position -normal -color - # Внутренняя система ID - `1` - unknown (implemented by CLandscape) видимо ILandscape @@ -456,48 +470,6 @@ World3D.dll содержит функцию CreateGameSettings. | ---- | 30 (0x1e) | ShadeConfig | | из файла shade.cfg | | ---- | (0x8001e) | | | добавляет AniMesh | - -## Goodies - -```c++ -// Тип положения объекта в пространстве. Используется при расчёте эффектов, -// расстановке объектов и для получения/выдачи их матриц трансформации. -enum EPlacementType -{ - // 0: Полная мировая матрица, ориентированная "смотрю на цель". - // Входная matrix — это мировая матрица (look-at к какой-то цели). - // Движок при необходимости переводит её в локальное/костное пространство - // (через inverse(parent/joint) * world и т.п.). - // Смысл: "Построй мне мировую матрицу так, чтобы я смотрел на цель; - // если я привязан к кости — учти это." - Placement_WorldLookAtTarget = 0, - - // 1: Мировая матрица, где основная ось объекта (this->direction) - // выровнена по направлению к цели. - // Входная matrix — мировая; логика выравнивания использует внутренний - // вектор direction и целевую точку/направление. - // Смысл: "Используй мой внутренний direction как основную ось - // и как можно лучше направь её в сторону цели." - Placement_WorldAlignDirectionToTarget = 1, - - // 2: Мировая матрица, полностью задаётся внутренним состоянием объекта. - // Входная matrix трактуется как "standalone world" — готовая мировая - // матрица объекта. Движок лишь переводит её во внутреннее локальное/ - // костное пространство (inverse(parent/joint) * world), без look-at - // и без выравнивания по цели. - // Смысл: "Вот моя конечная мировая матрица. Просто встрои её в иерархию, - // не трогая ориентацию дополнительной логикой." - Placement_WorldFromStoredTransform = 2, - - // 3: Мировая матрица, ориентированная вдоль вектора движения, но с учётом цели. - // Используется предыдущая мировая позиция + текущая + target, чтобы - // построить базис: основная ось — направление движения, вспомогательная — - // направление к цели. - // Смысл: "Ориентируй меня по вектору моего движения, но также учитывай цель." - Placement_WorldAlignMotionToTarget = 3 -}; -``` - ## Контакты Вы можете связаться со мной в [Telegram](https://t.me/bird_egop).