#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 prototypes = new List(); 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 mergedTrees = new List(); 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 allDetails = new List(); 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