-
Notifications
You must be signed in to change notification settings - Fork 10
Feature to restore a map source #287
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
jetelain
wants to merge
4
commits into
master
Choose a base branch
from
sos
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
GameRealisticMap.Arma3/Edit/Imagery/Generic/GenericIdMapReadStorage.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| using BIS.PAA; | ||
| using GameRealisticMap.Arma3.Assets; | ||
| using GameRealisticMap.Arma3.IO; | ||
| using Pmad.HugeImages.Storage; | ||
| using SixLabors.ImageSharp; | ||
| using SixLabors.ImageSharp.PixelFormats; | ||
| using SixLabors.ImageSharp.Processing; | ||
|
|
||
| namespace GameRealisticMap.Arma3.Edit.Imagery.Generic | ||
| { | ||
| internal sealed class GenericIdMapReadStorage : IHugeImageStorageSlot | ||
| { | ||
| private readonly GenericImageryInfos partitioner; | ||
| private readonly IGameFileSystem fileSystem; | ||
| private readonly Dictionary<string, TerrainMaterial> materials; | ||
| private readonly int tileSize; | ||
|
|
||
| public GenericIdMapReadStorage(GenericImageryInfos partitioner, IGameFileSystem fileSystem, TerrainMaterialLibrary library) | ||
| { | ||
| this.partitioner = partitioner; | ||
| this.fileSystem = fileSystem; | ||
| materials = library.Definitions.Select(d => d.Material).ToDictionary(m => m.GetColorTexturePath(partitioner), m => m, StringComparer.OrdinalIgnoreCase); | ||
| tileSize = partitioner.TileSize * partitioner.IdMapMultiplier; | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
|
|
||
| } | ||
|
|
||
| public Task<Image<TPixel>?> LoadImagePart<TPixel>(int partId) | ||
| where TPixel : unmanaged, IPixel<TPixel> | ||
| { | ||
| var part = partitioner.OtherTileInfos[partId-1]; | ||
|
|
||
| var textures = part.Textures.Select(tex => materials[tex]).Select(t => t.Id).ToList(); | ||
|
|
||
| using var paaStream = fileSystem.OpenFileIfExists(part.Mask); | ||
| if (paaStream == null) | ||
| { | ||
| throw new FileNotFoundException($"File '{part.Mask}' was not found."); | ||
| } | ||
| var paa = new PAA(paaStream); | ||
| var map = paa.Mipmaps.OrderBy(m => m.Width).Last(); | ||
| var pixels = PAA.GetARGB32PixelData(paa, paaStream, map); | ||
| var maskImage = Image.LoadPixelData<Bgra32>(pixels, map.Width, map.Height).CloneAs<Rgba32>(); | ||
| if (maskImage.Width != tileSize || maskImage.Height != tileSize) | ||
| { | ||
| maskImage.Mutate(img => img.Resize(tileSize, tileSize)); | ||
| } | ||
| var finalImage = new Image<TPixel>(maskImage.Width, maskImage.Height); | ||
| var px = new TPixel(); | ||
| for (int x = 0; x < maskImage.Width; ++x) | ||
| { | ||
| for (int y = 0; y < maskImage.Height; ++y) | ||
| { | ||
| px.FromRgb24(IdMapReadStorage.GetColor(maskImage[x, y], textures)); | ||
| finalImage[x, y] = px; | ||
| } | ||
| } | ||
| return Task.FromResult(finalImage)!; | ||
| } | ||
|
|
||
| public Task SaveImagePart<TPixel>(int partId, Image<TPixel> partImage) | ||
| where TPixel : unmanaged, IPixel<TPixel> | ||
| { | ||
| throw new NotSupportedException(); | ||
| } | ||
| } | ||
| } | ||
229 changes: 229 additions & 0 deletions
229
GameRealisticMap.Arma3/Edit/Imagery/Generic/GenericImageryInfos.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,229 @@ | ||
| using System.Globalization; | ||
| using BIS.PAA; | ||
| using BIS.WRP; | ||
| using GameRealisticMap.Arma3.Assets; | ||
| using GameRealisticMap.Arma3.GameEngine; | ||
| using GameRealisticMap.Arma3.IO; | ||
| using Pmad.HugeImages; | ||
| using SixLabors.ImageSharp; | ||
| using SixLabors.ImageSharp.PixelFormats; | ||
|
|
||
| namespace GameRealisticMap.Arma3.Edit.Imagery.Generic | ||
| { | ||
| public class GenericImageryInfos : IArma3MapConfig, IImageryInfos | ||
| { | ||
| public GenericImageryInfos(List<GenericTileInfo> otherTileInfos, int tileSize, double resolution, float sizeInMeters, string pboPrefix, int idMapMultiplier) | ||
| { | ||
| OtherTileInfos = otherTileInfos; | ||
| TileSize = tileSize; | ||
| Resolution = resolution; | ||
| SizeInMeters = sizeInMeters; | ||
| IdMapMultiplier = idMapMultiplier; | ||
| PboPrefix = pboPrefix; | ||
| } | ||
|
|
||
| public List<GenericTileInfo> OtherTileInfos { get; } | ||
|
|
||
| public int TileSize { get; } | ||
|
|
||
| public double Resolution { get; } | ||
|
|
||
| public float SizeInMeters { get; } | ||
|
|
||
| public int IdMapMultiplier { get; } | ||
|
|
||
| public int TotalSize => (int)Math.Floor(SizeInMeters / Resolution); | ||
|
|
||
| public string PboPrefix { get; } | ||
|
|
||
| public float FakeSatBlend => throw new NotImplementedException(); | ||
|
|
||
| public string WorldName => throw new NotImplementedException(); | ||
|
|
||
| public bool UseColorCorrection => throw new NotImplementedException(); | ||
|
|
||
| public IEnumerable<GroundDetailTexture> GetGroundDetailTextures() | ||
| { | ||
| return OtherTileInfos.SelectMany(t => Enumerable.Range(0, t.Textures.Count).Select(i => new GroundDetailTexture(t.Textures[i], t.Normals[i]))).Distinct(); | ||
| } | ||
|
|
||
| public static async Task<GenericImageryInfos?> TryCreate(IGameFileSystem fileSystem, EditableWrp wrp, string pboPrefix) | ||
| { | ||
| var rvmatCache = new Dictionary<string, TerrainRvMatInfos>(); | ||
|
|
||
| var list = new List<IntermediateTileInfo>(); | ||
| for (int x = 0; x < wrp.LandRangeX; x++) | ||
| { | ||
| for (int y = 0; y < wrp.LandRangeY; y++) | ||
| { | ||
| var materialIndex = wrp.MaterialIndex[x + y * wrp.LandRangeX]; | ||
| var materialName = wrp.MatNames[materialIndex]; | ||
| var infos = await GetMaterialInfo(fileSystem, rvmatCache, materialName); | ||
| if (infos != null) | ||
| { | ||
| infos.CellIndexes.Add(new Point(x, y)); | ||
| } | ||
| else | ||
| { | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var tiles = new List<IntermediateTileInfo>(); | ||
| foreach (var tileGroup in rvmatCache.Values.GroupBy(v => new { v.Mask, v.Sat })) | ||
| { | ||
| tiles.Add(new IntermediateTileInfo() | ||
| { | ||
| Mask = tileGroup.Key.Mask, | ||
| Sat = tileGroup.Key.Sat, | ||
| UA = tileGroup.First().UA, | ||
| UB = tileGroup.First().UB, | ||
| VB = tileGroup.First().VB, | ||
| CellIndexes = Rectangle.FromLTRB( | ||
| tileGroup.Min(v => v.CellIndexes.Min(p => p.X)), | ||
| tileGroup.Min(v => v.CellIndexes.Min(p => p.Y)), | ||
| tileGroup.Max(v => v.CellIndexes.Max(p => p.X)), | ||
| tileGroup.Max(v => v.CellIndexes.Max(p => p.Y))), | ||
| Textures = Merge(tileGroup.Select(v => v.Textures)), | ||
| Normals = Merge(tileGroup.Select(v => v.Normals)), | ||
| }); | ||
| } | ||
|
|
||
| var mask = GetPaa(tiles[0].Mask, fileSystem); | ||
|
|
||
| var sat = GetPaa(tiles[0].Sat, fileSystem); | ||
|
|
||
| if (mask == null || sat == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var tileSize = sat.Width; | ||
| var idMapMultiplier = mask.Width / sat.Width; | ||
| var resolution = 1d / (tiles[0].UA * tileSize); | ||
| var landgridCellCount = tiles.Select(t => t.CellIndexes.Width).Max() + 1; | ||
| var tileSizeMeters = landgridCellCount * wrp.CellSize; | ||
| var step = (int)Math.Round(tileSizeMeters / resolution); | ||
| var halfOverlap = (tileSize - step) / 2; | ||
|
|
||
| var fullImageSize = new Size((int)Math.Round(wrp.CellSize * wrp.LandRangeX / resolution), (int)Math.Round(wrp.CellSize * wrp.LandRangeY / resolution)); | ||
|
|
||
| var top = fullImageSize.Height + tileSize - halfOverlap - step; | ||
|
|
||
| var mappedTiles = new List<GenericTileInfo>(); | ||
|
|
||
| foreach (var tile in tiles) | ||
| { | ||
| var x = Math.Round((halfOverlap - tile.UB * tileSize) / step); | ||
| var y = Math.Round((top - tile.VB * tileSize) / step); | ||
|
|
||
| var grmTile = new ImageryTile((int)x, (int)y, step, halfOverlap, tileSize, top, tile.UA); | ||
| if (Math.Abs(grmTile.UB - tile.UB) > 0.0001 || Math.Abs(grmTile.VB - tile.VB) > 0.0001) | ||
| { | ||
| // Results mismatch, not compatible | ||
| return null; | ||
| } | ||
|
|
||
| // It's compatible ! | ||
| mappedTiles.Add(new GenericTileInfo(grmTile, tile)); | ||
| } | ||
|
|
||
| return new GenericImageryInfos( | ||
| mappedTiles.OrderBy(i => i.X).ThenBy(i => i.Y).ToList(), | ||
| tileSize, | ||
| resolution, | ||
| sizeInMeters: wrp.LandRangeX * wrp.CellSize, | ||
| pboPrefix, | ||
| idMapMultiplier); | ||
| } | ||
|
|
||
| private static PAA? GetPaa(string path, IGameFileSystem fileSystem) | ||
| { | ||
| using var stream = fileSystem.OpenFileIfExists(path); | ||
| if (stream == null) | ||
| { | ||
| return null; | ||
| } | ||
| return new PAA(stream); | ||
| } | ||
|
|
||
| private static async Task<TerrainRvMatInfos?> GetMaterialInfo(IGameFileSystem fileSystem, Dictionary<string, TerrainRvMatInfos> rvmatCache, string materialName) | ||
| { | ||
| if (!rvmatCache.TryGetValue(materialName, out var infos)) | ||
| { | ||
| using var content = fileSystem.OpenFileIfExists(materialName); | ||
| if (content == null) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| var contextAsText = await GameConfigHelper.GetText(content); | ||
| if (TerrainRvMatInfos.ShaderMatch.Matches(contextAsText).Count == 0) | ||
| { | ||
| return null; | ||
| } | ||
| var textures = IdMapHelper.TextureMatch.Matches(contextAsText).Select(m => m.Groups[1].Value).ToList(); | ||
| var normals = IdMapHelper.NormalMatch.Matches(contextAsText).Select(m => m.Groups[1].Value).ToList(); | ||
| var sat = TerrainRvMatInfos.SatMatch.Matches(contextAsText).Select(m => m.Groups[1].Value).ToList(); | ||
| var mask = TerrainRvMatInfos.MaskMatch.Matches(contextAsText).Select(m => m.Groups[1].Value).ToList(); | ||
| var transform = TerrainRvMatInfos.UvTransformMatch.Matches(contextAsText).FirstOrDefault(); | ||
|
|
||
| if (sat.Count == 1 && mask.Count == 1 && textures.Count > 0 && normals.Count > 0 && transform != null) | ||
| { | ||
| rvmatCache.Add(materialName, infos = new TerrainRvMatInfos() | ||
| { | ||
| Sat = sat[0], | ||
| Mask = mask[0], | ||
| Textures = textures, | ||
| Normals = normals, | ||
| UA = float.Parse(transform.Groups["UA"].Value, CultureInfo.InvariantCulture), | ||
| UB = float.Parse(transform.Groups["UB"].Value, CultureInfo.InvariantCulture), | ||
| VB = float.Parse(transform.Groups["VB"].Value, CultureInfo.InvariantCulture) | ||
| }); | ||
| } | ||
| } | ||
| return infos; | ||
| } | ||
|
|
||
| private static List<string> Merge(IEnumerable<List<string>> enumerable) | ||
| { | ||
| var first = enumerable.First(); | ||
|
|
||
| foreach (var item in enumerable.Skip(1)) | ||
| { | ||
| for (int i = 0; i < first.Count; i++) | ||
| { | ||
| if (string.IsNullOrEmpty(first[i]) && !string.IsNullOrEmpty(item[i])) | ||
| { | ||
| first[i] = item[i]; | ||
| } | ||
| } | ||
| } | ||
| return first; | ||
| } | ||
|
|
||
|
|
||
| public HugeImage<Rgb24> GetIdMap(IGameFileSystem fileSystem, TerrainMaterialLibrary materials) | ||
| { | ||
| return GetIdMap<Rgb24>(fileSystem, materials); | ||
| } | ||
|
|
||
| public HugeImage<Rgb24> GetSatMap(IGameFileSystem fileSystem) | ||
| { | ||
| return GetSatMap<Rgb24>(fileSystem); | ||
| } | ||
|
|
||
| public HugeImage<TPixel> GetIdMap<TPixel>(IGameFileSystem fileSystem, TerrainMaterialLibrary materials) where TPixel : unmanaged, IPixel<TPixel> | ||
| { | ||
| var parts = new ImageryTilerHugeImagePartitioner(OtherTileInfos.Select(o => o.GrmTile).ToList(), IdMapMultiplier); | ||
| return new HugeImage<TPixel>(new GenericIdMapReadStorage(this, fileSystem, materials), new Size(TotalSize * IdMapMultiplier), new HugeImageSettingsBase(), parts, new TPixel()); | ||
| } | ||
|
|
||
| public HugeImage<TPixel> GetSatMap<TPixel>(IGameFileSystem fileSystem) where TPixel : unmanaged, IPixel<TPixel> | ||
| { | ||
| var parts = new ImageryTilerHugeImagePartitioner(OtherTileInfos.Select(o => o.GrmTile).ToList(), 1); | ||
| return new HugeImage<TPixel>(new GenericSatMapReadStorage(this, fileSystem), new Size(TotalSize), new HugeImageSettingsBase(), parts, new TPixel()); | ||
| } | ||
| } | ||
| } |
74 changes: 74 additions & 0 deletions
74
GameRealisticMap.Arma3/Edit/Imagery/Generic/GenericImagerySource.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| using BIS.PAA; | ||
| using GameRealisticMap.Arma3.Assets; | ||
| using GameRealisticMap.Arma3.GameEngine; | ||
| using GameRealisticMap.Arma3.IO; | ||
| using Pmad.HugeImages; | ||
| using Pmad.HugeImages.Processing; | ||
| using SixLabors.ImageSharp; | ||
| using SixLabors.ImageSharp.PixelFormats; | ||
|
|
||
| namespace GameRealisticMap.Arma3.Edit.Imagery.Generic | ||
| { | ||
| public class GenericImagerySource : IImagerySource | ||
| { | ||
| private readonly GenericImageryInfos imagery; | ||
| private readonly IGameFileSystem projectDrive; | ||
| private readonly TerrainMaterialLibrary materials; | ||
|
|
||
| public GenericImagerySource(GenericImageryInfos imagery, IGameFileSystem projectDrive, TerrainMaterialLibrary materials) | ||
| { | ||
| this.imagery = imagery; | ||
| this.projectDrive = projectDrive; | ||
| this.materials = materials; | ||
| } | ||
|
|
||
| public Task<HugeImage<Rgba32>> CreateIdMap() | ||
| { | ||
| return Task.FromResult(imagery.GetIdMap<Rgba32>(projectDrive, materials)); | ||
| } | ||
|
|
||
| public async Task<Image> CreatePictureMap() | ||
| { | ||
| var existing = LoadExisting("picturemap_ca"); | ||
| if (existing != null) | ||
| { | ||
| return existing; | ||
| } | ||
| using var satMap = imagery.GetSatMap(projectDrive); | ||
| return await satMap.ToScaledImageAsync(2048, 2048); | ||
| } | ||
|
|
||
| public Task<HugeImage<Rgba32>> CreateSatMap() | ||
| { | ||
| return Task.FromResult(imagery.GetSatMap<Rgba32>(projectDrive)); | ||
| } | ||
|
|
||
| public Image CreateSatOut() | ||
| { | ||
| var existing = LoadExisting("satout_ca"); | ||
| if (existing != null) | ||
| { | ||
| return existing; | ||
| } | ||
| return new Image<Rgb24>(4, 4); | ||
| } | ||
|
|
||
| private Image? LoadExisting(string name) | ||
| { | ||
| using var paaStream = projectDrive.OpenFileIfExists($"{imagery.PboPrefix}\\data\\{name}.paa"); | ||
| if (paaStream != null) | ||
| { | ||
| var paa = new PAA(paaStream); | ||
| var map = paa.Mipmaps.OrderBy(m => m.Width).Last(); | ||
| var pixels = PAA.GetARGB32PixelData(paa, paaStream, map); | ||
| return Image.LoadPixelData<Bgra32>(pixels, map.Width, map.Height).CloneAs<Rgb24>(); | ||
| } | ||
| using var png = projectDrive.OpenFileIfExists($"{imagery.PboPrefix}\\data\\{name}.png"); | ||
| if (png != null) | ||
| { | ||
| return Image.Load(png); | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accessing an element using 'partId-1' assumes that the part IDs are 1-based and that the index will always be within bounds. Please add a validation to ensure 'partId' is valid to prevent potential out-of-range errors.