Files
KopMap/Assets/NatureManufacture Assets/WorldStreamer/Scritps/Terrain/Editor/TerrainSplitter.cs
2025-09-02 18:55:19 +08:00

470 lines
20 KiB
C#

// /**
// * Created by Pawel Homenko on 11/2023
// */
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.TerrainTools;
namespace WorldStreamer2
{
public static class TerrainSplitter
{
private const float SplitProgressParts = 5f;
private static int _managerSettingsSplitSize;
private static TerrainManagerSettings _terrainManagerSettings;
private static string _progressCaption;
private static float _progressTerrains;
private static string _progressTextTerrainCount;
private static Material _MainPaintMaterial = TerrainPaintUtility.GetBuiltinPaintMaterial();
//enum type for painting on terrain
private enum PaintingType
{
Heightmap,
Hole,
Texture,
}
public static void SplitTerrain(TerrainManagerSettings managerSettings, bool allTerrains = false)
{
_terrainManagerSettings = managerSettings;
CreateDirectoryIfNotExists();
Undo.SetCurrentGroupName("Split Terrain");
_managerSettingsSplitSize = _terrainManagerSettings.splitSize;
_terrainManagerSettings.terrainsCount = _managerSettingsSplitSize * _managerSettingsSplitSize;
Terrain[] terrains = GetTerrains(allTerrains);
ProcessTerrains(terrains);
Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
}
private static Terrain[] GetTerrains(bool allTerrains)
{
return allTerrains ? Terrain.activeTerrains : Selection.GetFiltered<Terrain>(SelectionMode.TopLevel);
}
private static void CreateDirectoryIfNotExists()
{
string path = "Assets/" + _terrainManagerSettings.terrainsDataPath;
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
}
private static void ProcessTerrains(IReadOnlyCollection<Terrain> terrains)
{
if (!_MainPaintMaterial) _MainPaintMaterial = TerrainPaintUtility.GetBuiltinPaintMaterial();
int currentObjectIndex = 0;
foreach (Terrain terrain in terrains)
{
currentObjectIndex++;
if (terrain == null) continue;
Undo.RecordObject(terrain.gameObject, "Split Terrain");
_progressCaption = $"Splitting terrain {terrain.name} ({currentObjectIndex} of {terrains.Count})";
TerrainData terrainData = terrain.terrainData;
Vector3 terrainPosition = terrain.GetPosition();
GetTerrainRenderTextures(terrain, out RenderTexture heightMap, out RenderTexture holeTexture, out List<RenderTexture> alphamaps);
List<(Terrain terrain, TerrainData terrainData)> newTerrains = CreateNewTerrains(terrain);
SetNewTerrainsData(terrainPosition, terrainData, newTerrains, heightMap, holeTexture, alphamaps);
AssetDatabase.SaveAssets();
EditorUtility.ClearProgressBar();
terrain.gameObject.SetActive(false);
GC.Collect();
ReleaseRenderTextures(heightMap, holeTexture, alphamaps);
}
}
private static void SetNewTerrainsData(Vector3 terrainPosition, TerrainData terrainData, List<(Terrain terrain, TerrainData terrainData)> newTerrains, RenderTexture heightMap, RenderTexture holeTexture,
List<RenderTexture> alphamaps)
{
for (int i = 0; i < _terrainManagerSettings.terrainsCount; i++)
{
int xI = (i % _managerSettingsSplitSize);
int yI = (i / _managerSettingsSplitSize);
GetTerrainChunkBrushRect(terrainPosition, terrainData, xI, yI, out BrushTransform brushChunkXForm);
ProcessTerrainHeightGPU(newTerrains[i].terrain, brushChunkXForm, heightMap);
ProcessTerrainHoleGPU(newTerrains[i].terrain, brushChunkXForm, holeTexture);
ProcessTerrainAlphamapsGPU(terrainData, newTerrains[i].terrain, brushChunkXForm, alphamaps);
ProcessTerrainDetails(terrainData, newTerrains[i].terrainData, xI, yI);
ProcessTerrainTrees(terrainData, xI, yI, _progressTextTerrainCount, newTerrains[i].terrainData);
newTerrains[i].terrainData.SyncHeightmap();
}
}
private static List<(Terrain terrain, TerrainData terrainData)> CreateNewTerrains(Terrain terrain)
{
TerrainData terrainData = terrain.terrainData;
List<(Terrain terrain, TerrainData terrainData)> newTerrains = new();
for (int i = 0; i < _terrainManagerSettings.terrainsCount; i++)
{
newTerrains.Add(ProcessEachTerrainSplit(i, terrain, terrainData));
}
return newTerrains;
}
private static void ReleaseRenderTextures(RenderTexture heightMap, RenderTexture holeTexture, List<RenderTexture> alphamaps)
{
RenderTexture.ReleaseTemporary(heightMap);
RenderTexture.ReleaseTemporary(holeTexture);
// release all alphamaps textures
foreach (RenderTexture alphamap in alphamaps)
{
RenderTexture.ReleaseTemporary(alphamap);
}
}
private static void GetTerrainRenderTextures(Terrain terrain, out RenderTexture heightMap, out RenderTexture holeTexture, out List<RenderTexture> alphamaps)
{
TerrainData terrainData = terrain.terrainData;
BrushTransform brushXForm = GetTerrainBrushRect(terrain, terrainData);
heightMap = GetTerrainRenderTexture(terrain, brushXForm, PaintingType.Heightmap);
holeTexture = GetTerrainRenderTexture(terrain, brushXForm, PaintingType.Hole);
//get all alphamaps textures into list
alphamaps = new List<RenderTexture>();
for (int index = 0; index < terrainData.terrainLayers.Length; index++)
{
TerrainLayer terrainLayer = terrainData.terrainLayers[index];
RenderTexture alphamap = GetTerrainRenderTexture(terrain, brushXForm, PaintingType.Texture, terrainLayer);
alphamaps.Add(alphamap);
}
}
private static BrushTransform GetTerrainBrushRect(Terrain terrain, TerrainData terrainData)
{
//Vector3 terrainPosition = terrain.GetPosition();
Vector2 terrainSize = new(terrainData.size.x, terrainData.size.z);
//Vector2 brushPosition = new(terrainPosition.x, terrainPosition.z);
Rect brushRect = new(Vector2.zero, terrainSize);
BrushTransform brushXForm = BrushTransform.FromRect(brushRect);
return brushXForm;
}
private static (Terrain terrain, TerrainData terrainData) ProcessEachTerrainSplit(int i, Terrain terrain, TerrainData terrainData)
{
int xI = (i % _managerSettingsSplitSize);
int yI = (i / _managerSettingsSplitSize);
_progressTextTerrainCount = $"Generating terrain split {i}/terrainsCount";
_progressTerrains = i / (float)_terrainManagerSettings.terrainsCount;
EditorUtility.DisplayProgressBar(_progressCaption, _progressTextTerrainCount, _progressTerrains);
TerrainData newTerrainData = CreateNewTerrain(terrain, terrainData, i, xI, yI, out Terrain newTerrain);
return (newTerrain, newTerrainData);
}
private static TerrainData CreateNewTerrain(Terrain terrain, TerrainData terrainData, int i, int xI, int yI, out Terrain newTerrain)
{
TerrainData newTerrainData = GetNewTerrainData(terrainData, $"{terrainData.name} {xI}_{yI}");
GameObject newTerrainGo = Terrain.CreateTerrainGameObject(newTerrainData);
UndoRegisterCreatedObjects(newTerrainData, newTerrainGo);
newTerrainGo.name = $"{terrain.name} {xI}_{yI}";
newTerrain = AssignTerrainData(newTerrainGo, newTerrainData);
TerrainSettingsSetter.SetTerrainSettings(newTerrain, _terrainManagerSettings);
SetNewTerrainPosition(i, terrain, terrainData, newTerrainGo);
newTerrain.materialTemplate = terrain.materialTemplate;
CreateAssetForNewTerrain(newTerrain, _terrainManagerSettings, newTerrainData);
return newTerrainData;
}
private static void ProcessTerrainHoleGPU(Terrain newTerrain, BrushTransform brushChunkXForm, RenderTexture renderTexture)
{
PaintTerrain(newTerrain, brushChunkXForm, renderTexture, PaintingType.Hole);
}
private static void ProcessTerrainHeightGPU(Terrain newTerrain, BrushTransform brushChunkXForm, RenderTexture renderTexture)
{
PaintTerrain(newTerrain, brushChunkXForm, renderTexture, PaintingType.Heightmap);
}
private static void ProcessTerrainAlphamapsGPU(TerrainData terrainData, Terrain newTerrain, BrushTransform brushChunkXForm, List<RenderTexture> renderTexture)
{
int alphamapId = 0;
for (int index = 0; index < terrainData.terrainLayers.Length; index++)
{
TerrainLayer terrainLayer = terrainData.terrainLayers[index];
PaintTerrain(newTerrain, brushChunkXForm, renderTexture[index], PaintingType.Texture, terrainLayer);
string info = $"{_progressTextTerrainCount} (Processing splat {alphamapId})";
float currentProgress = (alphamapId / (float)terrainData.alphamapLayers);
if (ShowCancelableProgressBar(info, 2, currentProgress)) return;
alphamapId++;
}
}
private static PaintContext BeginPaint(Terrain terrain, BrushTransform brushXForm, PaintingType paintingType, TerrainLayer terrainLayer = null)
{
PaintContext paintContext;
switch (paintingType)
{
case PaintingType.Heightmap:
paintContext = TerrainPaintUtility.BeginPaintHeightmap(terrain, brushXForm.GetBrushXYBounds());
break;
case PaintingType.Hole:
paintContext = TerrainPaintUtility.BeginPaintHoles(terrain, brushXForm.GetBrushXYBounds());
break;
case PaintingType.Texture:
default:
paintContext = TerrainPaintUtility.BeginPaintTexture(terrain, brushXForm.GetBrushXYBounds(), terrainLayer);
break;
}
return paintContext;
}
private static void EndPainting(PaintingType paintingType, PaintContext paintContext)
{
switch (paintingType)
{
case PaintingType.Heightmap:
TerrainPaintUtility.EndPaintHeightmap(paintContext, "Terrain Paint");
break;
case PaintingType.Hole:
TerrainPaintUtility.EndPaintHoles(paintContext, "Terrain Paint");
break;
case PaintingType.Texture:
default:
TerrainPaintUtility.EndPaintTexture(paintContext, "Terrain Paint");
break;
}
}
private static void PaintTerrain(Terrain newTerrain, BrushTransform brushChunkXForm, RenderTexture renderTexture, PaintingType paintingType, TerrainLayer terrainLayer = null)
{
PaintContext paintContext = BeginPaint(newTerrain, brushChunkXForm, paintingType, terrainLayer);
TerrainPaintUtility.SetupTerrainToolMaterialProperties(paintContext, brushChunkXForm, _MainPaintMaterial);
Graphics.Blit(renderTexture, paintContext.destinationRenderTexture);
EndPainting(paintingType, paintContext);
}
private static RenderTexture GetTerrainRenderTexture(Terrain terrain, BrushTransform brushXForm, PaintingType paintingType, TerrainLayer terrainLayer = null)
{
PaintContext paintContext = BeginPaint(terrain, brushXForm, paintingType, terrainLayer);
TerrainPaintUtility.SetupTerrainToolMaterialProperties(paintContext, brushXForm, _MainPaintMaterial);
RenderTexture temp = RenderTexture.GetTemporary(paintContext.sourceRenderTexture.descriptor);
Graphics.Blit(paintContext.sourceRenderTexture, temp);
temp.filterMode = FilterMode.Point;
TerrainPaintUtility.ReleaseContextResources(paintContext);
return temp;
}
private static void GetTerrainChunkBrushRect(Vector3 terrainPosition, TerrainData terrainData, int xI, int yI, out BrushTransform brushChunkXForm)
{
Vector3 terrainSizeChunk = terrainData.size / _managerSettingsSplitSize;
//Vector2 chunkSize = new(terrainSizeChunk.x, terrainSizeChunk.z);
Vector2 terrainSize = new(terrainData.size.x, terrainData.size.z);
//Vector2 brushPosition = new Vector2(terrainPosition.x, terrainPosition.z) - new Vector2(yI * terrainSizeChunk.x, xI * terrainSizeChunk.z);
Vector2 brushPosition = - new Vector2(yI * terrainSizeChunk.x, xI * terrainSizeChunk.z);
//Debug.Log($"name: {terrainData.name} terrainSizeChunk: {terrainSizeChunk} terrainSize: {terrainSize} brushPosition: {brushPosition}");
Rect brushChunkRect = new(brushPosition, terrainSize);
brushChunkXForm = BrushTransform.FromRect(brushChunkRect);
}
private static void ProcessTerrainDetails(TerrainData terrainData, TerrainData newTerrainData, int xI, int yI)
{
int startX;
int startY;
newTerrainData.SetDetailResolution(terrainData.detailResolution / _managerSettingsSplitSize, terrainData.detailResolutionPerPatch);
int detailShift = terrainData.detailResolution / _managerSettingsSplitSize;
for (int d = 0; d < terrainData.detailPrototypes.Length; d++)
{
string info = $"{_progressTextTerrainCount} (Processing detail {d})";
float currentProgress = (d / (float)terrainData.detailPrototypes.Length);
if (ShowCancelableProgressBar(info, 3, currentProgress)) return;
int[,] terrainDetail = terrainData.GetDetailLayer(0, 0,
terrainData.detailResolution, terrainData.detailResolution, d);
int[,] partDetail = new int[detailShift, detailShift];
startX = startY = 0;
for (int x = startX; x < detailShift; x++)
{
for (int y = startY; y < detailShift; y++)
{
int detail = terrainDetail[x + detailShift * xI, y + detailShift * yI];
partDetail[x, y] = detail;
}
}
newTerrainData.SetDetailLayer(0, 0, d, partDetail);
}
}
private static TerrainData GetNewTerrainData(TerrainData terrainData, string name)
{
var newTerrainData = new TerrainData
{
name = name
};
#if !UNITY_2021
newTerrainData.SetDetailScatterMode(terrainData.detailScatterMode);
#endif
newTerrainData.terrainLayers = terrainData.terrainLayers;
newTerrainData.detailPrototypes = terrainData.detailPrototypes;
newTerrainData.treePrototypes = terrainData.treePrototypes;
newTerrainData.heightmapResolution = terrainData.heightmapResolution / _managerSettingsSplitSize;
//Debug.Log($"heightmapResolution: {terrainData.heightmapResolution} newTerrainData.heightmapResolution: {newTerrainData.heightmapResolution} managerSettingsSplitSize: {_managerSettingsSplitSize}");
newTerrainData.size = new Vector3(terrainData.size.x / _managerSettingsSplitSize, terrainData.size.y, terrainData.size.z / _managerSettingsSplitSize);
//Debug.Log($"terrainData.size: {terrainData.size} newTerrainData.size: {newTerrainData.size} managerSettingsSplitSize: {_managerSettingsSplitSize}");
return newTerrainData;
}
private static void SetNewTerrainPosition(int i, Terrain terrain, TerrainData terrainData, GameObject newTerrainGo)
{
float spaceShiftX = terrainData.size.z / _managerSettingsSplitSize;
float spaceShiftY = terrainData.size.x / _managerSettingsSplitSize;
float xWShift = (i % _managerSettingsSplitSize) * spaceShiftX;
// ReSharper disable once PossibleLossOfFraction
float zWShift = (i / _managerSettingsSplitSize) * spaceShiftY;
Vector3 parentPosition = terrain.GetPosition();
Vector3 position = newTerrainGo.transform.position;
position = parentPosition + new Vector3(position.x + zWShift, position.y, position.z + xWShift);
newTerrainGo.transform.position = position;
}
private static void ProcessTerrainTrees(TerrainData terrainData, int xI, int yI, string progressText, TerrainData newTerrainData)
{
float size = 1 / (float)_managerSettingsSplitSize;
float sizeCheckX = size * xI;
float sizeCheckX1 = size * (xI + 1);
float sizeCheckY = size * yI;
float sizeCheckY1 = size * (yI + 1);
List<TreeInstance> treeInstancesList = new();
TreeInstance ti;
TreeInstance[] treeInstances = terrainData.treeInstances;
for (int t = 0; t < treeInstances.Length; t++)
{
if (t % 1000 == 0)
{
string info = $"{progressText} (Processing trees {t}/{treeInstances.Length})";
float currentProgress = ((float)t / treeInstances.Length);
if (ShowCancelableProgressBar(info, 4, currentProgress)) return;
}
ti = treeInstances[t];
if (!(ti.position.x >= sizeCheckY) || !(ti.position.x <= sizeCheckY1) ||
!(ti.position.z >= sizeCheckX) || !(ti.position.z <= sizeCheckX1)) continue;
ti.position = new Vector3((ti.position.x * _managerSettingsSplitSize) % 1,
ti.position.y, (ti.position.z * _managerSettingsSplitSize) % 1);
treeInstancesList.Add(ti);
}
//Debug.Log("end");
newTerrainData.SetTreeInstances(treeInstancesList.ToArray(), true);
}
private static bool ShowCancelableProgressBar(string info, int partNumber, float currentProgress)
{
float terrainsCountProgress = _progressTerrains + (partNumber / SplitProgressParts + currentProgress / SplitProgressParts) / _terrainManagerSettings.terrainsCount;
bool cancel = EditorUtility.DisplayCancelableProgressBar(_progressCaption, info, terrainsCountProgress);
if (!cancel) return false;
Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
EditorUtility.ClearProgressBar();
return true;
}
private static void UndoRegisterCreatedObjects(TerrainData td, GameObject tgo)
{
Undo.RegisterCreatedObjectUndo(td, "Create terrain data");
Undo.RegisterCreatedObjectUndo(tgo, "Create terrain split");
}
private static Terrain AssignTerrainData(GameObject tgo, TerrainData td)
{
Terrain newTerrain = tgo.GetComponent<Terrain>();
newTerrain.terrainData = td;
newTerrain.gameObject.AddComponent<TerrainCullingSystem>();
return newTerrain;
}
private static void CreateAssetForNewTerrain(Terrain terrain, TerrainManagerSettings managerSettings, TerrainData terrainData)
{
AssetDatabase.CreateAsset(terrainData, "Assets" + managerSettings.terrainsDataPath + terrain.name + ".asset");
}
}
}