254 lines
9.7 KiB
C#
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 |