Use per-tile heightmap scaling
This commit is contained in:
@@ -116,7 +116,7 @@ public class GeoTileImporter : EditorWindow
|
||||
|
||||
EditorGUILayout.HelpBox(
|
||||
"Creates one Unity Terrain per CSV row and positions tiles on a meter grid.\n" +
|
||||
"Absolute elevation mapping: Terrain Y = global_min, Terrain height = (global_max - global_min).\n" +
|
||||
"Absolute elevation mapping: Terrain Y = tile_min (or global_min), Terrain height = (tile_max - tile_min).\n" +
|
||||
"CSV is header-driven (order-independent). Optionally applies ortho JPGs and instantiates buildings/trees GLBs.",
|
||||
MessageType.Info);
|
||||
}
|
||||
@@ -298,6 +298,9 @@ public class GeoTileImporter : EditorWindow
|
||||
int IDX_GMIN = headerMap["global_min"];
|
||||
int IDX_GMAX = headerMap["global_max"];
|
||||
int IDX_RES = headerMap["out_res"];
|
||||
bool hasTileMin = headerMap.TryGetValue("tile_min", out int IDX_TMIN);
|
||||
bool hasTileMax = headerMap.TryGetValue("tile_max", out int IDX_TMAX);
|
||||
bool useTileRange = hasTileMin && hasTileMax;
|
||||
|
||||
// Compute origin from min xmin/ymin
|
||||
double originX = double.PositiveInfinity;
|
||||
@@ -312,6 +315,8 @@ public class GeoTileImporter : EditorWindow
|
||||
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 (useTileRange)
|
||||
needMaxIndex = Math.Max(needMaxIndex, Math.Max(IDX_TMIN, IDX_TMAX));
|
||||
if (parts.Length <= needMaxIndex)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Origin scan: skipping line {i + 1} (too few columns: {parts.Length}). Line: '{line}'");
|
||||
@@ -342,7 +347,7 @@ public class GeoTileImporter : EditorWindow
|
||||
|
||||
int imported = 0, skipped = 0;
|
||||
int importedTextures = 0;
|
||||
var placements = new List<(string tileId, float ux, float uz, float gmin)>();
|
||||
var placements = new List<(string tileId, float ux, float uz, float baseMin)>();
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
{
|
||||
@@ -351,6 +356,8 @@ public class GeoTileImporter : EditorWindow
|
||||
|
||||
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 (useTileRange)
|
||||
needMaxIndex = Math.Max(needMaxIndex, Math.Max(IDX_TMIN, IDX_TMAX));
|
||||
if (parts.Length <= needMaxIndex)
|
||||
{
|
||||
skipped++;
|
||||
@@ -361,6 +368,9 @@ public class GeoTileImporter : EditorWindow
|
||||
string tileId = parts[IDX_TILE].Trim();
|
||||
|
||||
double xmin, ymin, gmin, gmax;
|
||||
double tileMin = 0.0;
|
||||
double tileMax = 0.0;
|
||||
bool tileRangeValid = false;
|
||||
int outRes;
|
||||
try
|
||||
{
|
||||
@@ -377,15 +387,40 @@ public class GeoTileImporter : EditorWindow
|
||||
continue;
|
||||
}
|
||||
|
||||
if (useTileRange)
|
||||
{
|
||||
if (double.TryParse(parts[IDX_TMIN], NumberStyles.Float, ci, out double tmin) &&
|
||||
double.TryParse(parts[IDX_TMAX], NumberStyles.Float, ci, out double tmax))
|
||||
{
|
||||
tileMin = tmin;
|
||||
tileMax = tmax;
|
||||
tileRangeValid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: invalid tile_min/tile_max; falling back to global range.");
|
||||
}
|
||||
}
|
||||
|
||||
if (outRes != heightmapResolution)
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: out_res={outRes} but importer expects {heightmapResolution}.");
|
||||
|
||||
float heightRange = (float)(gmax - gmin);
|
||||
double baseMin = tileRangeValid ? tileMin : gmin;
|
||||
double baseMax = tileRangeValid ? tileMax : gmax;
|
||||
float heightRange = (float)(baseMax - baseMin);
|
||||
if (heightRange <= 0.0001f)
|
||||
{
|
||||
skipped++;
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: invalid height range (global_max <= global_min). Skipping.");
|
||||
continue;
|
||||
if (tileRangeValid)
|
||||
{
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: flat tile range; using epsilon height range.");
|
||||
heightRange = 0.001f;
|
||||
}
|
||||
else
|
||||
{
|
||||
skipped++;
|
||||
Debug.LogWarning($"[GeoTileImporter] Tile {tileId}: invalid height range (global_max <= global_min). Skipping.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
string pngPath = Path.Combine(heightmapsDir, $"{tileId}.png").Replace("\\", "/");
|
||||
@@ -452,7 +487,7 @@ public class GeoTileImporter : EditorWindow
|
||||
|
||||
float ux = (float)(xmin - originX);
|
||||
float uz = (float)(ymin - originY);
|
||||
go.transform.position = new Vector3(ux, (float)gmin, uz);
|
||||
go.transform.position = new Vector3(ux, (float)baseMin, uz);
|
||||
|
||||
var terrain = go.GetComponent<Terrain>();
|
||||
terrain.drawInstanced = true;
|
||||
@@ -493,9 +528,9 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTileImporter] Imported {tileId} @ XZ=({ux},{uz}) Y={gmin} heightRange={heightRange} usedU16={usedU16}");
|
||||
Debug.Log($"[GeoTileImporter] Imported {tileId} @ XZ=({ux},{uz}) Y={baseMin} heightRange={heightRange} usedU16={usedU16}");
|
||||
imported++;
|
||||
placements.Add((tileId, ux, uz, (float)gmin));
|
||||
placements.Add((tileId, ux, uz, (float)baseMin));
|
||||
}
|
||||
|
||||
Debug.Log($"[GeoTileImporter] DONE. Imported={imported}, Skipped={skipped}, OrthoApplied={importedTextures} under '{parentName}'.");
|
||||
@@ -509,7 +544,7 @@ public class GeoTileImporter : EditorWindow
|
||||
ImportEnhancedTrees(placements);
|
||||
}
|
||||
|
||||
private void ImportBuildings(List<(string tileId, float ux, float uz, float gmin)> placements)
|
||||
private void ImportBuildings(List<(string tileId, float ux, float uz, float baseMin)> placements)
|
||||
{
|
||||
if (!importBuildings)
|
||||
return;
|
||||
@@ -533,7 +568,7 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
|
||||
int imported = 0, missing = 0;
|
||||
foreach (var (tileId, ux, uz, gmin) in placements)
|
||||
foreach (var (tileId, ux, uz, baseMin) in placements)
|
||||
{
|
||||
string glbPath = Path.Combine(activeDir, $"{tileId}.glb").Replace("\\", "/");
|
||||
if (!File.Exists(glbPath))
|
||||
@@ -554,7 +589,7 @@ public class GeoTileImporter : EditorWindow
|
||||
var inst = PrefabUtility.InstantiatePrefab(prefab) as GameObject ?? Instantiate(prefab);
|
||||
inst.name = tileId;
|
||||
inst.transform.SetParent(parent.transform, false);
|
||||
inst.transform.position = new Vector3(ux, gmin, uz);
|
||||
inst.transform.position = new Vector3(ux, baseMin, uz);
|
||||
inst.transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
|
||||
inst.isStatic = true;
|
||||
imported++;
|
||||
@@ -563,7 +598,7 @@ public class GeoTileImporter : EditorWindow
|
||||
Debug.Log($"[GeoTileImporter] Buildings ({sourceLabel}) imported={imported}, missing/failed={missing} under '{buildingsParentName}'.");
|
||||
}
|
||||
|
||||
private void ImportTrees(List<(string tileId, float ux, float uz, float gmin)> placements)
|
||||
private void ImportTrees(List<(string tileId, float ux, float uz, float baseMin)> placements)
|
||||
{
|
||||
if (!importTrees)
|
||||
return;
|
||||
@@ -588,7 +623,7 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
|
||||
int importedTiles = 0, importedChunks = 0, missingTiles = 0;
|
||||
foreach (var (tileId, ux, uz, gmin) in placements)
|
||||
foreach (var (tileId, ux, uz, baseMin) in placements)
|
||||
{
|
||||
// Look for chunk files: {tileId}_0_0.glb, {tileId}_0_1.glb, etc.
|
||||
// Standard tree export creates 4x4 chunks per tile
|
||||
@@ -612,7 +647,7 @@ public class GeoTileImporter : EditorWindow
|
||||
// Create container for this tile's tree chunks
|
||||
var tileContainer = new GameObject($"Trees_{tileId}");
|
||||
tileContainer.transform.SetParent(parent.transform, false);
|
||||
tileContainer.transform.position = new Vector3(ux, gmin, uz);
|
||||
tileContainer.transform.position = new Vector3(ux, baseMin, uz);
|
||||
tileContainer.transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
|
||||
tileContainer.isStatic = true;
|
||||
|
||||
@@ -640,7 +675,7 @@ public class GeoTileImporter : EditorWindow
|
||||
Debug.Log($"[GeoTileImporter] Trees imported: {importedTiles} tiles, {importedChunks} chunks, {missingTiles} missing under '{treesParentName}'.");
|
||||
}
|
||||
|
||||
private void ImportFurniture(List<(string tileId, float ux, float uz, float gmin)> placements)
|
||||
private void ImportFurniture(List<(string tileId, float ux, float uz, float baseMin)> placements)
|
||||
{
|
||||
if (!importFurniture)
|
||||
return;
|
||||
@@ -662,7 +697,7 @@ public class GeoTileImporter : EditorWindow
|
||||
int imported = 0, skipped = 0;
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
|
||||
foreach (var (tileId, ux, uz, gmin) in placements)
|
||||
foreach (var (tileId, ux, uz, baseMin) in placements)
|
||||
{
|
||||
string csvPath = Path.Combine(furnitureDir, $"{tileId}.csv").Replace("\\", "/");
|
||||
if (!File.Exists(csvPath))
|
||||
@@ -696,7 +731,7 @@ public class GeoTileImporter : EditorWindow
|
||||
// Create tile container
|
||||
var tileContainer = new GameObject($"Furniture_{tileId}");
|
||||
tileContainer.transform.SetParent(parent.transform, false);
|
||||
tileContainer.transform.position = new Vector3(ux, gmin, uz);
|
||||
tileContainer.transform.position = new Vector3(ux, baseMin, uz);
|
||||
tileContainer.isStatic = true;
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
@@ -767,7 +802,7 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
|
||||
obj.transform.SetParent(tileContainer.transform, false);
|
||||
obj.transform.localPosition = new Vector3(xLocal, zGround - gmin, yLocal);
|
||||
obj.transform.localPosition = new Vector3(xLocal, zGround - baseMin, yLocal);
|
||||
obj.isStatic = true;
|
||||
imported++;
|
||||
}
|
||||
@@ -782,7 +817,7 @@ public class GeoTileImporter : EditorWindow
|
||||
Debug.Log($"[GeoTileImporter] Furniture imported={imported}, skipped={skipped} under '{furnitureParentName}'.");
|
||||
}
|
||||
|
||||
private void ImportEnhancedTrees(List<(string tileId, float ux, float uz, float gmin)> placements)
|
||||
private void ImportEnhancedTrees(List<(string tileId, float ux, float uz, float baseMin)> placements)
|
||||
{
|
||||
if (!importEnhancedTrees)
|
||||
return;
|
||||
@@ -804,7 +839,7 @@ public class GeoTileImporter : EditorWindow
|
||||
int imported = 0, skipped = 0;
|
||||
var ci = CultureInfo.InvariantCulture;
|
||||
|
||||
foreach (var (tileId, ux, uz, gmin) in placements)
|
||||
foreach (var (tileId, ux, uz, baseMin) in placements)
|
||||
{
|
||||
string csvPath = Path.Combine(enhancedTreesDir, $"{tileId}.csv").Replace("\\", "/");
|
||||
if (!File.Exists(csvPath))
|
||||
@@ -842,7 +877,7 @@ public class GeoTileImporter : EditorWindow
|
||||
// Create tile container
|
||||
var tileContainer = new GameObject($"Trees_{tileId}");
|
||||
tileContainer.transform.SetParent(parent.transform, false);
|
||||
tileContainer.transform.position = new Vector3(ux, gmin, uz);
|
||||
tileContainer.transform.position = new Vector3(ux, baseMin, uz);
|
||||
tileContainer.isStatic = true;
|
||||
|
||||
for (int i = 1; i < lines.Length; i++)
|
||||
@@ -940,7 +975,7 @@ public class GeoTileImporter : EditorWindow
|
||||
}
|
||||
|
||||
treeObj.transform.SetParent(tileContainer.transform, false);
|
||||
treeObj.transform.localPosition = new Vector3(xLocal, zGround - gmin, yLocal);
|
||||
treeObj.transform.localPosition = new Vector3(xLocal, zGround - baseMin, yLocal);
|
||||
treeObj.isStatic = true;
|
||||
imported++;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user