add flood swe module and 2km building bundles

This commit is contained in:
2026-02-04 01:05:20 +01:00
parent 2c77c0d215
commit ff5af7a63a
57 changed files with 5369 additions and 44 deletions

View File

@@ -12,10 +12,15 @@ using UnityEngine;
public static class GeoTileAddressablesBuilder
{
private const string TilePrefabsDir = "Assets/TilePrefabs";
private const string BuildingPrefabsDir = "Assets/TilePrefabs_Buildings";
private const string TileIndexCsvPath = "Assets/GeoData/tile_index.csv";
private const string GroupName = "TilePrefabs";
private const string BuildingGroupName = "TileBuildings";
private const string TileLabel = "tile";
private const string BuildingLabel = "tile_building";
private const string ManifestFileName = "TileManifest.json";
private const string BuildingManifestFileName = "TileBuildingsManifest.json";
private const string BuildingAddressSuffix = "_bldg";
private const float DefaultTileSizeMeters = 1000f;
private const float DefaultTileSizeX = 1000f;
private const float DefaultTileSizeY = 1000f;
@@ -61,6 +66,7 @@ public static class GeoTileAddressablesBuilder
{
public string TileIndexCsvPath;
public string TilePrefabsDir;
public string BuildingPrefabsDir;
public string OutputRoot;
public BuildTarget Target;
public BuildTargetGroup TargetGroup;
@@ -69,6 +75,8 @@ public static class GeoTileAddressablesBuilder
public List<TileRecord> SelectedTiles;
public bool OverwriteExisting;
public bool Verbose;
public bool IncludeBuildingPrefabs;
public int BuildingBlockSizeInTiles;
}
[MenuItem("Tools/Geo Tiles/Build (Android)")]
@@ -91,6 +99,7 @@ public static class GeoTileAddressablesBuilder
{
TileIndexCsvPath = TileIndexCsvPath,
TilePrefabsDir = TilePrefabsDir,
BuildingPrefabsDir = BuildingPrefabsDir,
OutputRoot = "ServerData/TileBundles",
Target = target,
TargetGroup = group,
@@ -98,7 +107,9 @@ public static class GeoTileAddressablesBuilder
TileKeyConfig = TileKeyConfig.Default,
SelectedTiles = null,
OverwriteExisting = false,
Verbose = false
Verbose = false,
IncludeBuildingPrefabs = true,
BuildingBlockSizeInTiles = 2
});
}
@@ -106,8 +117,11 @@ public static class GeoTileAddressablesBuilder
{
var tileIndexCsvPath = string.IsNullOrWhiteSpace(request.TileIndexCsvPath) ? TileIndexCsvPath : request.TileIndexCsvPath;
var tilePrefabsDir = string.IsNullOrWhiteSpace(request.TilePrefabsDir) ? TilePrefabsDir : request.TilePrefabsDir;
var buildingPrefabsDir = string.IsNullOrWhiteSpace(request.BuildingPrefabsDir) ? BuildingPrefabsDir : request.BuildingPrefabsDir;
var outputRoot = string.IsNullOrWhiteSpace(request.OutputRoot) ? "ServerData/TileBundles" : request.OutputRoot;
var tileSizeMeters = request.TileSizeMeters > 0f ? request.TileSizeMeters : DefaultTileSizeMeters;
var includeBuildingPrefabs = request.IncludeBuildingPrefabs;
var buildingBlockSize = Math.Max(1, request.BuildingBlockSizeInTiles);
var tileKeyConfig = request.TileKeyConfig;
if (tileKeyConfig.TileSizeX <= 0f && tileKeyConfig.TileSizeY <= 0f &&
tileKeyConfig.OverlapX == 0f && tileKeyConfig.OverlapY == 0f)
@@ -120,6 +134,11 @@ public static class GeoTileAddressablesBuilder
Debug.LogError($"[GeoTileAddressablesBuilder] Prefab directory missing: {tilePrefabsDir}");
return false;
}
if (includeBuildingPrefabs && !Directory.Exists(buildingPrefabsDir))
{
Debug.LogWarning($"[GeoTileAddressablesBuilder] Building prefab directory missing: {buildingPrefabsDir}. Skipping building bundles.");
includeBuildingPrefabs = false;
}
if (!File.Exists(tileIndexCsvPath))
{
Debug.LogError($"[GeoTileAddressablesBuilder] CSV missing: {tileIndexCsvPath}");
@@ -145,7 +164,7 @@ public static class GeoTileAddressablesBuilder
EnsureProfileVariable(settings, LoadPathVariable, LoadPathValue);
EnsureRemoteCatalogPaths(settings);
var groupAsset = GetOrCreateGroup(settings);
var groupAsset = GetOrCreateGroup(settings, GroupName);
ConfigureGroup(settings, groupAsset);
var assignedTileIds = AssignPrefabs(settings, groupAsset, tilePrefabsDir, tiles, true);
@@ -158,6 +177,18 @@ public static class GeoTileAddressablesBuilder
return false;
}
List<TileRecord> buildingTiles = null;
if (includeBuildingPrefabs)
{
var buildingGroup = GetOrCreateGroup(settings, BuildingGroupName);
ConfigureGroup(settings, buildingGroup);
var anchors = tiles.Where(tile => IsBuildingAnchor(tile, buildingBlockSize)).ToList();
var assignedBuildingIds = AssignBuildingPrefabs(settings, buildingGroup, buildingPrefabsDir, anchors, true);
buildingTiles = anchors
.Where(tile => !string.IsNullOrWhiteSpace(tile.TileId) && assignedBuildingIds.Contains(tile.TileId))
.ToList();
}
settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true);
AssetDatabase.SaveAssets();
@@ -191,14 +222,43 @@ public static class GeoTileAddressablesBuilder
return false;
}
var manifest = BuildManifest(request.Target.ToString(), catalogFile, catalogHashFile, filteredTiles, tileSizeMeters);
var (originX, originY) = ComputeOrigin(filteredTiles);
var manifest = BuildManifest(
request.Target.ToString(),
catalogFile,
catalogHashFile,
filteredTiles,
tileSizeMeters,
ResolveTileKey,
originX,
originY);
var manifestPath = Path.Combine(outputPath, ManifestFileName);
File.WriteAllText(manifestPath, JsonUtility.ToJson(manifest, true));
if (includeBuildingPrefabs && buildingTiles != null && buildingTiles.Count > 0)
{
var buildingManifest = BuildManifest(
request.Target.ToString(),
catalogFile,
catalogHashFile,
buildingTiles,
tileSizeMeters,
BuildBuildingAddress,
originX,
originY);
var buildingManifestPath = Path.Combine(outputPath, BuildingManifestFileName);
File.WriteAllText(buildingManifestPath, JsonUtility.ToJson(buildingManifest, true));
}
AssetDatabase.Refresh();
if (request.Verbose)
Debug.Log($"[GeoTileAddressablesBuilder] Built {filteredTiles.Count} tiles (from {tiles.Count} selected). Output={outputPath}");
{
var buildingInfo = includeBuildingPrefabs && buildingTiles != null
? $", Buildings={buildingTiles.Count}"
: "";
Debug.Log($"[GeoTileAddressablesBuilder] Built {filteredTiles.Count} tiles (from {tiles.Count} selected){buildingInfo}. Output={outputPath}");
}
return true;
}
@@ -227,7 +287,7 @@ public static class GeoTileAddressablesBuilder
continue;
var entry = settings.CreateOrMoveEntry(guid, group, false, false);
entry.address = tile.TileKey;
entry.address = ResolveTileKey(tile);
entry.SetLabel(TileLabel, true, true);
selectedGuids.Add(guid);
assignedTileIds.Add(tileId);
@@ -254,13 +314,64 @@ public static class GeoTileAddressablesBuilder
return assignedTileIds;
}
private static AddressableAssetGroup GetOrCreateGroup(AddressableAssetSettings settings)
private static HashSet<string> AssignBuildingPrefabs(AddressableAssetSettings settings, AddressableAssetGroup group, string prefabsDir, List<TileRecord> tiles, bool removeUnselected)
{
var group = settings.FindGroup(GroupName);
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, 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 = BuildBuildingAddress(tile);
entry.SetLabel(BuildingLabel, 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, string groupName)
{
var group = settings.FindGroup(groupName);
if (group != null)
return group;
group = settings.CreateGroup(GroupName, false, false, false, new List<AddressableAssetGroupSchema>());
group = settings.CreateGroup(groupName, false, false, false, new List<AddressableAssetGroupSchema>());
group.AddSchema<BundledAssetGroupSchema>();
group.AddSchema<ContentUpdateGroupSchema>();
return group;
@@ -327,17 +438,37 @@ public static class GeoTileAddressablesBuilder
}
private static TileManifest BuildManifest(string buildTarget, string catalogFile, string catalogHashFile, List<TileRecord> tiles, float tileSizeMeters)
=> BuildManifest(buildTarget, catalogFile, catalogHashFile, tiles, tileSizeMeters, ResolveTileKey, null, null);
private static TileManifest BuildManifest(
string buildTarget,
string catalogFile,
string catalogHashFile,
List<TileRecord> tiles,
float tileSizeMeters,
Func<TileRecord, string> keyResolver,
double? originX,
double? originY)
{
if (tiles.Count == 0)
throw new InvalidOperationException("No tiles selected for TileManifest.");
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
foreach (var tile in tiles)
double minX;
double minY;
if (originX.HasValue && originY.HasValue)
{
minX = Math.Min(minX, tile.Xmin);
minY = Math.Min(minY, tile.Ymin);
minX = originX.Value;
minY = originY.Value;
}
else
{
minX = double.PositiveInfinity;
minY = double.PositiveInfinity;
foreach (var tile in tiles)
{
minX = Math.Min(minX, tile.Xmin);
minY = Math.Min(minY, tile.Ymin);
}
}
var entries = new TileEntry[tiles.Count];
@@ -346,7 +477,7 @@ public static class GeoTileAddressablesBuilder
var tile = tiles[i];
entries[i] = new TileEntry
{
tileKey = tile.TileKey,
tileKey = keyResolver(tile),
tileId = tile.TileId,
offsetX = (float)(tile.Xmin - minX),
offsetZ = (float)(tile.Ymin - minY),
@@ -366,6 +497,28 @@ public static class GeoTileAddressablesBuilder
};
}
private static (double originX, double originY) ComputeOrigin(List<TileRecord> tiles)
{
double minX = double.PositiveInfinity;
double minY = double.PositiveInfinity;
foreach (var tile in tiles)
{
minX = Math.Min(minX, tile.Xmin);
minY = Math.Min(minY, tile.Ymin);
}
return (minX, minY);
}
private static string ResolveTileKey(TileRecord tile)
=> string.IsNullOrWhiteSpace(tile.TileKey) ? tile.TileId : tile.TileKey;
private static string BuildBuildingAddress(TileRecord tile)
=> $"{ResolveTileKey(tile)}{BuildingAddressSuffix}";
private static bool IsBuildingAnchor(TileRecord tile, int blockSize)
=> (tile.XKey % blockSize == 0) && (tile.YKey % blockSize == 0);
private static string GetRemoteCatalogBuildPath(AddressableAssetSettings settings, BuildTarget target)
{
var rawPath = settings.RemoteCatalogBuildPath.GetValue(settings);

View File

@@ -8,6 +8,7 @@ public class GeoTileAddressablesWindow : EditorWindow
{
private string tileIndexCsvPath = "Assets/GeoData/tile_index.csv";
private string tilePrefabsDir = "Assets/TilePrefabs";
private string buildingPrefabsDir = "Assets/TilePrefabs_Buildings";
private string outputRoot = "ServerData/TileBundles";
private BuildTarget buildTarget = BuildTarget.StandaloneLinux64;
@@ -21,6 +22,8 @@ public class GeoTileAddressablesWindow : EditorWindow
private bool overwriteExisting = true;
private bool verboseLogging = false;
private bool includeBuildingPrefabs = true;
private int buildingBlockSize = 2;
private Vector2 scrollPosition;
@@ -68,6 +71,7 @@ public class GeoTileAddressablesWindow : EditorWindow
GUILayout.Label("Paths", EditorStyles.boldLabel);
tileIndexCsvPath = EditorGUILayout.TextField("Tile Index CSV", tileIndexCsvPath);
tilePrefabsDir = EditorGUILayout.TextField("Tile Prefabs Dir", tilePrefabsDir);
buildingPrefabsDir = EditorGUILayout.TextField("Building Prefabs Dir", buildingPrefabsDir);
outputRoot = EditorGUILayout.TextField("Output Root", outputRoot);
buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target", buildTarget);
@@ -89,6 +93,8 @@ public class GeoTileAddressablesWindow : EditorWindow
buildSource = (BuildSource)EditorGUILayout.EnumPopup("Source", buildSource);
overwriteExisting = EditorGUILayout.ToggleLeft("Overwrite existing bundles", overwriteExisting);
verboseLogging = EditorGUILayout.ToggleLeft("Verbose logging", verboseLogging);
includeBuildingPrefabs = EditorGUILayout.ToggleLeft("Include building prefabs (2km blocks)", includeBuildingPrefabs);
buildingBlockSize = EditorGUILayout.IntField("Building block size (tiles)", buildingBlockSize);
if (buildSource == BuildSource.SceneTerrains)
EditorGUILayout.HelpBox("Scene terrain source is not implemented yet.", MessageType.Info);
@@ -299,6 +305,7 @@ public class GeoTileAddressablesWindow : EditorWindow
{
TileIndexCsvPath = tileIndexCsvPath,
TilePrefabsDir = tilePrefabsDir,
BuildingPrefabsDir = buildingPrefabsDir,
OutputRoot = outputRoot,
Target = buildTarget,
TargetGroup = GetBuildTargetGroup(buildTarget),
@@ -312,7 +319,9 @@ public class GeoTileAddressablesWindow : EditorWindow
},
SelectedTiles = selectedTiles,
OverwriteExisting = overwriteExisting,
Verbose = verboseLogging
Verbose = verboseLogging,
IncludeBuildingPrefabs = includeBuildingPrefabs,
BuildingBlockSizeInTiles = buildingBlockSize
};
GeoTileAddressablesBuilder.BuildSelectedTiles(request);

View File

@@ -13,9 +13,10 @@ using UnityEngine;
public class GeoTileImporter : EditorWindow
{
private string tilesCsvPath = "Assets/GeoData/tile_index.csv";
private string heightmapsDir = "Assets/GeoData/height_png16";
private string orthoDir = "Assets/GeoData/ortho_jpg";
private string tilesCsvPath = "Assets/GeoData/tile_index_river_vr.csv";
private string heightmapsDir = "Assets/GeoData/height_png16_river/vr";
private string orthoDir = "Assets/GeoData/ortho_jpg_river";
private string orthoDirFallback = "Assets/GeoData/ortho_jpg";
private string buildingsDir = "Assets/GeoData/buildings_tiles";
private string buildingsEnhancedDir = "Assets/GeoData/buildings_enhanced";
private string treesDir = "Assets/GeoData/trees_tiles";
@@ -34,6 +35,7 @@ public class GeoTileImporter : EditorWindow
private bool applyOrthoTextures = true;
private bool importBuildings = true;
private bool useEnhancedBuildings = false;
private bool importBuildingsEvenTilesOnly = true;
private bool importTrees = true;
private bool importFurniture = false;
private bool deleteExistingBuildings = false;
@@ -83,7 +85,8 @@ public class GeoTileImporter : EditorWindow
GUILayout.Label("Inputs", EditorStyles.boldLabel);
tilesCsvPath = EditorGUILayout.TextField("tile_index.csv", tilesCsvPath);
heightmapsDir = EditorGUILayout.TextField("height_png16 dir", heightmapsDir);
orthoDir = EditorGUILayout.TextField("ortho_jpg dir", orthoDir);
orthoDir = EditorGUILayout.TextField("ortho_jpg dir (primary)", orthoDir);
orthoDirFallback = EditorGUILayout.TextField("ortho_jpg dir (fallback)", orthoDirFallback);
buildingsDir = EditorGUILayout.TextField("buildings_glb dir", buildingsDir);
treesDir = EditorGUILayout.TextField("trees_glb dir", treesDir);
treeProxyPath = EditorGUILayout.TextField("tree_proxies.glb", treeProxyPath);
@@ -103,6 +106,7 @@ public class GeoTileImporter : EditorWindow
deleteExistingBuildings = EditorGUILayout.ToggleLeft("Delete existing buildings under parent", deleteExistingBuildings);
importBuildings = EditorGUILayout.ToggleLeft("Import buildings (GLB per tile)", importBuildings);
useEnhancedBuildings = EditorGUILayout.ToggleLeft("Use enhanced buildings (from buildings_enhanced/)", useEnhancedBuildings);
importBuildingsEvenTilesOnly = EditorGUILayout.ToggleLeft("Import 2km buildings once (even X/Y tiles only)", importBuildingsEvenTilesOnly);
GUILayout.Space(5);
treesParentName = EditorGUILayout.TextField("Trees parent name", treesParentName);
@@ -287,10 +291,15 @@ public class GeoTileImporter : EditorWindow
Debug.LogError($"[GeoTileImporter] Heightmap dir not found: {heightmapsDir}");
return;
}
if (applyOrthoTextures && !Directory.Exists(orthoDir))
if (applyOrthoTextures)
{
Debug.LogWarning($"[GeoTileImporter] Ortho dir not found: {orthoDir} (textures will be skipped).");
applyOrthoTextures = false;
bool primaryExists = Directory.Exists(orthoDir);
bool fallbackExists = !string.IsNullOrWhiteSpace(orthoDirFallback) && Directory.Exists(orthoDirFallback);
if (!primaryExists && !fallbackExists)
{
Debug.LogWarning($"[GeoTileImporter] Ortho dirs not found: primary={orthoDir}, fallback={orthoDirFallback} (textures will be skipped).");
applyOrthoTextures = false;
}
}
RefreshTileIndexCache();
@@ -431,6 +440,12 @@ public class GeoTileImporter : EditorWindow
if (applyOrthoTextures)
{
string orthoPath = Path.Combine(orthoDir, $"{tileId}.jpg").Replace("\\", "/");
if (!File.Exists(orthoPath) && !string.IsNullOrWhiteSpace(orthoDirFallback))
{
string fallbackPath = Path.Combine(orthoDirFallback, $"{tileId}.jpg").Replace("\\", "/");
if (File.Exists(fallbackPath))
orthoPath = fallbackPath;
}
if (File.Exists(orthoPath))
{
EnsureOrthoImportSettings(orthoPath);
@@ -805,6 +820,56 @@ public class GeoTileImporter : EditorWindow
public int Y;
}
private static bool TryGetTileXY(string tileId, out int x, out int y)
{
x = 0;
y = 0;
if (string.IsNullOrWhiteSpace(tileId))
return false;
var parts = tileId.Split('_');
var coords = new List<int>();
for (int i = 0; i < parts.Length; i++)
{
string part = parts[i];
if (part.Length < 3)
continue;
bool allDigits = true;
for (int j = 0; j < part.Length; j++)
{
if (!char.IsDigit(part[j]))
{
allDigits = false;
break;
}
}
if (!allDigits)
continue;
if (int.TryParse(part, NumberStyles.Integer, CultureInfo.InvariantCulture, out int value))
coords.Add(value);
}
if (coords.Count < 2)
return false;
x = coords[coords.Count - 2];
y = coords[coords.Count - 1];
return true;
}
private static bool ShouldImportBuildingsForTile(string tileId, bool evenTilesOnly)
{
if (!evenTilesOnly)
return true;
if (!TryGetTileXY(tileId, out int x, out int y))
return true;
return (x % 2 == 0) && (y % 2 == 0);
}
private void ImportBuildings(List<(string tileId, float ux, float uz, float baseY)> placements)
{
@@ -829,9 +894,15 @@ public class GeoTileImporter : EditorWindow
DestroyImmediate(parent.transform.GetChild(i).gameObject);
}
int imported = 0, missing = 0;
int imported = 0, missing = 0, skipped = 0;
foreach (var (tileId, ux, uz, baseY) in placements)
{
if (!ShouldImportBuildingsForTile(tileId, importBuildingsEvenTilesOnly))
{
skipped++;
continue;
}
string glbPath = Path.Combine(activeDir, $"{tileId}.glb").Replace("\\", "/");
if (!File.Exists(glbPath))
{
@@ -863,7 +934,7 @@ public class GeoTileImporter : EditorWindow
imported++;
}
Debug.Log($"[GeoTileImporter] Buildings ({sourceLabel}) imported={imported}, missing/failed={missing} under '{buildingsParentName}'.");
Debug.Log($"[GeoTileImporter] Buildings ({sourceLabel}) imported={imported}, skipped={skipped}, missing/failed={missing} under '{buildingsParentName}'.");
}
private void ImportTrees(List<(string tileId, float ux, float uz, float baseY)> placements)

View File

@@ -15,6 +15,7 @@ public class GeoTilePrefabImporter : EditorWindow
private string tilesCsvPath = "Assets/GeoData/tile_index.csv";
private string heightmapsDir = "Assets/GeoData/height_png16";
private string orthoDir = "Assets/GeoData/ortho_jpg";
private string orthoDirFallback = "Assets/GeoData/ortho_jpg_river";
private string buildingsDir = "Assets/GeoData/buildings_tiles";
private string treesDir = "Assets/GeoData/trees_tiles";
private string furnitureDir = "Assets/GeoData/street_furniture";
@@ -23,6 +24,9 @@ public class GeoTilePrefabImporter : EditorWindow
// Output settings
private string prefabOutputDir = "Assets/TilePrefabs";
private bool overwriteExisting = false;
private string buildingPrefabsDir = "Assets/TilePrefabs_Buildings";
private bool exportBuildingPrefabs = true;
private bool overwriteBuildingPrefabs = false;
// Terrain settings
private float tileSizeMeters = 1000f;
@@ -31,6 +35,7 @@ public class GeoTilePrefabImporter : EditorWindow
// Component toggles
private bool applyOrthoTextures = true;
private bool includeBuildings = true;
private bool includeBuildingsEvenTilesOnly = true;
private bool includeTrees = true;
private bool includeFurniture = false;
private bool includeEnhancedTrees = false;
@@ -87,7 +92,8 @@ public class GeoTilePrefabImporter : EditorWindow
GUILayout.Label("Input Paths", EditorStyles.boldLabel);
tilesCsvPath = EditorGUILayout.TextField("tile_index.csv", tilesCsvPath);
heightmapsDir = EditorGUILayout.TextField("height_png16 dir", heightmapsDir);
orthoDir = EditorGUILayout.TextField("ortho_jpg dir", orthoDir);
orthoDir = EditorGUILayout.TextField("ortho_jpg dir (primary)", orthoDir);
orthoDirFallback = EditorGUILayout.TextField("ortho_jpg dir (fallback)", orthoDirFallback);
buildingsDir = EditorGUILayout.TextField("buildings_tiles dir", buildingsDir);
treesDir = EditorGUILayout.TextField("trees_tiles dir", treesDir);
furnitureDir = EditorGUILayout.TextField("street_furniture dir", furnitureDir);
@@ -97,6 +103,9 @@ public class GeoTilePrefabImporter : EditorWindow
GUILayout.Label("Output Settings", EditorStyles.boldLabel);
prefabOutputDir = EditorGUILayout.TextField("Prefab output dir", prefabOutputDir);
overwriteExisting = EditorGUILayout.ToggleLeft("Overwrite existing prefabs", overwriteExisting);
buildingPrefabsDir = EditorGUILayout.TextField("Building prefab output dir", buildingPrefabsDir);
exportBuildingPrefabs = EditorGUILayout.ToggleLeft("Export building-only prefabs (2km blocks)", exportBuildingPrefabs);
overwriteBuildingPrefabs = EditorGUILayout.ToggleLeft("Overwrite building prefabs", overwriteBuildingPrefabs);
GUILayout.Space(10);
GUILayout.Label("Terrain Settings", EditorStyles.boldLabel);
@@ -107,6 +116,7 @@ public class GeoTilePrefabImporter : EditorWindow
GUILayout.Label("Include Components", EditorStyles.boldLabel);
applyOrthoTextures = EditorGUILayout.ToggleLeft("Apply ortho textures", applyOrthoTextures);
includeBuildings = EditorGUILayout.ToggleLeft("Include buildings (GLB)", includeBuildings);
includeBuildingsEvenTilesOnly = EditorGUILayout.ToggleLeft("Include 2km buildings once (even X/Y tiles only)", includeBuildingsEvenTilesOnly);
includeTrees = EditorGUILayout.ToggleLeft("Include trees (GLB chunks)", includeTrees);
includeFurniture = EditorGUILayout.ToggleLeft("Include street furniture (CSV)", includeFurniture);
includeEnhancedTrees = EditorGUILayout.ToggleLeft("Include enhanced trees (CSV)", includeEnhancedTrees);
@@ -249,6 +259,8 @@ public class GeoTilePrefabImporter : EditorWindow
EnsureDirectoryExists(prefabOutputDir);
EnsureDirectoryExists($"{prefabOutputDir}/TerrainData");
EnsureDirectoryExists($"{prefabOutputDir}/TerrainLayers");
if (exportBuildingPrefabs)
EnsureDirectoryExists(buildingPrefabsDir);
// Parse CSV
RefreshTileIndexCache();
@@ -269,6 +281,7 @@ public class GeoTilePrefabImporter : EditorWindow
Debug.Log($"[GeoTilePrefabImporter] Found {selectedTiles.Count} tiles to process.");
int created = 0, skipped = 0, failed = 0;
int buildingsCreated = 0, buildingsSkipped = 0, buildingsFailed = 0;
for (int i = 0; i < selectedTiles.Count; i++)
{
@@ -279,19 +292,33 @@ public class GeoTilePrefabImporter : EditorWindow
(float)i / selectedTiles.Count);
string prefabPath = $"{prefabOutputDir}/{tile.TileId}.prefab";
if (File.Exists(prefabPath) && !overwriteExisting)
bool skipTerrain = File.Exists(prefabPath) && !overwriteExisting;
if (skipTerrain)
{
Debug.Log($"[GeoTilePrefabImporter] Skipping existing: {tile.TileId}");
Debug.Log($"[GeoTilePrefabImporter] Skipping existing terrain prefab: {tile.TileId}");
skipped++;
continue;
}
try
{
if (CreateTilePrefab(tile))
created++;
else
failed++;
if (!skipTerrain)
{
if (CreateTilePrefab(tile))
created++;
else
failed++;
}
if (exportBuildingPrefabs)
{
var result = CreateBuildingPrefab(tile);
if (result == BuildResult.Created)
buildingsCreated++;
else if (result == BuildResult.Skipped)
buildingsSkipped++;
else
buildingsFailed++;
}
}
catch (Exception e)
{
@@ -304,7 +331,8 @@ public class GeoTilePrefabImporter : EditorWindow
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[GeoTilePrefabImporter] DONE. Created={created}, Skipped={skipped}, Failed={failed}");
Debug.Log($"[GeoTilePrefabImporter] DONE. Created={created}, Skipped={skipped}, Failed={failed}" +
(exportBuildingPrefabs ? $", Buildings Created={buildingsCreated}, Skipped={buildingsSkipped}, Failed={buildingsFailed}" : ""));
}
private void RefreshTileIndexCache()
@@ -618,6 +646,21 @@ public class GeoTilePrefabImporter : EditorWindow
public int Y;
}
private static bool ShouldIncludeBuildings(TileMetadata tile, bool evenTilesOnly)
{
if (!evenTilesOnly)
return true;
return (tile.XKey % 2 == 0) && (tile.YKey % 2 == 0);
}
private enum BuildResult
{
Created,
Skipped,
Failed
}
private bool CreateTilePrefab(TileMetadata tile)
{
// Validate height range
@@ -736,9 +779,53 @@ public class GeoTilePrefabImporter : EditorWindow
return true;
}
private BuildResult CreateBuildingPrefab(TileMetadata tile)
{
if (!exportBuildingPrefabs)
return BuildResult.Skipped;
if (!ShouldIncludeBuildings(tile, includeBuildingsEvenTilesOnly))
return BuildResult.Skipped;
string glbPath = Path.Combine(buildingsDir, $"{tile.TileId}.glb").Replace("\\", "/");
if (!File.Exists(glbPath))
return BuildResult.Skipped;
string prefabPath = $"{buildingPrefabsDir}/{tile.TileId}.prefab";
if (File.Exists(prefabPath))
{
if (!overwriteBuildingPrefabs)
return BuildResult.Skipped;
AssetDatabase.DeleteAsset(prefabPath);
}
var root = new GameObject(tile.TileId);
var metadata = root.AddComponent<GeoTileMetadata>();
metadata.tileKey = tile.TileKey;
metadata.tileId = tile.TileId;
metadata.xmin = tile.Xmin;
metadata.ymin = tile.Ymin;
metadata.globalMin = tile.GlobalMin;
metadata.globalMax = tile.GlobalMax;
metadata.tileMin = tile.TileMin;
metadata.tileMax = tile.TileMax;
AddBuildings(root, tile);
PrefabUtility.SaveAsPrefabAsset(root, prefabPath);
Debug.Log($"[GeoTilePrefabImporter] Created building prefab: {prefabPath}");
DestroyImmediate(root);
return BuildResult.Created;
}
private void ApplyOrthoTexture(TerrainData terrainData, string tileId)
{
string orthoPath = Path.Combine(orthoDir, $"{tileId}.jpg").Replace("\\", "/");
if (!File.Exists(orthoPath) && !string.IsNullOrWhiteSpace(orthoDirFallback))
{
string fallbackPath = Path.Combine(orthoDirFallback, $"{tileId}.jpg").Replace("\\", "/");
if (File.Exists(fallbackPath))
orthoPath = fallbackPath;
}
if (!File.Exists(orthoPath))
{
Debug.LogWarning($"[GeoTilePrefabImporter] Ortho texture missing for {tileId}: {orthoPath}");
@@ -779,6 +866,9 @@ public class GeoTilePrefabImporter : EditorWindow
private void AddBuildings(GameObject root, TileMetadata tile)
{
if (!ShouldIncludeBuildings(tile, includeBuildingsEvenTilesOnly))
return;
string glbPath = Path.Combine(buildingsDir, $"{tile.TileId}.glb").Replace("\\", "/");
if (!File.Exists(glbPath))
return;