ClientServer/Client/Assets/Scripts/Util/Terrain/TerrainSplatmapMerger.cs
TG9six 03a642d635 first push
first push
2025-09-06 17:17:39 +04:00

254 lines
9.7 KiB
C#

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Collections;
public class TerrainSplatmapMerger : EditorWindow
{
public Terrain terrainA; // Перший терейн (менший, 1000x1000)
public Terrain terrainB; // Другий терейн (більший, 1500x1500, зсунутий)
public Terrain targetTerrain; // Новий (2049x2049, 2000x2000)
[MenuItem("Tools/Merge Terrains Splatmaps")]
public static void ShowWindow()
{
GetWindow(typeof(TerrainSplatmapMerger));
}
void OnGUI()
{
GUILayout.Label("Merge Splatmaps From 2 Terrains", EditorStyles.boldLabel);
terrainA = (Terrain)EditorGUILayout.ObjectField("Terrain A (1000x1000)", terrainA, typeof(Terrain), true);
terrainB = (Terrain)EditorGUILayout.ObjectField("Terrain B (1500x1500)", terrainB, typeof(Terrain), true);
targetTerrain = (Terrain)EditorGUILayout.ObjectField("Target Terrain", targetTerrain, typeof(Terrain), true);
if (GUILayout.Button("Merge Splatmaps!"))
{
MergeSplatmaps();
}
if (GUILayout.Button("Merge Details"))
{
MergeDetails();
}
if (GUILayout.Button("Merge Trees!"))
{
MergeTrees();
}
}
void MergeTrees()
{
if (terrainA == null || terrainB == null || targetTerrain == null)
{
Debug.LogError("Assign all terrains!");
return;
}
// --- 1. Merge unique TreePrototypes ---
List<TreePrototype> prototypes = new List<TreePrototype>();
foreach (var t in new Terrain[] { terrainA, terrainB })
{
foreach (var proto in t.terrainData.treePrototypes)
{
bool exists = false;
foreach (var p in prototypes)
{
if (p.prefab == proto.prefab)
{
exists = true;
break;
}
}
if (!exists)
prototypes.Add(proto);
}
}
targetTerrain.terrainData.treePrototypes = prototypes.ToArray();
Debug.Log($"Merged {prototypes.Count} unique tree prototypes.");
// --- 2. Merge Tree Instances ---
List<TreeInstance> mergedTrees = new List<TreeInstance>();
foreach (var t in new Terrain[] { terrainA, terrainB })
{
Vector3 pos = t.transform.position;
Vector3 size = t.terrainData.size;
foreach (var tree in t.terrainData.treeInstances)
{
// Переводимо в world space
Vector3 world = Vector3.Scale(tree.position, size) + pos;
// Переводимо в локальну позицію на таргет терейні
Vector3 targetSize = targetTerrain.terrainData.size;
Vector3 targetPos = targetTerrain.transform.position;
Vector3 local = (world - targetPos);
Vector3 normalized = new Vector3(
local.x / targetSize.x,
local.y / targetSize.y,
local.z / targetSize.z
);
// Знаходимо правильний індекс префабу в новому TerrainData
GameObject treePrefab = t.terrainData.treePrototypes[tree.prototypeIndex].prefab;
int newProtoIndex = -1;
for (int i = 0; i < prototypes.Count; i++)
{
if (prototypes[i].prefab == treePrefab)
{
newProtoIndex = i;
break;
}
}
if (newProtoIndex == -1)
{
Debug.LogError("Failed to match tree prefab!");
continue;
}
// Створюємо новий TreeInstance
TreeInstance newTree = tree;
newTree.position = normalized;
newTree.prototypeIndex = newProtoIndex;
mergedTrees.Add(newTree);
}
}
targetTerrain.terrainData.treeInstances = mergedTrees.ToArray();
Debug.Log($"Merged {mergedTrees.Count} trees into target terrain.");
}
void MergeDetails()
{
if (terrainA == null || terrainB == null || targetTerrain == null)
{
Debug.LogError("Assign all terrains!");
return;
}
// --- 1. Merge DetailPrototypes ---
var detailsA = terrainA.terrainData.detailPrototypes;
var detailsB = terrainB.terrainData.detailPrototypes;
List<DetailPrototype> allDetails = new List<DetailPrototype>();
allDetails.AddRange(detailsA);
foreach (var d in detailsB)
{
bool exists = false;
foreach (var da in allDetails)
{
// Unity не має порівняння DetailPrototype, тому перевіряємо по prefab/objectReference
if (da.prototype == d.prototype && da.prototypeTexture == d.prototypeTexture)
{
exists = true;
break;
}
}
if (!exists)
allDetails.Add(d);
}
targetTerrain.terrainData.detailPrototypes = allDetails.ToArray();
Debug.Log($"Merged {allDetails.Count} unique DetailPrototypes.");
// --- 2. Merge Detail Layers (detail maps) ---
int tW = targetTerrain.terrainData.detailWidth;
int tH = targetTerrain.terrainData.detailHeight;
for (int layer = 0; layer < allDetails.Count; layer++)
{
int[,] newDetail = new int[tW, tH];
// Від A (старий detail, якщо такий є)
if (layer < detailsA.Length)
{
int[,] detailA = terrainA.terrainData.GetDetailLayer(0, 0, tW, tH, layer);
for (int y = 0; y < tH; y++)
for (int x = 0; x < tW; x++)
newDetail[y, x] = Mathf.Max(newDetail[y, x], detailA[y, x]);
}
// Від B (старий detail, якщо такий є)
if (layer < detailsB.Length)
{
int[,] detailB = terrainB.terrainData.GetDetailLayer(0, 0, tW, tH, layer);
for (int y = 0; y < tH; y++)
for (int x = 0; x < tW; x++)
newDetail[y, x] = Mathf.Max(newDetail[y, x], detailB[y, x]);
}
targetTerrain.terrainData.SetDetailLayer(0, 0, layer, newDetail);
}
}
void MergeSplatmaps()
{
if (terrainA == null || terrainB == null || targetTerrain == null)
{
Debug.LogError("Assign all terrains!");
return;
}
int tw = targetTerrain.terrainData.alphamapWidth;
int th = targetTerrain.terrainData.alphamapHeight;
int layers = targetTerrain.terrainData.alphamapLayers;
float[,,] merged = new float[th, tw, layers];
var tDataA = terrainA.terrainData;
var tDataB = terrainB.terrainData;
float[,,] splatA = tDataA.GetAlphamaps(0, 0, tDataA.alphamapWidth, tDataA.alphamapHeight);
float[,,] splatB = tDataB.GetAlphamaps(0, 0, tDataB.alphamapWidth, tDataB.alphamapHeight);
Vector3 posA = terrainA.transform.position;
Vector3 sizeA = tDataA.size;
Vector3 posB = terrainB.transform.position;
Vector3 sizeB = tDataB.size;
Vector3 posTarget = targetTerrain.transform.position;
Vector3 sizeTarget = targetTerrain.terrainData.size;
for (int y = 0; y < th; y++)
{
for (int x = 0; x < tw; x++)
{
// World position цієї точки на таргет терейні
float normX = (float)x / (tw - 1);
float normY = (float)y / (th - 1);
float wx = posTarget.x + normX * sizeTarget.x;
float wz = posTarget.z + normY * sizeTarget.z;
bool filled = false;
// Перевіряємо чи потрапляє в terrainA
if (wx >= posA.x && wx < posA.x + sizeA.x && wz >= posA.z && wz < posA.z + sizeA.z)
{
float normAx = (wx - posA.x) / sizeA.x;
float normAz = (wz - posA.z) / sizeA.z;
int ax = Mathf.RoundToInt(normAx * (tDataA.alphamapWidth - 1));
int az = Mathf.RoundToInt(normAz * (tDataA.alphamapHeight - 1));
for (int l = 0; l < layers; l++)
merged[y, x, l] = splatA[az, ax, l];
filled = true;
}
// Якщо не потрапляє в A, перевіряємо terrainB
else if (wx >= posB.x && wx < posB.x + sizeB.x && wz >= posB.z && wz < posB.z + sizeB.z)
{
float normBx = (wx - posB.x) / sizeB.x;
float normBz = (wz - posB.z) / sizeB.z;
int bx = Mathf.RoundToInt(normBx * (tDataB.alphamapWidth - 1));
int bz = Mathf.RoundToInt(normBz * (tDataB.alphamapHeight - 1));
for (int l = 0; l < layers; l++)
merged[y, x, l] = splatB[bz, bx, l];
filled = true;
}
// Якщо не потрапляє ні в один — залишаємо чорний (все по 0)
}
}
targetTerrain.terrainData.SetAlphamaps(0, 0, merged);
Debug.Log("Splatmaps merged!");
}
}
#endif