add tile key selection for addressables builds
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -98,3 +98,4 @@ InitTestScene*.unity*
|
||||
# Auto-generated scenes by play mode tests
|
||||
/[Aa]ssets/[Ii]nit[Tt]est[Ss]cene*.unity*
|
||||
|
||||
Notes
|
||||
|
||||
@@ -17,18 +17,18 @@ MonoBehaviour:
|
||||
serializedVersion: 2
|
||||
Hash: 00000000000000000000000000000000
|
||||
m_OptimizeCatalogSize: 0
|
||||
m_BuildRemoteCatalog: 0
|
||||
m_BuildRemoteCatalog: 1
|
||||
m_CatalogRequestsTimeout: 0
|
||||
m_DisableCatalogUpdateOnStart: 0
|
||||
m_InternalIdNamingMode: 0
|
||||
m_InternalBundleIdMode: 1
|
||||
m_AssetLoadMode: 0
|
||||
m_BundledAssetProviderType:
|
||||
m_AssemblyName:
|
||||
m_ClassName:
|
||||
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.BundledAssetProvider
|
||||
m_AssetBundleProviderType:
|
||||
m_AssemblyName:
|
||||
m_ClassName:
|
||||
m_AssemblyName: Unity.ResourceManager, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
|
||||
m_ClassName: UnityEngine.ResourceManagement.ResourceProviders.AssetBundleProvider
|
||||
m_IgnoreUnsupportedFilesInBuild: 0
|
||||
m_UniqueBundleIds: 0
|
||||
m_EnableJsonCatalog: 0
|
||||
@@ -51,9 +51,9 @@ MonoBehaviour:
|
||||
m_CheckForContentUpdateRestrictionsOption: 0
|
||||
m_MonoScriptBundleCustomNaming:
|
||||
m_RemoteCatalogBuildPath:
|
||||
m_Id: 888e852cf5299d044b396c8c5d1be8ca
|
||||
m_Id: 3dbec98a2ceea7a2db5e98358038bd43
|
||||
m_RemoteCatalogLoadPath:
|
||||
m_Id:
|
||||
m_Id: 334365b7569c7a681827d1a99c166327
|
||||
m_ContentStateBuildPathProfileVariableName:
|
||||
m_CustomContentStateBuildPath:
|
||||
m_ContentStateBuildPath:
|
||||
|
||||
8
Assets/AddressableAssetsData/Linux.meta
Normal file
8
Assets/AddressableAssetsData/Linux.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f929c00fb92096faea57ffc942e2424d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1933,6 +1933,10 @@ PrefabInstance:
|
||||
propertyPath: m_Parameters.numCapVertices
|
||||
value: 4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2761784063978902504, guid: c1800acf6366418a9b5f610249000331, type: 3}
|
||||
propertyPath: m_Parameters.widthMultiplier
|
||||
value: 0.01
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2761784063978902504, guid: c1800acf6366418a9b5f610249000331, type: 3}
|
||||
propertyPath: m_Parameters.numCornerVertices
|
||||
value: 4
|
||||
@@ -2180,6 +2184,10 @@ PrefabInstance:
|
||||
propertyPath: m_Parameters.numCapVertices
|
||||
value: 4
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2761784063978902504, guid: c1800acf6366418a9b5f610249000331, type: 3}
|
||||
propertyPath: m_Parameters.widthMultiplier
|
||||
value: 0.01
|
||||
objectReference: {fileID: 0}
|
||||
- target: {fileID: 2761784063978902504, guid: c1800acf6366418a9b5f610249000331, type: 3}
|
||||
propertyPath: m_Parameters.numCornerVertices
|
||||
value: 4
|
||||
|
||||
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.AddressableAssets;
|
||||
using UnityEditor.AddressableAssets.Settings;
|
||||
@@ -15,99 +16,240 @@ public static class GeoTileAddressablesBuilder
|
||||
private const string GroupName = "TilePrefabs";
|
||||
private const string TileLabel = "tile";
|
||||
private const string ManifestFileName = "TileManifest.json";
|
||||
private const float TileSizeMeters = 1000f;
|
||||
private const float DefaultTileSizeMeters = 1000f;
|
||||
private const float DefaultTileSizeX = 1000f;
|
||||
private const float DefaultTileSizeY = 1000f;
|
||||
private const float DefaultOverlapX = 0.5f;
|
||||
private const float DefaultOverlapY = 0.5f;
|
||||
|
||||
private const string BuildPathVariable = "TileBuildPath";
|
||||
private const string LoadPathVariable = "TileLoadPath";
|
||||
private const string BuildPathValue = "ServerData/TileBundles/[BuildTarget]";
|
||||
private const string LoadPathValue = "file://{UnityEngine.Application.persistentDataPath}/TileBundles/[BuildTarget]";
|
||||
|
||||
[MenuItem("Tools/Geo Tiles/Build Tile Addressables (Android)")]
|
||||
internal struct TileKeyConfig
|
||||
{
|
||||
public float TileSizeX;
|
||||
public float TileSizeY;
|
||||
public float OverlapX;
|
||||
public float OverlapY;
|
||||
|
||||
public static TileKeyConfig Default => new TileKeyConfig
|
||||
{
|
||||
TileSizeX = DefaultTileSizeX,
|
||||
TileSizeY = DefaultTileSizeY,
|
||||
OverlapX = DefaultOverlapX,
|
||||
OverlapY = DefaultOverlapY
|
||||
};
|
||||
}
|
||||
|
||||
internal struct TileRecord
|
||||
{
|
||||
public string TileId;
|
||||
public string TileKey;
|
||||
public int XKey;
|
||||
public int YKey;
|
||||
public double Xmin;
|
||||
public double Ymin;
|
||||
public double GlobalMin;
|
||||
public double GlobalMax;
|
||||
}
|
||||
|
||||
internal struct BuildRequest
|
||||
{
|
||||
public string TileIndexCsvPath;
|
||||
public string TilePrefabsDir;
|
||||
public string OutputRoot;
|
||||
public BuildTarget Target;
|
||||
public BuildTargetGroup TargetGroup;
|
||||
public float TileSizeMeters;
|
||||
public TileKeyConfig TileKeyConfig;
|
||||
public List<TileRecord> SelectedTiles;
|
||||
public bool OverwriteExisting;
|
||||
public bool Verbose;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Geo Tiles/Build (Android)")]
|
||||
public static void BuildAndroid()
|
||||
{
|
||||
Debug.LogWarning("[GeoTileAddressablesBuilder] Legacy build. Use Tools/Geo Tiles/Addressables Build Window for selection builds.");
|
||||
BuildForTarget(BuildTarget.Android, BuildTargetGroup.Android);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Geo Tiles/Build (Linux)")]
|
||||
public static void BuildLinux()
|
||||
{
|
||||
Debug.LogWarning("[GeoTileAddressablesBuilder] Legacy build. Use Tools/Geo Tiles/Addressables Build Window for selection builds.");
|
||||
BuildForTarget(BuildTarget.StandaloneLinux64, BuildTargetGroup.Standalone);
|
||||
}
|
||||
|
||||
private static void BuildForTarget(BuildTarget target, BuildTargetGroup group)
|
||||
{
|
||||
if (!Directory.Exists(TilePrefabsDir))
|
||||
BuildSelectedTiles(new BuildRequest
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Prefab directory missing: {TilePrefabsDir}");
|
||||
return;
|
||||
TileIndexCsvPath = TileIndexCsvPath,
|
||||
TilePrefabsDir = TilePrefabsDir,
|
||||
OutputRoot = "ServerData/TileBundles",
|
||||
Target = target,
|
||||
TargetGroup = group,
|
||||
TileSizeMeters = DefaultTileSizeMeters,
|
||||
TileKeyConfig = TileKeyConfig.Default,
|
||||
SelectedTiles = null,
|
||||
OverwriteExisting = false,
|
||||
Verbose = false
|
||||
});
|
||||
}
|
||||
|
||||
internal static bool BuildSelectedTiles(BuildRequest request)
|
||||
{
|
||||
var tileIndexCsvPath = string.IsNullOrWhiteSpace(request.TileIndexCsvPath) ? TileIndexCsvPath : request.TileIndexCsvPath;
|
||||
var tilePrefabsDir = string.IsNullOrWhiteSpace(request.TilePrefabsDir) ? TilePrefabsDir : request.TilePrefabsDir;
|
||||
var outputRoot = string.IsNullOrWhiteSpace(request.OutputRoot) ? "ServerData/TileBundles" : request.OutputRoot;
|
||||
var tileSizeMeters = request.TileSizeMeters > 0f ? request.TileSizeMeters : DefaultTileSizeMeters;
|
||||
var tileKeyConfig = request.TileKeyConfig;
|
||||
if (tileKeyConfig.TileSizeX <= 0f && tileKeyConfig.TileSizeY <= 0f &&
|
||||
tileKeyConfig.OverlapX == 0f && tileKeyConfig.OverlapY == 0f)
|
||||
tileKeyConfig = TileKeyConfig.Default;
|
||||
if (tileKeyConfig.TileSizeX <= 0f) tileKeyConfig.TileSizeX = DefaultTileSizeX;
|
||||
if (tileKeyConfig.TileSizeY <= 0f) tileKeyConfig.TileSizeY = DefaultTileSizeY;
|
||||
|
||||
if (!Directory.Exists(tilePrefabsDir))
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Prefab directory missing: {tilePrefabsDir}");
|
||||
return false;
|
||||
}
|
||||
if (!File.Exists(TileIndexCsvPath))
|
||||
if (!File.Exists(tileIndexCsvPath))
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] CSV missing: {TileIndexCsvPath}");
|
||||
return;
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] CSV missing: {tileIndexCsvPath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var tiles = request.SelectedTiles ?? ParseTilesCsv(tileIndexCsvPath, tileKeyConfig);
|
||||
if (tiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesBuilder] No tiles selected for build.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
|
||||
if (settings == null)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesBuilder] Addressables settings not found.");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureProfileVariable(settings, BuildPathVariable, BuildPathValue);
|
||||
var buildPathValue = BuildPathFromOutputRoot(outputRoot);
|
||||
EnsureProfileVariable(settings, BuildPathVariable, buildPathValue);
|
||||
EnsureProfileVariable(settings, LoadPathVariable, LoadPathValue);
|
||||
EnsureRemoteCatalogPaths(settings);
|
||||
|
||||
var groupAsset = GetOrCreateGroup(settings);
|
||||
ConfigureGroup(settings, groupAsset);
|
||||
AssignPrefabs(settings, groupAsset);
|
||||
var assignedTileIds = AssignPrefabs(settings, groupAsset, tilePrefabsDir, tiles, true);
|
||||
|
||||
var filteredTiles = tiles
|
||||
.Where(tile => !string.IsNullOrWhiteSpace(tile.TileId) && assignedTileIds.Contains(tile.TileId))
|
||||
.ToList();
|
||||
if (filteredTiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesBuilder] No matching prefabs found for selected tiles.");
|
||||
return false;
|
||||
}
|
||||
|
||||
settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
var previousTarget = EditorUserBuildSettings.activeBuildTarget;
|
||||
if (previousTarget != target)
|
||||
if (previousTarget != request.Target)
|
||||
{
|
||||
if (!EditorUserBuildSettings.SwitchActiveBuildTarget(group, target))
|
||||
if (!EditorUserBuildSettings.SwitchActiveBuildTarget(request.TargetGroup, request.Target))
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Failed to switch build target to {target}.");
|
||||
return;
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Failed to switch build target to {request.Target}.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var outputPath = GetRemoteCatalogBuildPath(settings, request.Target);
|
||||
if (request.OverwriteExisting && Directory.Exists(outputPath))
|
||||
Directory.Delete(outputPath, true);
|
||||
|
||||
AddressableAssetSettings.BuildPlayerContent();
|
||||
|
||||
var outputPath = GetRemoteCatalogBuildPath(settings, target);
|
||||
outputPath = GetRemoteCatalogBuildPath(settings, request.Target);
|
||||
if (!Directory.Exists(outputPath))
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Build output not found: {outputPath}");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var (catalogFile, catalogHashFile) = FindCatalogFiles(outputPath);
|
||||
if (string.IsNullOrEmpty(catalogFile))
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesBuilder] Catalog file not found in: {outputPath}");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
var manifest = BuildManifest(target.ToString(), catalogFile, catalogHashFile);
|
||||
var manifest = BuildManifest(request.Target.ToString(), catalogFile, catalogHashFile, filteredTiles, tileSizeMeters);
|
||||
var manifestPath = Path.Combine(outputPath, ManifestFileName);
|
||||
File.WriteAllText(manifestPath, JsonUtility.ToJson(manifest, true));
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
Debug.Log($"[GeoTileAddressablesBuilder] DONE. Output={outputPath}");
|
||||
if (request.Verbose)
|
||||
Debug.Log($"[GeoTileAddressablesBuilder] Built {filteredTiles.Count} tiles (from {tiles.Count} selected). Output={outputPath}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void AssignPrefabs(AddressableAssetSettings settings, AddressableAssetGroup group)
|
||||
private static HashSet<string> AssignPrefabs(AddressableAssetSettings settings, AddressableAssetGroup group, string prefabsDir, List<TileRecord> tiles, bool removeUnselected)
|
||||
{
|
||||
var prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { TilePrefabsDir });
|
||||
var tileById = new Dictionary<string, TileRecord>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var tile in tiles)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tile.TileId))
|
||||
tileById[tile.TileId] = tile;
|
||||
}
|
||||
|
||||
var assignedTileIds = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var selectedGuids = new HashSet<string>();
|
||||
var prefabGuids = AssetDatabase.FindAssets("t:Prefab", new[] { prefabsDir });
|
||||
foreach (var guid in prefabGuids)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(guid).Replace("\\", "/");
|
||||
var parentDir = Path.GetDirectoryName(path)?.Replace("\\", "/");
|
||||
if (!string.Equals(parentDir, TilePrefabsDir, StringComparison.OrdinalIgnoreCase))
|
||||
if (!string.Equals(parentDir, prefabsDir, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
var tileId = Path.GetFileNameWithoutExtension(path);
|
||||
if (!tileById.TryGetValue(tileId, out var tile))
|
||||
continue;
|
||||
|
||||
var entry = settings.CreateOrMoveEntry(guid, group, false, false);
|
||||
entry.address = Path.GetFileNameWithoutExtension(path);
|
||||
entry.address = tile.TileKey;
|
||||
entry.SetLabel(TileLabel, true, true);
|
||||
selectedGuids.Add(guid);
|
||||
assignedTileIds.Add(tileId);
|
||||
}
|
||||
|
||||
if (!removeUnselected)
|
||||
return assignedTileIds;
|
||||
|
||||
var entries = group.entries.ToList();
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
if (entry == null || string.IsNullOrWhiteSpace(entry.AssetPath))
|
||||
continue;
|
||||
|
||||
var entryPath = entry.AssetPath.Replace("\\", "/");
|
||||
var entryDir = Path.GetDirectoryName(entryPath)?.Replace("\\", "/");
|
||||
if (!string.Equals(entryDir, prefabsDir, StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
if (!selectedGuids.Contains(entry.guid))
|
||||
group.RemoveAssetEntry(entry);
|
||||
}
|
||||
|
||||
return assignedTileIds;
|
||||
}
|
||||
|
||||
private static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings)
|
||||
@@ -182,11 +324,10 @@ public static class GeoTileAddressablesBuilder
|
||||
return (catalogFile, catalogHash);
|
||||
}
|
||||
|
||||
private static TileManifest BuildManifest(string buildTarget, string catalogFile, string catalogHashFile)
|
||||
private static TileManifest BuildManifest(string buildTarget, string catalogFile, string catalogHashFile, List<TileRecord> tiles, float tileSizeMeters)
|
||||
{
|
||||
var tiles = ParseTilesCsv();
|
||||
if (tiles.Count == 0)
|
||||
throw new InvalidOperationException("No tiles parsed from tile_index.csv");
|
||||
throw new InvalidOperationException("No tiles selected for TileManifest.");
|
||||
|
||||
double minX = double.PositiveInfinity;
|
||||
double minY = double.PositiveInfinity;
|
||||
@@ -203,6 +344,7 @@ public static class GeoTileAddressablesBuilder
|
||||
var tile = tiles[i];
|
||||
entries[i] = new TileEntry
|
||||
{
|
||||
tileKey = tile.TileKey,
|
||||
tileId = tile.TileId,
|
||||
offsetX = (float)(tile.Xmin - minX),
|
||||
offsetZ = (float)(tile.Ymin - minY),
|
||||
@@ -215,7 +357,7 @@ public static class GeoTileAddressablesBuilder
|
||||
buildTarget = buildTarget,
|
||||
catalogFile = catalogFile,
|
||||
catalogHashFile = catalogHashFile,
|
||||
tileSizeMeters = TileSizeMeters,
|
||||
tileSizeMeters = tileSizeMeters,
|
||||
tiles = entries
|
||||
};
|
||||
}
|
||||
@@ -229,20 +371,23 @@ public static class GeoTileAddressablesBuilder
|
||||
return Path.GetFullPath(rawPath.Replace("[BuildTarget]", target.ToString()));
|
||||
}
|
||||
|
||||
private struct TileRecord
|
||||
private static string BuildPathFromOutputRoot(string outputRoot)
|
||||
{
|
||||
public string TileId;
|
||||
public double Xmin;
|
||||
public double Ymin;
|
||||
public double GlobalMin;
|
||||
public double GlobalMax;
|
||||
if (string.IsNullOrWhiteSpace(outputRoot))
|
||||
return BuildPathValue;
|
||||
|
||||
var normalized = outputRoot.Replace("\\", "/").TrimEnd('/');
|
||||
if (normalized.IndexOf("[BuildTarget]", StringComparison.OrdinalIgnoreCase) >= 0)
|
||||
return normalized;
|
||||
|
||||
return $"{normalized}/[BuildTarget]";
|
||||
}
|
||||
|
||||
private static List<TileRecord> ParseTilesCsv()
|
||||
internal static List<TileRecord> ParseTilesCsv(string csvPath, TileKeyConfig tileKeyConfig)
|
||||
{
|
||||
var tiles = new List<TileRecord>();
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
var lines = File.ReadAllLines(TileIndexCsvPath);
|
||||
var lines = File.ReadAllLines(csvPath);
|
||||
|
||||
if (lines.Length < 2)
|
||||
return tiles;
|
||||
@@ -260,6 +405,7 @@ public static class GeoTileAddressablesBuilder
|
||||
int IDX_YMIN = headerMap["ymin"];
|
||||
int IDX_GMIN = headerMap["global_min"];
|
||||
int IDX_GMAX = headerMap["global_max"];
|
||||
int IDX_TILE_KEY = headerMap.TryGetValue("tile_key", out var idxTileKey) ? idxTileKey : -1;
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
@@ -269,16 +415,26 @@ public static class GeoTileAddressablesBuilder
|
||||
|
||||
var parts = line.Split(',');
|
||||
int maxIdx = Math.Max(IDX_TILE, Math.Max(IDX_XMIN, Math.Max(IDX_YMIN, Math.Max(IDX_GMIN, IDX_GMAX))));
|
||||
if (IDX_TILE_KEY >= 0)
|
||||
maxIdx = Math.Max(maxIdx, IDX_TILE_KEY);
|
||||
if (parts.Length <= maxIdx)
|
||||
continue;
|
||||
|
||||
try
|
||||
{
|
||||
var xmin = double.Parse(parts[IDX_XMIN], ci);
|
||||
var ymin = double.Parse(parts[IDX_YMIN], ci);
|
||||
var tileKeyRaw = IDX_TILE_KEY >= 0 ? parts[IDX_TILE_KEY].Trim() : "";
|
||||
var tileKey = ResolveTileKey(tileKeyRaw, xmin, ymin, tileKeyConfig, out var xKey, out var yKey);
|
||||
|
||||
tiles.Add(new TileRecord
|
||||
{
|
||||
TileId = parts[IDX_TILE].Trim(),
|
||||
Xmin = double.Parse(parts[IDX_XMIN], ci),
|
||||
Ymin = double.Parse(parts[IDX_YMIN], ci),
|
||||
TileKey = tileKey,
|
||||
XKey = xKey,
|
||||
YKey = yKey,
|
||||
Xmin = xmin,
|
||||
Ymin = ymin,
|
||||
GlobalMin = double.Parse(parts[IDX_GMIN], ci),
|
||||
GlobalMax = double.Parse(parts[IDX_GMAX], ci)
|
||||
});
|
||||
@@ -292,6 +448,33 @@ public static class GeoTileAddressablesBuilder
|
||||
return tiles;
|
||||
}
|
||||
|
||||
private static string ResolveTileKey(string tileKeyRaw, double xmin, double ymin, TileKeyConfig tileKeyConfig, out int xKey, out int yKey)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tileKeyRaw) && TryParseTileKey(tileKeyRaw, out xKey, out yKey))
|
||||
return tileKeyRaw;
|
||||
|
||||
float sizeX = tileKeyConfig.TileSizeX <= 0f ? 1f : tileKeyConfig.TileSizeX;
|
||||
float sizeY = tileKeyConfig.TileSizeY <= 0f ? 1f : tileKeyConfig.TileSizeY;
|
||||
xKey = (int)Math.Floor((xmin + tileKeyConfig.OverlapX) / sizeX);
|
||||
yKey = (int)Math.Floor((ymin + tileKeyConfig.OverlapY) / sizeY);
|
||||
return $"{xKey}_{yKey}";
|
||||
}
|
||||
|
||||
private static bool TryParseTileKey(string tileKey, out int xKey, out int yKey)
|
||||
{
|
||||
xKey = 0;
|
||||
yKey = 0;
|
||||
if (string.IsNullOrWhiteSpace(tileKey))
|
||||
return false;
|
||||
|
||||
var parts = tileKey.Split('_');
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
return int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out xKey)
|
||||
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out yKey);
|
||||
}
|
||||
|
||||
private static Dictionary<string, int> BuildHeaderMap(string headerLine)
|
||||
{
|
||||
var map = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
375
Assets/Scripts/Editor/GeoTileAddressablesWindow.cs
Normal file
375
Assets/Scripts/Editor/GeoTileAddressablesWindow.cs
Normal file
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public class GeoTileAddressablesWindow : EditorWindow
|
||||
{
|
||||
private string tileIndexCsvPath = "Assets/GeoData/tile_index.csv";
|
||||
private string tilePrefabsDir = "Assets/TilePrefabs";
|
||||
private string outputRoot = "ServerData/TileBundles";
|
||||
private BuildTarget buildTarget = BuildTarget.StandaloneLinux64;
|
||||
|
||||
private float tileSizeX = 1000f;
|
||||
private float tileSizeY = 1000f;
|
||||
private float overlapX = 0.5f;
|
||||
private float overlapY = 0.5f;
|
||||
|
||||
private string bottomLeftKey = "";
|
||||
private string topRightKey = "";
|
||||
|
||||
private bool overwriteExisting = true;
|
||||
private bool verboseLogging = false;
|
||||
|
||||
private Vector2 scrollPosition;
|
||||
|
||||
private enum BuildSource
|
||||
{
|
||||
Prefabs,
|
||||
SceneTerrains
|
||||
}
|
||||
|
||||
private BuildSource buildSource = BuildSource.Prefabs;
|
||||
|
||||
private readonly List<GeoTileAddressablesBuilder.TileRecord> tileRecords = new List<GeoTileAddressablesBuilder.TileRecord>();
|
||||
private readonly List<TileKeyOption> tileKeyOptions = new List<TileKeyOption>();
|
||||
private readonly Dictionary<string, TileKeyOption> tileKeyLookup = new Dictionary<string, TileKeyOption>(StringComparer.OrdinalIgnoreCase);
|
||||
private string[] tileKeyStrings = Array.Empty<string>();
|
||||
|
||||
private string cachedCsvPath = "";
|
||||
private DateTime cachedCsvWriteTimeUtc = DateTime.MinValue;
|
||||
private float cachedTileSizeX = 0f;
|
||||
private float cachedTileSizeY = 0f;
|
||||
private float cachedOverlapX = 0f;
|
||||
private float cachedOverlapY = 0f;
|
||||
|
||||
private struct TileKeyOption
|
||||
{
|
||||
public string Key;
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Geo Tiles/Addressables Build Window")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
var win = GetWindow<GeoTileAddressablesWindow>("Geo Tile Addressables Build");
|
||||
win.minSize = new Vector2(620, 420);
|
||||
}
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
GUILayout.Label("Geo Tile Addressables Build", EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.Space(8);
|
||||
GUILayout.Label("Paths", EditorStyles.boldLabel);
|
||||
tileIndexCsvPath = EditorGUILayout.TextField("Tile Index CSV", tileIndexCsvPath);
|
||||
tilePrefabsDir = EditorGUILayout.TextField("Tile Prefabs Dir", tilePrefabsDir);
|
||||
outputRoot = EditorGUILayout.TextField("Output Root", outputRoot);
|
||||
buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target", buildTarget);
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Tile Key Config", EditorStyles.boldLabel);
|
||||
tileSizeX = EditorGUILayout.FloatField("Tile Size X (m)", tileSizeX);
|
||||
tileSizeY = EditorGUILayout.FloatField("Tile Size Y (m)", tileSizeY);
|
||||
overlapX = EditorGUILayout.FloatField("Overlap X (m)", overlapX);
|
||||
overlapY = EditorGUILayout.FloatField("Overlap Y (m)", overlapY);
|
||||
|
||||
RefreshTileCache();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Tile Selection", EditorStyles.boldLabel);
|
||||
DrawTileSelection();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Build Options", EditorStyles.boldLabel);
|
||||
buildSource = (BuildSource)EditorGUILayout.EnumPopup("Source", buildSource);
|
||||
overwriteExisting = EditorGUILayout.ToggleLeft("Overwrite existing bundles", overwriteExisting);
|
||||
verboseLogging = EditorGUILayout.ToggleLeft("Verbose logging", verboseLogging);
|
||||
|
||||
if (buildSource == BuildSource.SceneTerrains)
|
||||
EditorGUILayout.HelpBox("Scene terrain source is not implemented yet.", MessageType.Info);
|
||||
|
||||
GUILayout.Space(12);
|
||||
if (GUILayout.Button("Build Addressables (Selected Tiles)"))
|
||||
BuildSelectedTiles();
|
||||
|
||||
if (GUILayout.Button("Open Output Folder"))
|
||||
OpenOutputFolder();
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void RefreshTileCache()
|
||||
{
|
||||
if (!File.Exists(tileIndexCsvPath))
|
||||
{
|
||||
tileRecords.Clear();
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
tileKeyStrings = Array.Empty<string>();
|
||||
return;
|
||||
}
|
||||
|
||||
var writeTime = File.GetLastWriteTimeUtc(tileIndexCsvPath);
|
||||
if (tileIndexCsvPath == cachedCsvPath &&
|
||||
writeTime == cachedCsvWriteTimeUtc &&
|
||||
Mathf.Approximately(tileSizeX, cachedTileSizeX) &&
|
||||
Mathf.Approximately(tileSizeY, cachedTileSizeY) &&
|
||||
Mathf.Approximately(overlapX, cachedOverlapX) &&
|
||||
Mathf.Approximately(overlapY, cachedOverlapY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var config = new GeoTileAddressablesBuilder.TileKeyConfig
|
||||
{
|
||||
TileSizeX = tileSizeX,
|
||||
TileSizeY = tileSizeY,
|
||||
OverlapX = overlapX,
|
||||
OverlapY = overlapY
|
||||
};
|
||||
|
||||
tileRecords.Clear();
|
||||
tileRecords.AddRange(GeoTileAddressablesBuilder.ParseTilesCsv(tileIndexCsvPath, config));
|
||||
BuildTileKeyOptions();
|
||||
|
||||
cachedCsvPath = tileIndexCsvPath;
|
||||
cachedCsvWriteTimeUtc = writeTime;
|
||||
cachedTileSizeX = tileSizeX;
|
||||
cachedTileSizeY = tileSizeY;
|
||||
cachedOverlapX = overlapX;
|
||||
cachedOverlapY = overlapY;
|
||||
}
|
||||
|
||||
private void BuildTileKeyOptions()
|
||||
{
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
|
||||
for (int i = 0; i < tileRecords.Count; i++)
|
||||
{
|
||||
var tile = tileRecords[i];
|
||||
if (string.IsNullOrWhiteSpace(tile.TileKey))
|
||||
continue;
|
||||
|
||||
if (tileKeyLookup.ContainsKey(tile.TileKey))
|
||||
continue;
|
||||
|
||||
var option = new TileKeyOption
|
||||
{
|
||||
Key = tile.TileKey,
|
||||
X = tile.XKey,
|
||||
Y = tile.YKey
|
||||
};
|
||||
|
||||
tileKeyOptions.Add(option);
|
||||
tileKeyLookup[tile.TileKey] = option;
|
||||
}
|
||||
|
||||
tileKeyOptions.Sort((a, b) =>
|
||||
{
|
||||
int cmp = a.X.CompareTo(b.X);
|
||||
return cmp != 0 ? cmp : a.Y.CompareTo(b.Y);
|
||||
});
|
||||
|
||||
tileKeyStrings = new string[tileKeyOptions.Count];
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
tileKeyStrings[i] = tileKeyOptions[i].Key;
|
||||
}
|
||||
|
||||
private void DrawTileSelection()
|
||||
{
|
||||
if (tileKeyOptions.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No tile keys available. Check the tile index CSV and key config.", MessageType.Info);
|
||||
EditorGUILayout.LabelField("Tiles in selection", "0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(bottomLeftKey) || !tileKeyLookup.ContainsKey(bottomLeftKey))
|
||||
bottomLeftKey = tileKeyOptions[0].Key;
|
||||
|
||||
var bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
var topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (string.IsNullOrEmpty(topRightKey) || !ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
|
||||
int bottomLeftIndex = Array.IndexOf(tileKeyStrings, bottomLeftKey);
|
||||
int newBottomLeftIndex = EditorGUILayout.Popup("Bottom-Left Key", bottomLeftIndex, tileKeyStrings);
|
||||
if (newBottomLeftIndex != bottomLeftIndex)
|
||||
{
|
||||
bottomLeftKey = tileKeyStrings[newBottomLeftIndex];
|
||||
bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (!ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
}
|
||||
|
||||
var topRightKeys = BuildKeyArray(topRightOptions);
|
||||
int topRightIndex = Array.IndexOf(topRightKeys, topRightKey);
|
||||
if (topRightIndex < 0)
|
||||
topRightIndex = topRightKeys.Length - 1;
|
||||
|
||||
int newTopRightIndex = EditorGUILayout.Popup("Top-Right Key", topRightIndex, topRightKeys);
|
||||
if (newTopRightIndex != topRightIndex)
|
||||
topRightKey = topRightKeys[newTopRightIndex];
|
||||
|
||||
EditorGUILayout.LabelField("Tiles in selection", CountSelectedTiles().ToString());
|
||||
}
|
||||
|
||||
private List<TileKeyOption> GetTopRightOptions(TileKeyOption bottomLeft)
|
||||
{
|
||||
var options = new List<TileKeyOption>();
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
{
|
||||
var option = tileKeyOptions[i];
|
||||
if (option.X >= bottomLeft.X && option.Y >= bottomLeft.Y)
|
||||
options.Add(option);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private int CountSelectedTiles()
|
||||
{
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < tileRecords.Count; i++)
|
||||
{
|
||||
var tile = tileRecords[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private List<GeoTileAddressablesBuilder.TileRecord> GetSelectedTiles()
|
||||
{
|
||||
var selected = new List<GeoTileAddressablesBuilder.TileRecord>();
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return selected;
|
||||
|
||||
for (int i = 0; i < tileRecords.Count; i++)
|
||||
{
|
||||
var tile = tileRecords[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
selected.Add(tile);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private void BuildSelectedTiles()
|
||||
{
|
||||
if (buildSource != BuildSource.Prefabs)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesWindow] Scene terrain source is not implemented.");
|
||||
return;
|
||||
}
|
||||
|
||||
RefreshTileCache();
|
||||
if (tileRecords.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesWindow] No tiles loaded from CSV.");
|
||||
return;
|
||||
}
|
||||
|
||||
var selectedTiles = GetSelectedTiles();
|
||||
if (selectedTiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileAddressablesWindow] Selection is empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
var request = new GeoTileAddressablesBuilder.BuildRequest
|
||||
{
|
||||
TileIndexCsvPath = tileIndexCsvPath,
|
||||
TilePrefabsDir = tilePrefabsDir,
|
||||
OutputRoot = outputRoot,
|
||||
Target = buildTarget,
|
||||
TargetGroup = GetBuildTargetGroup(buildTarget),
|
||||
TileSizeMeters = tileSizeX,
|
||||
TileKeyConfig = new GeoTileAddressablesBuilder.TileKeyConfig
|
||||
{
|
||||
TileSizeX = tileSizeX,
|
||||
TileSizeY = tileSizeY,
|
||||
OverlapX = overlapX,
|
||||
OverlapY = overlapY
|
||||
},
|
||||
SelectedTiles = selectedTiles,
|
||||
OverwriteExisting = overwriteExisting,
|
||||
Verbose = verboseLogging
|
||||
};
|
||||
|
||||
GeoTileAddressablesBuilder.BuildSelectedTiles(request);
|
||||
}
|
||||
|
||||
private void OpenOutputFolder()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(outputRoot))
|
||||
return;
|
||||
|
||||
var normalized = outputRoot.Replace("\\", "/").TrimEnd('/');
|
||||
var resolved = normalized.IndexOf("[BuildTarget]", StringComparison.OrdinalIgnoreCase) >= 0
|
||||
? normalized.Replace("[BuildTarget]", buildTarget.ToString())
|
||||
: $"{normalized}/{buildTarget}";
|
||||
|
||||
var fullPath = Path.GetFullPath(resolved);
|
||||
if (Directory.Exists(fullPath))
|
||||
EditorUtility.RevealInFinder(fullPath);
|
||||
else
|
||||
Debug.LogWarning($"[GeoTileAddressablesWindow] Output folder not found: {fullPath}");
|
||||
}
|
||||
|
||||
private static bool ContainsKey(List<TileKeyOption> options, string key)
|
||||
{
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (string.Equals(options[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string[] BuildKeyArray(List<TileKeyOption> options)
|
||||
{
|
||||
var keys = new string[options.Count];
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
keys[i] = options[i].Key;
|
||||
return keys;
|
||||
}
|
||||
|
||||
private static BuildTargetGroup GetBuildTargetGroup(BuildTarget target)
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case BuildTarget.Android:
|
||||
return BuildTargetGroup.Android;
|
||||
case BuildTarget.StandaloneLinux64:
|
||||
case BuildTarget.StandaloneWindows:
|
||||
case BuildTarget.StandaloneWindows64:
|
||||
case BuildTarget.StandaloneOSX:
|
||||
return BuildTargetGroup.Standalone;
|
||||
case BuildTarget.iOS:
|
||||
return BuildTargetGroup.iOS;
|
||||
case BuildTarget.WebGL:
|
||||
return BuildTargetGroup.WebGL;
|
||||
default:
|
||||
return BuildTargetGroup.Standalone;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Editor/GeoTileAddressablesWindow.cs.meta
Normal file
2
Assets/Scripts/Editor/GeoTileAddressablesWindow.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9927ed702ab46b49721a4695f84efb7
|
||||
@@ -43,6 +43,24 @@ public class GeoTileImporter : EditorWindow
|
||||
private bool deleteExistingEnhancedTrees = false;
|
||||
private string enhancedTreesParentName = "Geo_Trees_Enhanced";
|
||||
|
||||
private Vector2 scrollPosition;
|
||||
private float tileKeySizeX = 1000f;
|
||||
private float tileKeySizeY = 1000f;
|
||||
private float tileKeyOverlapX = 0.5f;
|
||||
private float tileKeyOverlapY = 0.5f;
|
||||
private string bottomLeftKey = "";
|
||||
private string topRightKey = "";
|
||||
private readonly List<TileRecord> tileIndexCache = new List<TileRecord>();
|
||||
private readonly List<TileKeyOption> tileKeyOptions = new List<TileKeyOption>();
|
||||
private readonly Dictionary<string, TileKeyOption> tileKeyLookup = new Dictionary<string, TileKeyOption>(StringComparer.OrdinalIgnoreCase);
|
||||
private string[] tileKeyStrings = Array.Empty<string>();
|
||||
private string cachedTileCsvPath = "";
|
||||
private DateTime cachedTileCsvTimeUtc = DateTime.MinValue;
|
||||
private float cachedTileKeySizeX = 0f;
|
||||
private float cachedTileKeySizeY = 0f;
|
||||
private float cachedTileOverlapX = 0f;
|
||||
private float cachedTileOverlapY = 0f;
|
||||
|
||||
// Prefabs for trees and furniture (assign in editor)
|
||||
private GameObject treePrefab;
|
||||
private GameObject lampPrefab;
|
||||
@@ -60,6 +78,8 @@ public class GeoTileImporter : EditorWindow
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
GUILayout.Label("Inputs", EditorStyles.boldLabel);
|
||||
tilesCsvPath = EditorGUILayout.TextField("tile_index.csv", tilesCsvPath);
|
||||
heightmapsDir = EditorGUILayout.TextField("height_png16 dir", heightmapsDir);
|
||||
@@ -110,6 +130,19 @@ public class GeoTileImporter : EditorWindow
|
||||
bollardPrefab = (GameObject)EditorGUILayout.ObjectField("Bollard Prefab", bollardPrefab, typeof(GameObject), false);
|
||||
defaultFurniturePrefab = (GameObject)EditorGUILayout.ObjectField("Default Furniture Prefab", defaultFurniturePrefab, typeof(GameObject), false);
|
||||
|
||||
GUILayout.Space(12);
|
||||
GUILayout.Label("Tile Key Config", EditorStyles.boldLabel);
|
||||
tileKeySizeX = EditorGUILayout.FloatField("Tile Size X (m)", tileKeySizeX);
|
||||
tileKeySizeY = EditorGUILayout.FloatField("Tile Size Y (m)", tileKeySizeY);
|
||||
tileKeyOverlapX = EditorGUILayout.FloatField("Overlap X (m)", tileKeyOverlapX);
|
||||
tileKeyOverlapY = EditorGUILayout.FloatField("Overlap Y (m)", tileKeyOverlapY);
|
||||
|
||||
RefreshTileIndexCache();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Tile Selection", EditorStyles.boldLabel);
|
||||
DrawTileSelectionUI();
|
||||
|
||||
GUILayout.Space(12);
|
||||
if (GUILayout.Button("Import / Rebuild"))
|
||||
ImportTiles();
|
||||
@@ -119,6 +152,8 @@ public class GeoTileImporter : EditorWindow
|
||||
"Absolute elevation mapping: Terrain Y = global_min, Terrain height = (global_max - global_min).\n" +
|
||||
"CSV is header-driven (order-independent). Optionally applies ortho JPGs and instantiates buildings/trees GLBs.",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private static void EnsureHeightmapImportSettings(string assetPath)
|
||||
@@ -258,6 +293,8 @@ public class GeoTileImporter : EditorWindow
|
||||
applyOrthoTextures = false;
|
||||
}
|
||||
|
||||
RefreshTileIndexCache();
|
||||
|
||||
var parent = GameObject.Find(parentName);
|
||||
if (parent == null) parent = new GameObject(parentName);
|
||||
|
||||
@@ -267,118 +304,51 @@ public class GeoTileImporter : EditorWindow
|
||||
DestroyImmediate(parent.transform.GetChild(i).gameObject);
|
||||
}
|
||||
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
var lines = File.ReadAllLines(tilesCsvPath);
|
||||
|
||||
Debug.Log($"[GeoTileImporter] Read {lines.Length} lines.");
|
||||
if (lines.Length < 2)
|
||||
var tiles = ParseTilesCsv();
|
||||
if (tiles == null || tiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileImporter] CSV has no data rows (need header + at least 1 row).");
|
||||
Debug.LogError("[GeoTileImporter] No valid tiles found in CSV.");
|
||||
return;
|
||||
}
|
||||
|
||||
var headerLine = lines[0].Trim();
|
||||
var headerMap = BuildHeaderMap(headerLine);
|
||||
Debug.Log($"[GeoTileImporter] Header: {headerLine}");
|
||||
Debug.Log($"[GeoTileImporter] Header columns mapped: {string.Join(", ", headerMap.Keys)}");
|
||||
|
||||
// Required columns (order-independent)
|
||||
string[] required = { "tile_id", "xmin", "ymin", "global_min", "global_max", "out_res" };
|
||||
if (!HasAll(headerMap, required))
|
||||
var selectedTiles = ApplySelection(tiles);
|
||||
if (selectedTiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTileImporter] CSV missing required columns. Required: " +
|
||||
string.Join(", ", required) +
|
||||
"\nFound: " + string.Join(", ", headerMap.Keys));
|
||||
Debug.LogError("[GeoTileImporter] Selection is empty. Adjust Tile Selection and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
int IDX_TILE = headerMap["tile_id"];
|
||||
int IDX_XMIN = headerMap["xmin"];
|
||||
int IDX_YMIN = headerMap["ymin"];
|
||||
int IDX_GMIN = headerMap["global_min"];
|
||||
int IDX_GMAX = headerMap["global_max"];
|
||||
int IDX_RES = headerMap["out_res"];
|
||||
|
||||
// Compute origin from min xmin/ymin
|
||||
double originX = double.PositiveInfinity;
|
||||
double originY = double.PositiveInfinity;
|
||||
|
||||
int validRowsForOrigin = 0;
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
for (int i = 0; i < selectedTiles.Count; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var parts = line.Split(',');
|
||||
// Robust: just ensure indices exist in this row
|
||||
int needMaxIndex = Math.Max(Math.Max(Math.Max(Math.Max(IDX_TILE, IDX_XMIN), IDX_YMIN), IDX_GMIN), Math.Max(IDX_GMAX, IDX_RES));
|
||||
if (parts.Length <= needMaxIndex)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Origin scan: skipping line {i + 1} (too few columns: {parts.Length}). Line: '{line}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
double xmin = double.Parse(parts[IDX_XMIN], ci);
|
||||
double ymin = double.Parse(parts[IDX_YMIN], ci);
|
||||
originX = Math.Min(originX, xmin);
|
||||
originY = Math.Min(originY, ymin);
|
||||
validRowsForOrigin++;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Origin scan parse failed line {i + 1}: '{line}'\n{e.Message}");
|
||||
}
|
||||
originX = Math.Min(originX, selectedTiles[i].Xmin);
|
||||
originY = Math.Min(originY, selectedTiles[i].Ymin);
|
||||
}
|
||||
|
||||
if (validRowsForOrigin == 0 || double.IsInfinity(originX) || double.IsInfinity(originY))
|
||||
if (double.IsInfinity(originX) || double.IsInfinity(originY))
|
||||
{
|
||||
Debug.LogError("[GeoTileImporter] Could not compute origin (no valid rows parsed). Check CSV numeric format.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTileImporter] Origin: ({originX}, {originY}) from {validRowsForOrigin} valid rows.");
|
||||
Debug.Log($"[GeoTileImporter] Origin: ({originX}, {originY}) from {selectedTiles.Count} selected tiles.");
|
||||
|
||||
int imported = 0, skipped = 0;
|
||||
int importedTextures = 0;
|
||||
var placements = new List<(string tileId, float ux, float uz, float gmin)>();
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
for (int i = 0; i < selectedTiles.Count; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
var tile = selectedTiles[i];
|
||||
var tileId = tile.TileId;
|
||||
double xmin = tile.Xmin;
|
||||
double ymin = tile.Ymin;
|
||||
double gmin = tile.GlobalMin;
|
||||
double gmax = tile.GlobalMax;
|
||||
|
||||
var parts = line.Split(',');
|
||||
int needMaxIndex = Math.Max(Math.Max(Math.Max(Math.Max(IDX_TILE, IDX_XMIN), IDX_YMIN), IDX_GMIN), Math.Max(IDX_GMAX, IDX_RES));
|
||||
if (parts.Length <= needMaxIndex)
|
||||
{
|
||||
skipped++;
|
||||
Debug.LogWarning($"[GeoTileImporter] Skipping line {i + 1} (too few columns: {parts.Length}). Line: '{line}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
string tileId = parts[IDX_TILE].Trim();
|
||||
|
||||
double xmin, ymin, gmin, gmax;
|
||||
int outRes;
|
||||
try
|
||||
{
|
||||
xmin = double.Parse(parts[IDX_XMIN], ci);
|
||||
ymin = double.Parse(parts[IDX_YMIN], ci);
|
||||
gmin = double.Parse(parts[IDX_GMIN], ci);
|
||||
gmax = double.Parse(parts[IDX_GMAX], ci);
|
||||
outRes = int.Parse(parts[IDX_RES], ci);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
skipped++;
|
||||
Debug.LogWarning($"[GeoTileImporter] Parse failed line {i + 1} tile '{tileId}': {e.Message}\nLine: '{line}'");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (outRes != heightmapResolution)
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: out_res={outRes} but importer expects {heightmapResolution}.");
|
||||
if (tile.OutRes != heightmapResolution)
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: out_res={tile.OutRes} but importer expects {heightmapResolution}.");
|
||||
|
||||
float heightRange = (float)(gmax - gmin);
|
||||
if (heightRange <= 0.0001f)
|
||||
@@ -493,7 +463,7 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTileImporter] Imported {tileId} @ XZ=({ux},{uz}) Y={gmin} heightRange={heightRange} usedU16={usedU16}");
|
||||
Debug.Log($"[GeoTileImporter] Imported {tileId} ({tile.TileKey}) @ XZ=({ux},{uz}) Y={gmin} heightRange={heightRange} usedU16={usedU16}");
|
||||
imported++;
|
||||
placements.Add((tileId, ux, uz, (float)gmin));
|
||||
}
|
||||
@@ -509,6 +479,313 @@ public class GeoTileImporter : EditorWindow
|
||||
ImportEnhancedTrees(placements);
|
||||
}
|
||||
|
||||
private void RefreshTileIndexCache()
|
||||
{
|
||||
if (!File.Exists(tilesCsvPath))
|
||||
{
|
||||
tileIndexCache.Clear();
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
tileKeyStrings = Array.Empty<string>();
|
||||
return;
|
||||
}
|
||||
|
||||
var writeTime = File.GetLastWriteTimeUtc(tilesCsvPath);
|
||||
if (tilesCsvPath == cachedTileCsvPath &&
|
||||
writeTime == cachedTileCsvTimeUtc &&
|
||||
Mathf.Approximately(tileKeySizeX, cachedTileKeySizeX) &&
|
||||
Mathf.Approximately(tileKeySizeY, cachedTileKeySizeY) &&
|
||||
Mathf.Approximately(tileKeyOverlapX, cachedTileOverlapX) &&
|
||||
Mathf.Approximately(tileKeyOverlapY, cachedTileOverlapY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tileIndexCache.Clear();
|
||||
tileIndexCache.AddRange(ParseTilesCsv());
|
||||
BuildTileKeyOptions();
|
||||
|
||||
cachedTileCsvPath = tilesCsvPath;
|
||||
cachedTileCsvTimeUtc = writeTime;
|
||||
cachedTileKeySizeX = tileKeySizeX;
|
||||
cachedTileKeySizeY = tileKeySizeY;
|
||||
cachedTileOverlapX = tileKeyOverlapX;
|
||||
cachedTileOverlapY = tileKeyOverlapY;
|
||||
}
|
||||
|
||||
private void BuildTileKeyOptions()
|
||||
{
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
|
||||
for (int i = 0; i < tileIndexCache.Count; i++)
|
||||
{
|
||||
var tile = tileIndexCache[i];
|
||||
if (string.IsNullOrWhiteSpace(tile.TileKey))
|
||||
continue;
|
||||
|
||||
if (tileKeyLookup.ContainsKey(tile.TileKey))
|
||||
continue;
|
||||
|
||||
var option = new TileKeyOption
|
||||
{
|
||||
Key = tile.TileKey,
|
||||
X = tile.XKey,
|
||||
Y = tile.YKey
|
||||
};
|
||||
|
||||
tileKeyOptions.Add(option);
|
||||
tileKeyLookup[tile.TileKey] = option;
|
||||
}
|
||||
|
||||
tileKeyOptions.Sort((a, b) =>
|
||||
{
|
||||
int cmp = a.X.CompareTo(b.X);
|
||||
return cmp != 0 ? cmp : a.Y.CompareTo(b.Y);
|
||||
});
|
||||
|
||||
tileKeyStrings = new string[tileKeyOptions.Count];
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
tileKeyStrings[i] = tileKeyOptions[i].Key;
|
||||
}
|
||||
|
||||
private void DrawTileSelectionUI()
|
||||
{
|
||||
if (tileKeyOptions.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No tile keys available. Check the tile index CSV and key config.", MessageType.Info);
|
||||
EditorGUILayout.LabelField("Tiles in selection", "0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(bottomLeftKey) || !tileKeyLookup.ContainsKey(bottomLeftKey))
|
||||
bottomLeftKey = tileKeyOptions[0].Key;
|
||||
|
||||
var bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
var topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (string.IsNullOrEmpty(topRightKey) || !ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
|
||||
int bottomLeftIndex = Array.IndexOf(tileKeyStrings, bottomLeftKey);
|
||||
int newBottomLeftIndex = EditorGUILayout.Popup("Bottom-Left Key", bottomLeftIndex, tileKeyStrings);
|
||||
if (newBottomLeftIndex != bottomLeftIndex)
|
||||
{
|
||||
bottomLeftKey = tileKeyStrings[newBottomLeftIndex];
|
||||
bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (!ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
}
|
||||
|
||||
var topRightKeys = BuildKeyArray(topRightOptions);
|
||||
int topRightIndex = Array.IndexOf(topRightKeys, topRightKey);
|
||||
if (topRightIndex < 0)
|
||||
topRightIndex = topRightKeys.Length - 1;
|
||||
|
||||
int newTopRightIndex = EditorGUILayout.Popup("Top-Right Key", topRightIndex, topRightKeys);
|
||||
if (newTopRightIndex != topRightIndex)
|
||||
topRightKey = topRightKeys[newTopRightIndex];
|
||||
|
||||
EditorGUILayout.LabelField("Tiles in selection", CountSelectedTiles().ToString());
|
||||
}
|
||||
|
||||
private List<TileKeyOption> GetTopRightOptions(TileKeyOption bottomLeft)
|
||||
{
|
||||
var options = new List<TileKeyOption>();
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
{
|
||||
var option = tileKeyOptions[i];
|
||||
if (option.X >= bottomLeft.X && option.Y >= bottomLeft.Y)
|
||||
options.Add(option);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private int CountSelectedTiles()
|
||||
{
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < tileIndexCache.Count; i++)
|
||||
{
|
||||
var tile = tileIndexCache[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private List<TileRecord> ApplySelection(List<TileRecord> tiles)
|
||||
{
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return tiles;
|
||||
|
||||
var selected = new List<TileRecord>();
|
||||
for (int i = 0; i < tiles.Count; i++)
|
||||
{
|
||||
var tile = tiles[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
selected.Add(tile);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private List<TileRecord> ParseTilesCsv()
|
||||
{
|
||||
var tiles = new List<TileRecord>();
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
var lines = File.ReadAllLines(tilesCsvPath);
|
||||
|
||||
if (lines.Length < 2)
|
||||
{
|
||||
Debug.LogError("[GeoTileImporter] CSV has no data rows (need header + at least 1 row).");
|
||||
return tiles;
|
||||
}
|
||||
|
||||
var headerLine = lines[0].Trim();
|
||||
var headerMap = BuildHeaderMap(headerLine);
|
||||
Debug.Log($"[GeoTileImporter] Header columns mapped: {string.Join(", ", headerMap.Keys)}");
|
||||
|
||||
string[] required = { "tile_id", "xmin", "ymin", "global_min", "global_max", "out_res" };
|
||||
if (!HasAll(headerMap, required))
|
||||
{
|
||||
Debug.LogError("[GeoTileImporter] CSV missing required columns. Required: " +
|
||||
string.Join(", ", required) +
|
||||
"\nFound: " + string.Join(", ", headerMap.Keys));
|
||||
return tiles;
|
||||
}
|
||||
|
||||
int IDX_TILE = headerMap["tile_id"];
|
||||
int IDX_XMIN = headerMap["xmin"];
|
||||
int IDX_YMIN = headerMap["ymin"];
|
||||
int IDX_GMIN = headerMap["global_min"];
|
||||
int IDX_GMAX = headerMap["global_max"];
|
||||
int IDX_RES = headerMap["out_res"];
|
||||
int IDX_TILE_KEY = headerMap.ContainsKey("tile_key") ? headerMap["tile_key"] : -1;
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
var line = lines[i].Trim();
|
||||
if (string.IsNullOrWhiteSpace(line))
|
||||
continue;
|
||||
|
||||
var parts = line.Split(',');
|
||||
int needMaxIndex = Math.Max(Math.Max(Math.Max(Math.Max(IDX_TILE, IDX_XMIN), IDX_YMIN), IDX_GMIN), Math.Max(IDX_GMAX, IDX_RES));
|
||||
if (IDX_TILE_KEY >= 0)
|
||||
needMaxIndex = Math.Max(needMaxIndex, IDX_TILE_KEY);
|
||||
|
||||
if (parts.Length <= needMaxIndex)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Skipping line {i + 1} (too few columns: {parts.Length}).");
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var xmin = double.Parse(parts[IDX_XMIN], ci);
|
||||
var ymin = double.Parse(parts[IDX_YMIN], ci);
|
||||
var tileKeyRaw = IDX_TILE_KEY >= 0 ? parts[IDX_TILE_KEY].Trim() : "";
|
||||
var tileKey = ResolveTileKey(tileKeyRaw, xmin, ymin, out var xKey, out var yKey);
|
||||
|
||||
tiles.Add(new TileRecord
|
||||
{
|
||||
TileId = parts[IDX_TILE].Trim(),
|
||||
TileKey = tileKey,
|
||||
XKey = xKey,
|
||||
YKey = yKey,
|
||||
Xmin = xmin,
|
||||
Ymin = ymin,
|
||||
GlobalMin = double.Parse(parts[IDX_GMIN], ci),
|
||||
GlobalMax = double.Parse(parts[IDX_GMAX], ci),
|
||||
OutRes = int.Parse(parts[IDX_RES], ci)
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Parse error line {i + 1}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
private string ResolveTileKey(string tileKeyRaw, double xmin, double ymin, out int xKey, out int yKey)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tileKeyRaw) && TryParseTileKey(tileKeyRaw, out xKey, out yKey))
|
||||
return tileKeyRaw;
|
||||
|
||||
float sizeX = tileKeySizeX <= 0f ? 1f : tileKeySizeX;
|
||||
float sizeY = tileKeySizeY <= 0f ? 1f : tileKeySizeY;
|
||||
xKey = (int)Math.Floor((xmin + tileKeyOverlapX) / sizeX);
|
||||
yKey = (int)Math.Floor((ymin + tileKeyOverlapY) / sizeY);
|
||||
return $"{xKey}_{yKey}";
|
||||
}
|
||||
|
||||
private static bool TryParseTileKey(string tileKey, out int xKey, out int yKey)
|
||||
{
|
||||
xKey = 0;
|
||||
yKey = 0;
|
||||
if (string.IsNullOrWhiteSpace(tileKey))
|
||||
return false;
|
||||
|
||||
var parts = tileKey.Split('_');
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
return int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out xKey)
|
||||
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out yKey);
|
||||
}
|
||||
|
||||
private static bool ContainsKey(List<TileKeyOption> options, string key)
|
||||
{
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (string.Equals(options[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string[] BuildKeyArray(List<TileKeyOption> options)
|
||||
{
|
||||
var keys = new string[options.Count];
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
keys[i] = options[i].Key;
|
||||
return keys;
|
||||
}
|
||||
|
||||
private struct TileRecord
|
||||
{
|
||||
public string TileId;
|
||||
public string TileKey;
|
||||
public int XKey;
|
||||
public int YKey;
|
||||
public double Xmin;
|
||||
public double Ymin;
|
||||
public double GlobalMin;
|
||||
public double GlobalMax;
|
||||
public int OutRes;
|
||||
}
|
||||
|
||||
private struct TileKeyOption
|
||||
{
|
||||
public string Key;
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
|
||||
private void ImportBuildings(List<(string tileId, float ux, float uz, float gmin)> placements)
|
||||
{
|
||||
if (!importBuildings)
|
||||
|
||||
@@ -35,6 +35,24 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
private bool includeFurniture = false;
|
||||
private bool includeEnhancedTrees = false;
|
||||
|
||||
private Vector2 scrollPosition;
|
||||
private float tileKeySizeX = 1000f;
|
||||
private float tileKeySizeY = 1000f;
|
||||
private float tileKeyOverlapX = 0.5f;
|
||||
private float tileKeyOverlapY = 0.5f;
|
||||
private string bottomLeftKey = "";
|
||||
private string topRightKey = "";
|
||||
private readonly List<TileMetadata> tileIndexCache = new List<TileMetadata>();
|
||||
private readonly List<TileKeyOption> tileKeyOptions = new List<TileKeyOption>();
|
||||
private readonly Dictionary<string, TileKeyOption> tileKeyLookup = new Dictionary<string, TileKeyOption>(StringComparer.OrdinalIgnoreCase);
|
||||
private string[] tileKeyStrings = Array.Empty<string>();
|
||||
private string cachedTileCsvPath = "";
|
||||
private DateTime cachedTileCsvTimeUtc = DateTime.MinValue;
|
||||
private float cachedTileKeySizeX = 0f;
|
||||
private float cachedTileKeySizeY = 0f;
|
||||
private float cachedTileOverlapX = 0f;
|
||||
private float cachedTileOverlapY = 0f;
|
||||
|
||||
// Prefabs for furniture (optional)
|
||||
private GameObject lampPrefab;
|
||||
private GameObject benchPrefab;
|
||||
@@ -45,7 +63,10 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
|
||||
private struct TileMetadata
|
||||
{
|
||||
public string TileKey;
|
||||
public string TileId;
|
||||
public int XKey;
|
||||
public int YKey;
|
||||
public double Xmin, Ymin, Xmax, Ymax;
|
||||
public double GlobalMin, GlobalMax;
|
||||
public int OutRes;
|
||||
@@ -60,6 +81,8 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
|
||||
private void OnGUI()
|
||||
{
|
||||
scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);
|
||||
|
||||
GUILayout.Label("Input Paths", EditorStyles.boldLabel);
|
||||
tilesCsvPath = EditorGUILayout.TextField("tile_index.csv", tilesCsvPath);
|
||||
heightmapsDir = EditorGUILayout.TextField("height_png16 dir", heightmapsDir);
|
||||
@@ -96,6 +119,19 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
bollardPrefab = (GameObject)EditorGUILayout.ObjectField("Bollard Prefab", bollardPrefab, typeof(GameObject), false);
|
||||
defaultFurniturePrefab = (GameObject)EditorGUILayout.ObjectField("Default Furniture", defaultFurniturePrefab, typeof(GameObject), false);
|
||||
|
||||
GUILayout.Space(12);
|
||||
GUILayout.Label("Tile Key Config", EditorStyles.boldLabel);
|
||||
tileKeySizeX = EditorGUILayout.FloatField("Tile Size X (m)", tileKeySizeX);
|
||||
tileKeySizeY = EditorGUILayout.FloatField("Tile Size Y (m)", tileKeySizeY);
|
||||
tileKeyOverlapX = EditorGUILayout.FloatField("Overlap X (m)", tileKeyOverlapX);
|
||||
tileKeyOverlapY = EditorGUILayout.FloatField("Overlap Y (m)", tileKeyOverlapY);
|
||||
|
||||
RefreshTileIndexCache();
|
||||
|
||||
GUILayout.Space(10);
|
||||
GUILayout.Label("Tile Selection", EditorStyles.boldLabel);
|
||||
DrawTileSelectionUI();
|
||||
|
||||
GUILayout.Space(15);
|
||||
if (GUILayout.Button("Generate Prefabs"))
|
||||
ImportTilesAsPrefabs();
|
||||
@@ -110,6 +146,8 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
"TerrainData and TerrainLayers are saved as separate .asset files.\n" +
|
||||
"IMPORTANT: Use 'Place All Prefabs in Scene' to position tiles correctly.",
|
||||
MessageType.Info);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
private void PlaceAllPrefabsInScene()
|
||||
@@ -211,6 +249,7 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
EnsureDirectoryExists($"{prefabOutputDir}/TerrainLayers");
|
||||
|
||||
// Parse CSV
|
||||
RefreshTileIndexCache();
|
||||
var tiles = ParseTilesCsv();
|
||||
if (tiles == null || tiles.Count == 0)
|
||||
{
|
||||
@@ -218,17 +257,24 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTilePrefabImporter] Found {tiles.Count} tiles to process.");
|
||||
var selectedTiles = ApplySelection(tiles);
|
||||
if (selectedTiles.Count == 0)
|
||||
{
|
||||
Debug.LogError("[GeoTilePrefabImporter] Selection is empty. Adjust Tile Selection and try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTilePrefabImporter] Found {selectedTiles.Count} tiles to process.");
|
||||
|
||||
int created = 0, skipped = 0, failed = 0;
|
||||
|
||||
for (int i = 0; i < tiles.Count; i++)
|
||||
for (int i = 0; i < selectedTiles.Count; i++)
|
||||
{
|
||||
var tile = tiles[i];
|
||||
var tile = selectedTiles[i];
|
||||
EditorUtility.DisplayProgressBar(
|
||||
"Creating Tile Prefabs",
|
||||
$"Processing {tile.TileId} ({i + 1}/{tiles.Count})",
|
||||
(float)i / tiles.Count);
|
||||
$"Processing {tile.TileId} ({i + 1}/{selectedTiles.Count})",
|
||||
(float)i / selectedTiles.Count);
|
||||
|
||||
string prefabPath = $"{prefabOutputDir}/{tile.TileId}.prefab";
|
||||
if (File.Exists(prefabPath) && !overwriteExisting)
|
||||
@@ -259,6 +305,168 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
Debug.Log($"[GeoTilePrefabImporter] DONE. Created={created}, Skipped={skipped}, Failed={failed}");
|
||||
}
|
||||
|
||||
private void RefreshTileIndexCache()
|
||||
{
|
||||
if (!File.Exists(tilesCsvPath))
|
||||
{
|
||||
tileIndexCache.Clear();
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
tileKeyStrings = Array.Empty<string>();
|
||||
return;
|
||||
}
|
||||
|
||||
var writeTime = File.GetLastWriteTimeUtc(tilesCsvPath);
|
||||
if (tilesCsvPath == cachedTileCsvPath &&
|
||||
writeTime == cachedTileCsvTimeUtc &&
|
||||
Mathf.Approximately(tileKeySizeX, cachedTileKeySizeX) &&
|
||||
Mathf.Approximately(tileKeySizeY, cachedTileKeySizeY) &&
|
||||
Mathf.Approximately(tileKeyOverlapX, cachedTileOverlapX) &&
|
||||
Mathf.Approximately(tileKeyOverlapY, cachedTileOverlapY))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tileIndexCache.Clear();
|
||||
tileIndexCache.AddRange(ParseTilesCsv());
|
||||
BuildTileKeyOptions();
|
||||
|
||||
cachedTileCsvPath = tilesCsvPath;
|
||||
cachedTileCsvTimeUtc = writeTime;
|
||||
cachedTileKeySizeX = tileKeySizeX;
|
||||
cachedTileKeySizeY = tileKeySizeY;
|
||||
cachedTileOverlapX = tileKeyOverlapX;
|
||||
cachedTileOverlapY = tileKeyOverlapY;
|
||||
}
|
||||
|
||||
private void BuildTileKeyOptions()
|
||||
{
|
||||
tileKeyOptions.Clear();
|
||||
tileKeyLookup.Clear();
|
||||
|
||||
for (int i = 0; i < tileIndexCache.Count; i++)
|
||||
{
|
||||
var tile = tileIndexCache[i];
|
||||
if (string.IsNullOrWhiteSpace(tile.TileKey))
|
||||
continue;
|
||||
|
||||
if (tileKeyLookup.ContainsKey(tile.TileKey))
|
||||
continue;
|
||||
|
||||
var option = new TileKeyOption
|
||||
{
|
||||
Key = tile.TileKey,
|
||||
X = tile.XKey,
|
||||
Y = tile.YKey
|
||||
};
|
||||
|
||||
tileKeyOptions.Add(option);
|
||||
tileKeyLookup[tile.TileKey] = option;
|
||||
}
|
||||
|
||||
tileKeyOptions.Sort((a, b) =>
|
||||
{
|
||||
int cmp = a.X.CompareTo(b.X);
|
||||
return cmp != 0 ? cmp : a.Y.CompareTo(b.Y);
|
||||
});
|
||||
|
||||
tileKeyStrings = new string[tileKeyOptions.Count];
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
tileKeyStrings[i] = tileKeyOptions[i].Key;
|
||||
}
|
||||
|
||||
private void DrawTileSelectionUI()
|
||||
{
|
||||
if (tileKeyOptions.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No tile keys available. Check the tile index CSV and key config.", MessageType.Info);
|
||||
EditorGUILayout.LabelField("Tiles in selection", "0");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(bottomLeftKey) || !tileKeyLookup.ContainsKey(bottomLeftKey))
|
||||
bottomLeftKey = tileKeyOptions[0].Key;
|
||||
|
||||
var bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
var topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (string.IsNullOrEmpty(topRightKey) || !ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
|
||||
int bottomLeftIndex = Array.IndexOf(tileKeyStrings, bottomLeftKey);
|
||||
int newBottomLeftIndex = EditorGUILayout.Popup("Bottom-Left Key", bottomLeftIndex, tileKeyStrings);
|
||||
if (newBottomLeftIndex != bottomLeftIndex)
|
||||
{
|
||||
bottomLeftKey = tileKeyStrings[newBottomLeftIndex];
|
||||
bottomLeft = tileKeyLookup[bottomLeftKey];
|
||||
topRightOptions = GetTopRightOptions(bottomLeft);
|
||||
if (topRightOptions.Count == 0)
|
||||
topRightOptions.Add(bottomLeft);
|
||||
|
||||
if (!ContainsKey(topRightOptions, topRightKey))
|
||||
topRightKey = topRightOptions[topRightOptions.Count - 1].Key;
|
||||
}
|
||||
|
||||
var topRightKeys = BuildKeyArray(topRightOptions);
|
||||
int topRightIndex = Array.IndexOf(topRightKeys, topRightKey);
|
||||
if (topRightIndex < 0)
|
||||
topRightIndex = topRightKeys.Length - 1;
|
||||
|
||||
int newTopRightIndex = EditorGUILayout.Popup("Top-Right Key", topRightIndex, topRightKeys);
|
||||
if (newTopRightIndex != topRightIndex)
|
||||
topRightKey = topRightKeys[newTopRightIndex];
|
||||
|
||||
EditorGUILayout.LabelField("Tiles in selection", CountSelectedTiles().ToString());
|
||||
}
|
||||
|
||||
private List<TileKeyOption> GetTopRightOptions(TileKeyOption bottomLeft)
|
||||
{
|
||||
var options = new List<TileKeyOption>();
|
||||
for (int i = 0; i < tileKeyOptions.Count; i++)
|
||||
{
|
||||
var option = tileKeyOptions[i];
|
||||
if (option.X >= bottomLeft.X && option.Y >= bottomLeft.Y)
|
||||
options.Add(option);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
private int CountSelectedTiles()
|
||||
{
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return 0;
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < tileIndexCache.Count; i++)
|
||||
{
|
||||
var tile = tileIndexCache[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private List<TileMetadata> ApplySelection(List<TileMetadata> tiles)
|
||||
{
|
||||
if (!tileKeyLookup.TryGetValue(bottomLeftKey, out var bottomLeft) ||
|
||||
!tileKeyLookup.TryGetValue(topRightKey, out var topRight))
|
||||
return tiles;
|
||||
|
||||
var selected = new List<TileMetadata>();
|
||||
for (int i = 0; i < tiles.Count; i++)
|
||||
{
|
||||
var tile = tiles[i];
|
||||
if (tile.XKey >= bottomLeft.X && tile.XKey <= topRight.X &&
|
||||
tile.YKey >= bottomLeft.Y && tile.YKey <= topRight.Y)
|
||||
selected.Add(tile);
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private List<TileMetadata> ParseTilesCsv()
|
||||
{
|
||||
var tiles = new List<TileMetadata>();
|
||||
@@ -288,6 +496,7 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
int IDX_GMIN = headerMap["global_min"];
|
||||
int IDX_GMAX = headerMap["global_max"];
|
||||
int IDX_RES = headerMap["out_res"];
|
||||
int IDX_TILE_KEY = headerMap.ContainsKey("tile_key") ? headerMap["tile_key"] : -1;
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
@@ -295,7 +504,11 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var parts = line.Split(',');
|
||||
int maxIdx = Math.Max(Math.Max(Math.Max(IDX_TILE, IDX_XMAX), IDX_YMAX), Math.Max(IDX_GMAX, IDX_RES));
|
||||
int maxIdx = Math.Max(
|
||||
Math.Max(Math.Max(IDX_TILE, IDX_XMIN), Math.Max(IDX_YMIN, IDX_XMAX)),
|
||||
Math.Max(Math.Max(IDX_YMAX, IDX_GMIN), Math.Max(IDX_GMAX, IDX_RES)));
|
||||
if (IDX_TILE_KEY >= 0)
|
||||
maxIdx = Math.Max(maxIdx, IDX_TILE_KEY);
|
||||
if (parts.Length <= maxIdx)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTilePrefabImporter] Skipping line {i + 1}: too few columns.");
|
||||
@@ -304,11 +517,19 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
|
||||
try
|
||||
{
|
||||
var xmin = double.Parse(parts[IDX_XMIN], ci);
|
||||
var ymin = double.Parse(parts[IDX_YMIN], ci);
|
||||
var tileKeyRaw = IDX_TILE_KEY >= 0 ? parts[IDX_TILE_KEY].Trim() : "";
|
||||
var tileKey = ResolveTileKey(tileKeyRaw, xmin, ymin, out var xKey, out var yKey);
|
||||
|
||||
tiles.Add(new TileMetadata
|
||||
{
|
||||
TileKey = tileKey,
|
||||
TileId = parts[IDX_TILE].Trim(),
|
||||
Xmin = double.Parse(parts[IDX_XMIN], ci),
|
||||
Ymin = double.Parse(parts[IDX_YMIN], ci),
|
||||
XKey = xKey,
|
||||
YKey = yKey,
|
||||
Xmin = xmin,
|
||||
Ymin = ymin,
|
||||
Xmax = double.Parse(parts[IDX_XMAX], ci),
|
||||
Ymax = double.Parse(parts[IDX_YMAX], ci),
|
||||
GlobalMin = double.Parse(parts[IDX_GMIN], ci),
|
||||
@@ -325,6 +546,58 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
return tiles;
|
||||
}
|
||||
|
||||
private string ResolveTileKey(string tileKeyRaw, double xmin, double ymin, out int xKey, out int yKey)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tileKeyRaw) && TryParseTileKey(tileKeyRaw, out xKey, out yKey))
|
||||
return tileKeyRaw;
|
||||
|
||||
float sizeX = tileKeySizeX <= 0f ? 1f : tileKeySizeX;
|
||||
float sizeY = tileKeySizeY <= 0f ? 1f : tileKeySizeY;
|
||||
xKey = (int)Math.Floor((xmin + tileKeyOverlapX) / sizeX);
|
||||
yKey = (int)Math.Floor((ymin + tileKeyOverlapY) / sizeY);
|
||||
return $"{xKey}_{yKey}";
|
||||
}
|
||||
|
||||
private static bool TryParseTileKey(string tileKey, out int xKey, out int yKey)
|
||||
{
|
||||
xKey = 0;
|
||||
yKey = 0;
|
||||
if (string.IsNullOrWhiteSpace(tileKey))
|
||||
return false;
|
||||
|
||||
var parts = tileKey.Split('_');
|
||||
if (parts.Length != 2)
|
||||
return false;
|
||||
|
||||
return int.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out xKey)
|
||||
&& int.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out yKey);
|
||||
}
|
||||
|
||||
private static bool ContainsKey(List<TileKeyOption> options, string key)
|
||||
{
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (string.Equals(options[i].Key, key, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string[] BuildKeyArray(List<TileKeyOption> options)
|
||||
{
|
||||
var keys = new string[options.Count];
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
keys[i] = options[i].Key;
|
||||
return keys;
|
||||
}
|
||||
|
||||
private struct TileKeyOption
|
||||
{
|
||||
public string Key;
|
||||
public int X;
|
||||
public int Y;
|
||||
}
|
||||
|
||||
private bool CreateTilePrefab(TileMetadata tile)
|
||||
{
|
||||
// Validate height range
|
||||
@@ -407,6 +680,7 @@ public class GeoTilePrefabImporter : EditorWindow
|
||||
|
||||
// Store metadata as component for later use
|
||||
var metadata = root.AddComponent<GeoTileMetadata>();
|
||||
metadata.tileKey = tile.TileKey;
|
||||
metadata.tileId = tile.TileId;
|
||||
metadata.xmin = tile.Xmin;
|
||||
metadata.ymin = tile.Ymin;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#if UNITY_EDITOR
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
@@ -46,6 +48,44 @@ public static class MissingScriptUtility
|
||||
Debug.Log($"[MissingScriptUtility] Missing scripts found in prefabs: {missingCount}");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Diagnostics/Find Missing Scripts in ScriptableObjects")]
|
||||
public static void FindMissingScriptsInScriptableObjects()
|
||||
{
|
||||
var assetGuids = AssetDatabase.FindAssets("t:ScriptableObject");
|
||||
int missingCount = 0;
|
||||
|
||||
for (int i = 0; i < assetGuids.Length; i++)
|
||||
{
|
||||
var path = AssetDatabase.GUIDToAssetPath(assetGuids[i]);
|
||||
EditorUtility.DisplayProgressBar("Scanning ScriptableObjects", path, (float)i / assetGuids.Length);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(path) || !path.EndsWith(".asset", StringComparison.OrdinalIgnoreCase))
|
||||
continue;
|
||||
|
||||
if (!File.Exists(path))
|
||||
continue;
|
||||
|
||||
string contents;
|
||||
try
|
||||
{
|
||||
contents = File.ReadAllText(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (contents.Contains("m_Script: {fileID: 0}"))
|
||||
{
|
||||
missingCount++;
|
||||
Debug.LogWarning($"[MissingScriptUtility] Missing script reference in asset: {path}");
|
||||
}
|
||||
}
|
||||
|
||||
EditorUtility.ClearProgressBar();
|
||||
Debug.Log($"[MissingScriptUtility] Missing scripts found in scriptable objects: {missingCount}");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Diagnostics/Remove Missing Scripts in Open Scenes")]
|
||||
public static void RemoveMissingScriptsInOpenScenes()
|
||||
{
|
||||
|
||||
@@ -89,8 +89,9 @@ public class GeoTileAddressablesLoader : MonoBehaviour
|
||||
tiles.Clear();
|
||||
foreach (var tile in manifest.tiles)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(tile.tileId))
|
||||
tiles[tile.tileId] = tile;
|
||||
var key = !string.IsNullOrWhiteSpace(tile.tileKey) ? tile.tileKey : tile.tileId;
|
||||
if (!string.IsNullOrWhiteSpace(key))
|
||||
tiles[key] = tile;
|
||||
}
|
||||
|
||||
Log($"Manifest loaded. Tiles={tiles.Count} CatalogFile={manifest.catalogFile}");
|
||||
@@ -149,69 +150,69 @@ public class GeoTileAddressablesLoader : MonoBehaviour
|
||||
|
||||
if (distance <= loadRadiusMeters)
|
||||
{
|
||||
EnqueueTileLoad(tile.tileId);
|
||||
EnqueueTileLoad(kvp.Key);
|
||||
}
|
||||
else if (distance >= unloadRadiusMeters)
|
||||
{
|
||||
UnloadTile(tile.tileId);
|
||||
UnloadTile(kvp.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnqueueTileLoad(string tileId)
|
||||
private void EnqueueTileLoad(string tileKey)
|
||||
{
|
||||
if (loaded.ContainsKey(tileId) || loading.Contains(tileId) || queued.Contains(tileId))
|
||||
if (loaded.ContainsKey(tileKey) || loading.Contains(tileKey) || queued.Contains(tileKey))
|
||||
return;
|
||||
|
||||
loadQueue.Enqueue(tileId);
|
||||
queued.Add(tileId);
|
||||
loadQueue.Enqueue(tileKey);
|
||||
queued.Add(tileKey);
|
||||
}
|
||||
|
||||
private void ProcessQueue()
|
||||
{
|
||||
while (loading.Count < maxConcurrentLoads && loadQueue.Count > 0)
|
||||
{
|
||||
var tileId = loadQueue.Dequeue();
|
||||
queued.Remove(tileId);
|
||||
StartLoad(tileId);
|
||||
var tileKey = loadQueue.Dequeue();
|
||||
queued.Remove(tileKey);
|
||||
StartLoad(tileKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void StartLoad(string tileId)
|
||||
private void StartLoad(string tileKey)
|
||||
{
|
||||
if (!tiles.TryGetValue(tileId, out var tile))
|
||||
if (!tiles.TryGetValue(tileKey, out var tile))
|
||||
return;
|
||||
|
||||
loading.Add(tileId);
|
||||
Log($"Loading tile {tileId}...");
|
||||
loading.Add(tileKey);
|
||||
Log($"Loading tile {tileKey}...");
|
||||
|
||||
var handle = Addressables.InstantiateAsync(tileId, tilesParent);
|
||||
var handle = Addressables.InstantiateAsync(tileKey, tilesParent);
|
||||
handle.Completed += op =>
|
||||
{
|
||||
loading.Remove(tileId);
|
||||
loading.Remove(tileKey);
|
||||
|
||||
if (op.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Debug.LogError($"[GeoTileAddressablesLoader] Load failed for {tileId}: {op.OperationException}");
|
||||
Debug.LogError($"[GeoTileAddressablesLoader] Load failed for {tileKey}: {op.OperationException}");
|
||||
return;
|
||||
}
|
||||
|
||||
var instance = op.Result;
|
||||
instance.name = tileId;
|
||||
instance.name = tileKey;
|
||||
instance.transform.position = new Vector3(tile.offsetX, tile.baseY, tile.offsetZ);
|
||||
loaded[tileId] = instance;
|
||||
Log($"Loaded tile {tileId}. LoadedCount={loaded.Count}");
|
||||
loaded[tileKey] = instance;
|
||||
Log($"Loaded tile {tileKey}. LoadedCount={loaded.Count}");
|
||||
};
|
||||
}
|
||||
|
||||
private void UnloadTile(string tileId)
|
||||
private void UnloadTile(string tileKey)
|
||||
{
|
||||
if (!loaded.TryGetValue(tileId, out var instance))
|
||||
if (!loaded.TryGetValue(tileKey, out var instance))
|
||||
return;
|
||||
|
||||
Addressables.ReleaseInstance(instance);
|
||||
loaded.Remove(tileId);
|
||||
Log($"Unloaded tile {tileId}. LoadedCount={loaded.Count}");
|
||||
loaded.Remove(tileKey);
|
||||
Log($"Unloaded tile {tileKey}. LoadedCount={loaded.Count}");
|
||||
}
|
||||
|
||||
private static string GetBuildTargetFolderName()
|
||||
@@ -222,13 +223,13 @@ public class GeoTileAddressablesLoader : MonoBehaviour
|
||||
return "Android";
|
||||
case RuntimePlatform.WindowsPlayer:
|
||||
case RuntimePlatform.WindowsEditor:
|
||||
return "Windows";
|
||||
return "StandaloneWindows64";
|
||||
case RuntimePlatform.LinuxPlayer:
|
||||
case RuntimePlatform.LinuxEditor:
|
||||
return "Linux";
|
||||
return "StandaloneLinux64";
|
||||
case RuntimePlatform.OSXPlayer:
|
||||
case RuntimePlatform.OSXEditor:
|
||||
return "OSX";
|
||||
return "StandaloneOSX";
|
||||
default:
|
||||
return "Android";
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ using UnityEngine;
|
||||
/// </summary>
|
||||
public class GeoTileMetadata : MonoBehaviour
|
||||
{
|
||||
public string tileKey;
|
||||
public string tileId;
|
||||
public double xmin;
|
||||
public double ymin;
|
||||
|
||||
@@ -13,6 +13,7 @@ public class TileManifest
|
||||
[Serializable]
|
||||
public class TileEntry
|
||||
{
|
||||
public string tileKey;
|
||||
public string tileId;
|
||||
public float offsetX;
|
||||
public float offsetZ;
|
||||
|
||||
@@ -101,38 +101,38 @@ MonoBehaviour:
|
||||
m_Keys: []
|
||||
m_Values:
|
||||
m_PrefilteringModeMainLightShadows: 3
|
||||
m_PrefilteringModeAdditionalLight: 4
|
||||
m_PrefilteringModeAdditionalLightShadows: 0
|
||||
m_PrefilteringModeAdditionalLight: 0
|
||||
m_PrefilteringModeAdditionalLightShadows: 2
|
||||
m_PrefilterXRKeywords: 1
|
||||
m_PrefilteringModeForwardPlus: 1
|
||||
m_PrefilteringModeForwardPlus: 2
|
||||
m_PrefilteringModeDeferredRendering: 0
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 1
|
||||
m_PrefilteringModeScreenSpaceOcclusion: 0
|
||||
m_PrefilterDebugKeywords: 1
|
||||
m_PrefilterWriteRenderingLayers: 0
|
||||
m_PrefilterWriteRenderingLayers: 1
|
||||
m_PrefilterHDROutput: 1
|
||||
m_PrefilterAlphaOutput: 0
|
||||
m_PrefilterSSAODepthNormals: 0
|
||||
m_PrefilterAlphaOutput: 1
|
||||
m_PrefilterSSAODepthNormals: 1
|
||||
m_PrefilterSSAOSourceDepthLow: 1
|
||||
m_PrefilterSSAOSourceDepthMedium: 1
|
||||
m_PrefilterSSAOSourceDepthHigh: 1
|
||||
m_PrefilterSSAOInterleaved: 1
|
||||
m_PrefilterSSAOBlueNoise: 0
|
||||
m_PrefilterSSAOBlueNoise: 1
|
||||
m_PrefilterSSAOSampleCountLow: 1
|
||||
m_PrefilterSSAOSampleCountMedium: 0
|
||||
m_PrefilterSSAOSampleCountMedium: 1
|
||||
m_PrefilterSSAOSampleCountHigh: 1
|
||||
m_PrefilterDBufferMRT1: 1
|
||||
m_PrefilterDBufferMRT2: 1
|
||||
m_PrefilterDBufferMRT3: 0
|
||||
m_PrefilterSoftShadowsQualityLow: 0
|
||||
m_PrefilterSoftShadowsQualityMedium: 0
|
||||
m_PrefilterSoftShadowsQualityHigh: 0
|
||||
m_PrefilterDBufferMRT3: 1
|
||||
m_PrefilterSoftShadowsQualityLow: 1
|
||||
m_PrefilterSoftShadowsQualityMedium: 1
|
||||
m_PrefilterSoftShadowsQualityHigh: 1
|
||||
m_PrefilterSoftShadows: 0
|
||||
m_PrefilterScreenCoord: 1
|
||||
m_PrefilterScreenSpaceIrradiance: 0
|
||||
m_PrefilterScreenSpaceIrradiance: 1
|
||||
m_PrefilterNativeRenderPass: 1
|
||||
m_PrefilterUseLegacyLightmaps: 0
|
||||
m_PrefilterBicubicLightmapSampling: 0
|
||||
m_PrefilterReflectionProbeRotation: 0
|
||||
m_PrefilterBicubicLightmapSampling: 1
|
||||
m_PrefilterReflectionProbeRotation: 1
|
||||
m_PrefilterReflectionProbeBlending: 0
|
||||
m_PrefilterReflectionProbeBoxProjection: 0
|
||||
m_PrefilterReflectionProbeAtlas: 0
|
||||
|
||||
@@ -11,8 +11,10 @@
|
||||
"com.unity.inputsystem": "1.17.0",
|
||||
"com.unity.multiplayer.center": "1.0.1",
|
||||
"com.unity.render-pipelines.universal": "17.3.0",
|
||||
"com.unity.sdk.linux-x86_64": "1.0.2",
|
||||
"com.unity.test-framework": "1.6.0",
|
||||
"com.unity.timeline": "1.8.10",
|
||||
"com.unity.toolchain.linux-x86_64-linux": "1.0.2",
|
||||
"com.unity.ugui": "2.0.0",
|
||||
"com.unity.visualscripting": "1.9.9",
|
||||
"com.unity.xr.arfoundation": "6.3.2",
|
||||
|
||||
@@ -264,6 +264,15 @@
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.sdk.linux-x86_64": {
|
||||
"version": "1.0.2",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.sysroot.base": "1.0.2"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.searcher": {
|
||||
"version": "4.9.4",
|
||||
"depth": 2,
|
||||
@@ -287,6 +296,13 @@
|
||||
"com.unity.searcher": "4.9.3"
|
||||
}
|
||||
},
|
||||
"com.unity.sysroot.base": {
|
||||
"version": "1.0.2",
|
||||
"depth": 1,
|
||||
"source": "registry",
|
||||
"dependencies": {},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.test-framework": {
|
||||
"version": "1.6.0",
|
||||
"depth": 0,
|
||||
@@ -319,6 +335,15 @@
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.toolchain.linux-x86_64-linux": {
|
||||
"version": "1.0.2",
|
||||
"depth": 0,
|
||||
"source": "registry",
|
||||
"dependencies": {
|
||||
"com.unity.sysroot.base": "1.0.2"
|
||||
},
|
||||
"url": "https://packages.unity.com"
|
||||
},
|
||||
"com.unity.ugui": {
|
||||
"version": "2.0.0",
|
||||
"depth": 0,
|
||||
|
||||
Reference in New Issue
Block a user