add flood swe module and 2km building bundles
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user