892 lines
38 KiB
C#
892 lines
38 KiB
C#
using Thousandto.Core.Asset;
|
||
using Thousandto.Core.Base;
|
||
using Thousandto.Package.Tool;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
namespace Thousandto.Editor.Test
|
||
{
|
||
public class SplitTerrain
|
||
{
|
||
float tRes =4.100001f;
|
||
|
||
static string SaveTerrainPath = "";
|
||
|
||
string _terrainName = "Terrain";
|
||
|
||
float progressUpdateInterval = 10000;
|
||
float HeightmapWidth;
|
||
float HeightmapHeight;
|
||
float BlockSize = TerrainConfigDefine.BLOCK_SIZE;
|
||
float DefaultVetexResolution = 90f;
|
||
//顶点对应像素的缩放,顶点x缩放=顶点的世界坐标
|
||
float PixelScale = 0;
|
||
|
||
bool _delExpandedMesh = false;
|
||
|
||
//替换法线时,有些顶点需要忽略
|
||
List<Vector3> ignoreVertices = new List<Vector3>();
|
||
|
||
public void StartSplitTerrain(Transform currentSelect)
|
||
{
|
||
StartSplitTerrain(currentSelect, DefaultVetexResolution, BlockSize);
|
||
}
|
||
|
||
public void StartSplitTerrain(string selectTerrainPath, float vertexResolution, float blockResolution, bool delExpandedMesh = false)
|
||
{
|
||
var terrainData = AssetDatabase.LoadAssetAtPath<TerrainData>(selectTerrainPath);
|
||
StartSplitTerrain(terrainData, vertexResolution, blockResolution, delExpandedMesh);
|
||
}
|
||
|
||
public void StartSplitTerrain(Transform currentSelect, float vertexResolution, float blockResolution, bool delExpandedMesh = false)
|
||
{
|
||
|
||
var terrainDat = currentSelect.GetComponent<Terrain>().terrainData;
|
||
StartSplitTerrain(terrainDat, vertexResolution, blockResolution, delExpandedMesh);
|
||
}
|
||
|
||
public void StartSplitTerrain(TerrainData terrainDat, float vertexResolution, float blockResolution, bool delExpandedMesh = false)
|
||
{
|
||
_terrainName = terrainDat.name;
|
||
_delExpandedMesh = delExpandedMesh;
|
||
|
||
SaveTerrainPath = ConfigDefine.GetSaveTerrainPath(_terrainName);
|
||
|
||
HeightmapWidth = terrainDat.heightmapResolution - 1;
|
||
HeightmapHeight = terrainDat.heightmapResolution - 1;
|
||
|
||
//每个分割点的像素大小
|
||
tRes = (HeightmapWidth) / vertexResolution;
|
||
var terrain = terrainDat;
|
||
//高度图的长宽, 高度图长宽减1才是真实的size
|
||
int w = terrain.heightmapResolution - 1;
|
||
int h = terrain.heightmapResolution - 1;
|
||
|
||
int collumns = (int)(vertexResolution);
|
||
int rows = (int)(vertexResolution);
|
||
|
||
//删除旧的mesh文件
|
||
DeleteOldMeshFiles(_terrainName);
|
||
//生成正式的prefab,需要删除旧的prefab
|
||
if (_delExpandedMesh)
|
||
DeleteOldMeshPrefab();
|
||
|
||
//以地形size为基准的每个分割点的像素大小
|
||
PixelScale = terrain.size.x / collumns;
|
||
var bounds = CalcBounds((int)(vertexResolution), (int)(vertexResolution), PixelScale, blockResolution);
|
||
//将terrain按照划分的边界拆分成若干个mesh
|
||
var configList = SplitTerrainWithBounds(bounds, terrain, (int)(vertexResolution), (int)(vertexResolution), blockResolution);
|
||
//保存成配置文件,在Resources/Config目录下
|
||
if(configList.Count > 0)
|
||
{
|
||
var saveConfigPath = ConfigDefine.GetConfigPath(TerrainConfigDefine.MESH_CONFIG_FILENAME);
|
||
File.WriteAllLines(saveConfigPath, configList.ToArray());
|
||
}
|
||
}
|
||
|
||
private List<string> SplitTerrainWithBounds(List<MyTerrainData> bounds, TerrainData terrain, int xVertexCount, int yVertexCount, float blockResolution)
|
||
{
|
||
//高度图的长宽, 高度图长宽减1才是真实的size
|
||
int w = terrain.heightmapResolution - 1;
|
||
int h = terrain.heightmapResolution - 1;
|
||
//地形尺寸
|
||
Vector3 meshScale = terrain.size;
|
||
//尺寸缩放比,y方向是高度,不做缩放
|
||
meshScale = new Vector3(meshScale.x / (h) * tRes, meshScale.y, meshScale.z / (w) * tRes);
|
||
//uv缩放比,每一个像素对应的纹理坐标
|
||
Vector2 uvScale = new Vector2((float)(1.0 / (w)), (float)(1.0 / (h)));
|
||
//地形的高度值
|
||
float[,] tData = terrain.GetHeights(0, 0, w, h);
|
||
int blockCollumn = (int)(xVertexCount * tRes / blockResolution);
|
||
if (blockCollumn == 0)
|
||
blockCollumn = 1;
|
||
|
||
//返回需要保存的配置信息
|
||
List<string> configList = new List<string>();
|
||
for (int i = 0; i < bounds.Count; ++i)
|
||
{
|
||
int curCollumn = i % blockCollumn;
|
||
int curRow = i / blockCollumn;
|
||
//bounds[i].Index = curRow + "x" + curCollumn;
|
||
Vector2 offset = new Vector2(bounds[i].StartX * bounds[i].PixelScaleX1000 / 1000 / terrain.size.x,
|
||
bounds[i].StartZ * bounds[i].PixelScaleX1000 / 1000 / terrain.size.z);
|
||
bounds[i].Offset = offset;
|
||
bounds[i].TerrainSize = terrain.size;
|
||
bounds[i].Index = "" + Utils.CalcIndex(bounds[i].GetCenterPos(), (int)blockResolution);
|
||
if (EditorUtility.DisplayCancelableProgressBar("Calc mesh", bounds[i].Index, i / (float)bounds.Count))
|
||
{
|
||
break;
|
||
}
|
||
|
||
var savePath = CreateMesh(curRow + "" + curCollumn, bounds[i], meshScale, uvScale, tData);
|
||
bool isFullMesh = bounds[i].StartX == 0 && bounds[i].StartZ == 0 && bounds[i].IsRightBound && bounds[i].IsBottomBound;
|
||
if (_delExpandedMesh && !isFullMesh)
|
||
{
|
||
AssetDatabase.ImportAsset(savePath);
|
||
|
||
var result = DeleteExpandedMesh(savePath);
|
||
//删除老的mesh
|
||
AssetDatabase.DeleteAsset(savePath);
|
||
|
||
//SetMaterial2(terrain, result);
|
||
var mat = GetSavedMeshMaterial(terrain, result);
|
||
var index = Utils.CalcTerrainMeshIndex(result, (int)blockResolution);
|
||
var savePrefabName = "Mesh_" + index;
|
||
if (CreateNewUnityMesh(ConfigDefine.GetSavePrefabPath(savePrefabName + ".prefab"), result, mat))
|
||
configList.Add(string.Format("{0}", index));
|
||
}
|
||
UnityEngine.Debug.Log(savePath);
|
||
}
|
||
|
||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||
EditorUtility.ClearProgressBar();
|
||
|
||
return configList;
|
||
}
|
||
|
||
public void ShowMeshBlocks()
|
||
{
|
||
var terrainMeshArray = Directory.GetFiles(SaveTerrainPath, "*.obj");
|
||
for (int i = 0; i < terrainMeshArray.Length; ++i)
|
||
{
|
||
ShowMeshBlock(terrainMeshArray[i]);
|
||
}
|
||
}
|
||
|
||
public static void ShowMeshBlock(string path)
|
||
{
|
||
if (GameObject.Find(Path.GetFileNameWithoutExtension(path)))
|
||
return;
|
||
|
||
var go = AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)) as GameObject;
|
||
if (go == null)
|
||
return;
|
||
|
||
var data = MyTerrainData.CreateTerrainDataFromFileName(path);
|
||
float pixelScale = data.PixelScaleX1000 / 1000f;
|
||
|
||
Vector3 position = new Vector3(data.StartX * pixelScale, 0, data.StartZ * pixelScale);
|
||
var instance = PrefabUtility.InstantiatePrefab(go) as GameObject;
|
||
instance.transform.position = position;
|
||
instance.name = go.name;
|
||
}
|
||
|
||
//bound x,y,z,w 对应z,x,w,h
|
||
public string CreateMesh(string boundIndex, MyTerrainData bound, Vector3 meshScale, Vector2 uvScale, float[,] tData)
|
||
{
|
||
int tDataRow = tData.GetLength(0);
|
||
int tDataCollumn = tData.GetLength(1);
|
||
|
||
//+1是把第一个点包含
|
||
int xLen = (int)(bound.Width - bound.StartZ) + 1;
|
||
int yLen = (int)(bound.Height - bound.StartX) + 1;
|
||
|
||
//边缘顶点,不做计算
|
||
if (xLen == 1 || yLen == 1)
|
||
return "";
|
||
|
||
//是否单一网格,即将terrain分割成1个网格
|
||
bool isSingleMesh = bound.StartX == 0 && bound.StartZ == 0 && bound.IsRightBound && bound.IsBottomBound;
|
||
|
||
#region 向4个方位拓展一个网格的范围
|
||
|
||
if(!isSingleMesh)
|
||
{
|
||
if (bound.StartZ > 0)
|
||
{
|
||
bound.StartZ -= 1;
|
||
xLen += 1;
|
||
}
|
||
if (bound.StartX > 0)
|
||
{
|
||
bound.StartX -= 1;
|
||
yLen += 1;
|
||
}
|
||
if (!bound.IsRightBound)
|
||
{
|
||
bound.Width += 1;
|
||
xLen += 1;
|
||
}
|
||
if (!bound.IsBottomBound)
|
||
{
|
||
bound.Height += 1;
|
||
yLen += 1;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
//顶点数,就是总共的格子数
|
||
Vector3[] tVertices = new Vector3[xLen * yLen];
|
||
Vector2[] tUV = new Vector2[(xLen) * (yLen)];
|
||
int y = 0;
|
||
int x = 0;
|
||
for (y = 0; y < yLen; y++)
|
||
{
|
||
for (x = 0; x < xLen; x++)
|
||
{
|
||
//tVertices[y*w + x] = Vector3.Scale(meshScale, new Vector3(x, tData[(int)(x*tRes),(int)(y*tRes)], y));
|
||
var hX = (int)((bound.StartZ + x) * tRes);
|
||
var hY = (int)((bound.StartX + y) * tRes);
|
||
if (hX >= tDataRow)
|
||
hX = tDataRow - 1;
|
||
if (hY >= tDataCollumn)
|
||
hY = tDataCollumn - 1;
|
||
|
||
try
|
||
{
|
||
tVertices[y * xLen + x] = Vector3.Scale(meshScale, new Vector3(-y, tData[hX, hY], x)); //Thank Cid Newman
|
||
tUV[y * xLen + x] = Vector2.Scale(new Vector2(y * tRes, x * tRes), uvScale);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
UnityEngine.Debug.LogException(ex);
|
||
}
|
||
}
|
||
}
|
||
|
||
y = 0;
|
||
x = 0;
|
||
//面数*2
|
||
int[] tPolys = new int[(xLen - 1) * (yLen - 1) * 6];
|
||
int index = 0;
|
||
for (y = 0; y < yLen - 1; y++)
|
||
{
|
||
for (x = 0; x < xLen - 1; x++)
|
||
{
|
||
tPolys[index++] = (y * xLen) + x;
|
||
tPolys[index++] = ((y + 1) * xLen) + x;
|
||
tPolys[index++] = (y * xLen) + x + 1;
|
||
|
||
tPolys[index++] = ((y + 1) * xLen) + x;
|
||
tPolys[index++] = ((y + 1) * xLen) + x + 1;
|
||
tPolys[index++] = (y * xLen) + x + 1;
|
||
}
|
||
}
|
||
|
||
int pixelScaleX1000 = (int)(PixelScale * 1000);
|
||
var savePath = SaveMesh(bound.CombineToFileName(), tVertices, tUV, tPolys);
|
||
return savePath;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除扩展的网格
|
||
/// </summary>
|
||
/// <param name="savePath"></param>
|
||
public static string DeleteExpandedMesh(string savePath)
|
||
{
|
||
if (!File.Exists(savePath))
|
||
return "";
|
||
var bound = MyTerrainData.CreateTerrainDataFromFileName(savePath);
|
||
Vector3[] realVertices;
|
||
Vector3[] realNormals;
|
||
Vector4[] realTangents;
|
||
Vector2[] realUvs;
|
||
int[] realTriangles;
|
||
if (DeleteExpandedMesh(savePath, bound, bound.PixelScaleX1000 / 1000f, out realVertices, out realNormals, out realTangents, out realUvs, out realTriangles))
|
||
{
|
||
if (!Directory.Exists(SaveTerrainPath))
|
||
Directory.CreateDirectory(SaveTerrainPath);
|
||
savePath = Path.Combine(SaveTerrainPath , bound.CombineToFileName(true) + ".obj");
|
||
savePath = SaveMesh(bound.CombineToFileName(true), realVertices, realUvs, realTriangles, realNormals, true);
|
||
AssetDatabase.ImportAsset(savePath);
|
||
}
|
||
|
||
return savePath;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存网格信息到obj文件,当有tNoramls数据时,说明是读取的网格顶点保存,这时需要反转顶点x方向和面的顶点需要逆时针方向
|
||
/// </summary>
|
||
/// <param name="name"></param>
|
||
/// <param name="tVertices"></param>
|
||
/// <param name="tUV"></param>
|
||
/// <param name="tPolys"></param>
|
||
/// <param name="tNormals"></param>
|
||
/// <param name="needReversX"></param>
|
||
/// <returns></returns>
|
||
public static string SaveMesh(string name, Vector3[] tVertices, Vector2[] tUV, int[] tPolys, Vector3[] tNormals = null, bool needReversX = false)
|
||
{
|
||
if (!Directory.Exists(SaveTerrainPath))
|
||
Directory.CreateDirectory(SaveTerrainPath);
|
||
string savePath = Path.Combine(SaveTerrainPath, name + ".obj");
|
||
StreamWriter sw = new StreamWriter(savePath);
|
||
try
|
||
{
|
||
sw.WriteLine("# T4M File");
|
||
System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("en-US");
|
||
for (int i = 0; i < tVertices.Length; i++)
|
||
{
|
||
if (needReversX)
|
||
{
|
||
tVertices[i].x *= -1;
|
||
}
|
||
|
||
UpdateProgress();
|
||
StringBuilder sb = new StringBuilder("v ", 20);
|
||
sb.Append(tVertices[i].x.ToString()).Append(" ").
|
||
Append(tVertices[i].y.ToString()).Append(" ").
|
||
Append(tVertices[i].z.ToString());
|
||
sw.WriteLine(sb);
|
||
}
|
||
|
||
for (int i = 0; i < tUV.Length; i++)
|
||
{
|
||
UpdateProgress();
|
||
StringBuilder sb = new StringBuilder("vt ", 22);
|
||
sb.Append(tUV[i].x.ToString()).Append(" ").
|
||
Append(tUV[i].y.ToString());
|
||
sw.WriteLine(sb);
|
||
}
|
||
|
||
if(tNormals != null)
|
||
{
|
||
for(int i = 0; i < tNormals.Length; ++i)
|
||
{
|
||
|
||
StringBuilder sb = new StringBuilder("vn ", 22);
|
||
sb.Append(tNormals[i].x.ToString()).Append(" ").
|
||
Append(tNormals[i].y.ToString()).Append(" ").
|
||
Append(tNormals[i].z.ToString());
|
||
sw.WriteLine(sb);
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < tPolys.Length; i += 3)
|
||
{
|
||
UpdateProgress();
|
||
StringBuilder sb = new StringBuilder("f ", 43);
|
||
if (tNormals != null)
|
||
{
|
||
//面上顶点的顺序要改成逆时针
|
||
sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
|
||
Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1).Append(" ").
|
||
Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1);
|
||
}
|
||
else
|
||
{
|
||
sb.Append(tPolys[i] + 1).Append("/").Append(tPolys[i] + 1).Append(" ").
|
||
Append(tPolys[i + 1] + 1).Append("/").Append(tPolys[i + 1] + 1).Append(" ").
|
||
Append(tPolys[i + 2] + 1).Append("/").Append(tPolys[i + 2] + 1);
|
||
}
|
||
|
||
sw.WriteLine(sb);
|
||
}
|
||
}
|
||
catch (Exception err)
|
||
{
|
||
Debug.Log("Error saving file: " + err.Message);
|
||
}
|
||
sw.Close();
|
||
AssetDatabase.SaveAssets();
|
||
|
||
return savePath;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除之前扩展的网格
|
||
/// </summary>
|
||
/// <param name="meshPath"></param>
|
||
/// <param name="originalData">网格信息</param>
|
||
/// <param name="pixcelScale">像素缩放比</param>
|
||
/// <param name="verticesOut">删除后有效的顶点数据</param>
|
||
/// <param name="normalsOut"></param>
|
||
/// <param name="tangentsOut"></param>
|
||
/// <param name="uvsOut"></param>
|
||
/// <param name="trianglesOut"></param>
|
||
/// <returns></returns>
|
||
public static bool DeleteExpandedMesh(string meshPath, MyTerrainData originalData, float pixcelScale,
|
||
out Vector3[] verticesOut, out Vector3[] normalsOut, out Vector4[] tangentsOut, out Vector2[] uvsOut, out int[] trianglesOut)
|
||
{
|
||
verticesOut = new Vector3[0];
|
||
tangentsOut = new Vector4[0];
|
||
normalsOut = new Vector3[0];
|
||
uvsOut = new Vector2[0];
|
||
trianglesOut = new int[0];
|
||
|
||
var go = AssetDatabase.LoadAssetAtPath<GameObject>(meshPath);
|
||
if (go == null)
|
||
return false;
|
||
var meshFilter = go.GetComponentInChildren<MeshFilter>();
|
||
if (meshFilter == null)
|
||
return false;
|
||
var mesh = meshFilter.sharedMesh;
|
||
{
|
||
var newVerticesList = new List<Vector3>();
|
||
var newVerticesNormals = new List<Vector3>();
|
||
var newVerticesTangents = new List<Vector4>();
|
||
var newUvs = new List<Vector2>();
|
||
var triangles = new List<int>();
|
||
|
||
//1. 无效顶点的索引
|
||
var invalidVericesIndexList = GetInvalidVerticeIndexs(mesh, originalData, pixcelScale);
|
||
var moreInvalidVerticeIndexs = new List<int>();
|
||
//2. 获取有效的面
|
||
triangles = GetValidTriangles(mesh, invalidVericesIndexList, out moreInvalidVerticeIndexs);
|
||
|
||
//这里考虑孤独点
|
||
//invalidVericesIndexList.AddRange(moreInvalidVerticeIndexs);
|
||
|
||
//3. 取有效的顶点、法线等
|
||
var replaceIndexDict = PickValidDatas(mesh, originalData, invalidVericesIndexList, out newVerticesList, out newVerticesNormals, out newVerticesTangents, out newUvs);
|
||
|
||
//4. 对三角面中的顶点索引进行新索引替换
|
||
for(int i = 0; i < triangles.Count; ++i)
|
||
{
|
||
EditorUtility.DisplayProgressBar("替换面的顶点索引", "替换面的顶点索引", i / (float)triangles.Count);
|
||
|
||
if (replaceIndexDict.ContainsKey( triangles[i]))
|
||
{
|
||
triangles[i] = replaceIndexDict[triangles[i]];
|
||
}
|
||
}
|
||
|
||
verticesOut = newVerticesList.ToArray();
|
||
tangentsOut = newVerticesTangents.ToArray();
|
||
normalsOut = newVerticesNormals.ToArray();
|
||
uvsOut = newUvs.ToArray();
|
||
trianglesOut = triangles.ToArray();
|
||
|
||
Vector2 offset = new Vector2(originalData.StartX * originalData.PixelScaleX1000 / 1000 / originalData.TerrainSize.x,
|
||
originalData.StartZ * originalData.PixelScaleX1000 / 1000 / originalData.TerrainSize.z);
|
||
originalData.Offset = offset;
|
||
return true;
|
||
}
|
||
}
|
||
|
||
private static List<int> GetInvalidVerticeIndexs(Mesh mesh, MyTerrainData originalData, float pixcelScale)
|
||
{
|
||
EditorUtility.DisplayProgressBar("获取无效的顶点", "", 0);
|
||
//左侧顶点的坐标
|
||
var leftVerteicPos = originalData.StartZ * pixcelScale;
|
||
var rightVerticesPos = (originalData.Width) * pixcelScale;
|
||
var topVerticesPos = originalData.StartX * pixcelScale;
|
||
var bottomVerticesPos = (originalData.Height) * pixcelScale;
|
||
|
||
var vertices = mesh.vertices;
|
||
//无效顶点的索引
|
||
var invalidVericesIndexList = new List<int>();
|
||
for (int i = 0; i < vertices.Length; ++i)
|
||
{
|
||
EditorUtility.DisplayProgressBar("获取无效的顶点", "获取无效的顶点", i / (float)vertices.Length);
|
||
//1. 第一次遍历,通过计算边缘顶点来获得需要去除的顶点索引
|
||
bool validate = (vertices[i].z > pixcelScale / 3) || !originalData.MoreLeft;
|
||
validate &= (rightVerticesPos - leftVerteicPos - vertices[i].z > pixcelScale / 3) || !originalData.MoreRight;
|
||
validate &= (vertices[i].x > pixcelScale / 3) || !originalData.MoreTop;
|
||
validate &= (bottomVerticesPos - topVerticesPos - vertices[i].x > pixcelScale / 3) || !originalData.MoreBottom;
|
||
if (!validate)
|
||
{
|
||
//无效的顶点索引
|
||
invalidVericesIndexList.Add(i);
|
||
}
|
||
}
|
||
|
||
return invalidVericesIndexList;
|
||
}
|
||
|
||
private static List<int> GetValidTriangles(Mesh mesh, List<int> invalidVericesIndexList, out List<int> noUseVertices)
|
||
{
|
||
var triangles = new List<int>();
|
||
|
||
//更多无效的顶点,去除一个三角形会遗留2个无效顶点,需要记录
|
||
noUseVertices = new List<int>();
|
||
for (int m = 0; m < mesh.triangles.Length; m += 3)
|
||
{
|
||
EditorUtility.DisplayProgressBar("获取有效的面", "获取有效的面", m / (float)mesh.triangles.Length);
|
||
|
||
bool isValid = true;
|
||
for (int i = 0; i < invalidVericesIndexList.Count; ++i)
|
||
{
|
||
//2. 通过无效顶点索引来计算无效的三角面片,同时,
|
||
//一个三角面片包含3个顶点,其余两个顶点也判定无效,需要加到无效列表中
|
||
if (mesh.triangles[m] == invalidVericesIndexList[i] ||
|
||
mesh.triangles[m + 1] == invalidVericesIndexList[i] ||
|
||
mesh.triangles[m + 2] == invalidVericesIndexList[i])
|
||
{
|
||
isValid = false;
|
||
AddVerticeIndex(mesh.triangles[m], invalidVericesIndexList, noUseVertices);
|
||
AddVerticeIndex(mesh.triangles[m + 1], invalidVericesIndexList, noUseVertices);
|
||
AddVerticeIndex(mesh.triangles[m + 2], invalidVericesIndexList, noUseVertices);
|
||
}
|
||
}
|
||
|
||
//剩余有效的三角面
|
||
if (isValid)
|
||
{
|
||
triangles.Add(mesh.triangles[m]);
|
||
triangles.Add(mesh.triangles[m + 1]);
|
||
triangles.Add(mesh.triangles[m + 2]);
|
||
|
||
//如果多余顶点里面有三角面使用到,则把顶点从孤独点列表中去掉,剩下的才是真正的孤独点
|
||
for (int i = 0; i < noUseVertices.Count; ++i)
|
||
{
|
||
if (noUseVertices.IndexOf(mesh.triangles[m]) >= 0)
|
||
{
|
||
noUseVertices.Remove(mesh.triangles[m]); continue;
|
||
}
|
||
if (noUseVertices.IndexOf(mesh.triangles[m + 1]) >= 0)
|
||
{ noUseVertices.Remove(mesh.triangles[m + 1]); continue; }
|
||
if (noUseVertices.IndexOf(mesh.triangles[m + 2]) >= 0)
|
||
{ noUseVertices.Remove(mesh.triangles[m + 2]); continue; }
|
||
}
|
||
}
|
||
}
|
||
|
||
return triangles;
|
||
}
|
||
|
||
private static Dictionary<int, int> PickValidDatas(Mesh mesh, MyTerrainData originalData, List<int> invalidVericesIndexList,
|
||
out List<Vector3> newVerticesList,
|
||
out List<Vector3> newVerticesNormals,
|
||
out List<Vector4> newVerticesTangents,
|
||
out List<Vector2> newUvs)
|
||
{
|
||
Dictionary<int, int> replaceIndexDict = new Dictionary<int, int>();
|
||
newVerticesList = new List<Vector3>();
|
||
newVerticesNormals = new List<Vector3>();
|
||
newVerticesTangents = new List<Vector4>();
|
||
newUvs = new List<Vector2>();
|
||
|
||
var vertices = mesh.vertices;
|
||
//第一个顶点,其他顶点需要减去第一个顶点
|
||
Vector3 firstVertice = Vector3.zero;
|
||
for (int i = 0; i < vertices.Length; ++i)
|
||
{
|
||
EditorUtility.DisplayProgressBar("获取有效的顶点、法线等", "获取有效的顶点、法线等", i / (float)vertices.Length);
|
||
|
||
//获取有效的顶点等数据
|
||
if (invalidVericesIndexList.IndexOf(i) == -1)
|
||
{
|
||
if (newVerticesList.Count == 0)
|
||
firstVertice = vertices[i];
|
||
|
||
//所有顶点要往x、z方向挪动一格
|
||
if (originalData.MoreLeft)
|
||
vertices[i].z -= firstVertice.z;
|
||
if (originalData.MoreTop)
|
||
vertices[i].x -= firstVertice.x;
|
||
|
||
//旧的顶点索引对应新的顶点索引
|
||
replaceIndexDict.Add(i, newVerticesList.Count);
|
||
var vertice = vertices[i];
|
||
newVerticesList.Add(vertice);
|
||
newVerticesNormals.Add(mesh.normals[i]);
|
||
newVerticesTangents.Add(mesh.tangents[i]);
|
||
newUvs.Add(mesh.uv[i]);
|
||
}
|
||
}
|
||
|
||
return replaceIndexDict;
|
||
}
|
||
|
||
public static void SetMaterial(TerrainData sourceTerrain, string savedMeshPath)
|
||
{
|
||
SetMaterial2(sourceTerrain, savedMeshPath, false);
|
||
}
|
||
|
||
public static void SetMaterial2(TerrainData sourceTerrain, string savedMeshPath, bool load = true)
|
||
{
|
||
var meshGo = AssetDatabase.LoadAssetAtPath<GameObject>(savedMeshPath);
|
||
var renderer = meshGo.GetComponentInChildren<MeshRenderer>();
|
||
|
||
renderer.sharedMaterial = GetSavedMeshMaterial(sourceTerrain, savedMeshPath);
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||
|
||
if(load)
|
||
ShowMeshBlock(savedMeshPath);
|
||
}
|
||
|
||
public static Material GetSavedMeshMaterial(TerrainData sourceTerrain, string savedMeshPath, bool replace = true)
|
||
{
|
||
string mapName = sourceTerrain.name;
|
||
string savedDir = Path.GetDirectoryName(savedMeshPath);
|
||
string meshName = Path.GetFileNameWithoutExtension(savedMeshPath);
|
||
string heightMapDir = ConfigDefine.GetControlMapPath(mapName);
|
||
if (!Directory.Exists(heightMapDir))
|
||
Directory.CreateDirectory(heightMapDir);
|
||
string controlImgPath = heightMapDir + "/control.png";
|
||
|
||
SaveControlMap(controlImgPath, sourceTerrain);
|
||
|
||
//Creation du Materiel
|
||
Material Tmaterial = CreateOrLoadMaterial(mapName, meshName);
|
||
|
||
if (!replace)
|
||
return Tmaterial;
|
||
|
||
var myTerrainData = MyTerrainData.CreateTerrainDataFromFileName(savedMeshPath);
|
||
|
||
//Recuperation des Texture du terrain
|
||
SplatPrototype[] texts = sourceTerrain.splatPrototypes;
|
||
for (int e = 0; e < texts.Length; e++)
|
||
{
|
||
if (e < 4)
|
||
{
|
||
var tileSize = texts[e].tileSize * 4f;
|
||
Tmaterial.SetTexture("_Splat" + e, texts[e].texture);
|
||
Tmaterial.SetTextureScale("_Splat" + e, tileSize);
|
||
//当前网格起始点对应平铺图的第几个,小数部分就是需要偏移的量
|
||
var tilePercentX = (myTerrainData.Offset.x * tileSize.x);
|
||
var tilePercentY = (myTerrainData.Offset.y * tileSize.y);
|
||
Tmaterial.SetTextureOffset("_Splat" + e, new Vector2(tilePercentX - (int)tilePercentX, tilePercentY - (int)tilePercentY));
|
||
}
|
||
}
|
||
|
||
//设置control图,已经其偏移
|
||
Texture heightMap = (Texture)AssetDatabase.LoadAssetAtPath(controlImgPath, typeof(Texture));
|
||
Tmaterial.SetTexture("_Control", heightMap);
|
||
Tmaterial.SetTextureOffset("_Control", myTerrainData.Offset);
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||
|
||
return Tmaterial;
|
||
}
|
||
|
||
public static bool CreateNewUnityMesh(string path, string savedMeshPath, Material mat)
|
||
{
|
||
var meshObjGo = AssetDatabase.LoadAssetAtPath<GameObject>(savedMeshPath);
|
||
if (!meshObjGo)
|
||
return false;
|
||
MyTerrainData myTd = MyTerrainData.CreateTerrainDataFromFileName(savedMeshPath);
|
||
var position = new Vector3(myTd.StartX * myTd.PixelScaleX1000 / 1000, 0, myTd.StartZ * myTd.PixelScaleX1000 / 1000);
|
||
GameObject rootGo = new GameObject(Path.GetFileNameWithoutExtension(path));
|
||
var go = new GameObject("default");
|
||
go.transform.parent = rootGo.transform;
|
||
go.transform.position = position;
|
||
|
||
var mf = go.AddComponent<MeshFilter>();
|
||
var renderer = go.AddComponent<MeshRenderer>();
|
||
renderer.sharedMaterial = mat;
|
||
|
||
var meshGo = AssetDatabase.LoadAssetAtPath<GameObject>(savedMeshPath);
|
||
mf.sharedMesh = meshGo.GetComponentInChildren<MeshFilter>().sharedMesh;
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
|
||
|
||
PrefabUtility.CreatePrefab(path, rootGo, ReplacePrefabOptions.ConnectToPrefab);
|
||
AssetDatabase.ImportAsset(path);
|
||
GameObject.DestroyImmediate(rootGo);
|
||
|
||
return true;
|
||
}
|
||
|
||
public static void ShowPrefab(string path)
|
||
{
|
||
var go = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||
go = PrefabUtility.InstantiatePrefab(go) as GameObject;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建或加载对应mesh的材质
|
||
/// </summary>
|
||
/// <param name="savedDir">保存路径</param>
|
||
/// <param name="meshName">mesh的名字</param>
|
||
/// <returns></returns>
|
||
private static Material CreateOrLoadMaterial(string mapName, string meshName)
|
||
{
|
||
Material Tmaterial;
|
||
string matPath = ConfigDefine.GetMateriaPath(mapName, meshName + ".mat");// string.Format(savedDir + "/{0}/{1}.mat", ConfigDefine.MatDirName, meshName);
|
||
string matRootDir = matPath.Substring(0, matPath.LastIndexOf("/"));
|
||
if (!Directory.Exists(matRootDir))
|
||
Directory.CreateDirectory(matRootDir);
|
||
if (File.Exists(matPath))
|
||
{
|
||
Tmaterial = AssetDatabase.LoadAssetAtPath<Material>(matPath);
|
||
Tmaterial.shader = Shader.Find(ConfigDefine.ShaderName);
|
||
}
|
||
else
|
||
{
|
||
Tmaterial = new Material(Shader.Find(ConfigDefine.ShaderName));
|
||
AssetDatabase.CreateAsset(Tmaterial, matPath);
|
||
AssetDatabase.ImportAsset(matPath, ImportAssetOptions.ForceUpdate);
|
||
AssetDatabase.Refresh();
|
||
}
|
||
|
||
return Tmaterial;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存terrain的control图
|
||
/// </summary>
|
||
/// <param name="path"></param>
|
||
/// <param name="sourceTerrain"></param>
|
||
public static void SaveControlMap(string path, TerrainData sourceTerrain)
|
||
{
|
||
if (!File.Exists(path))
|
||
{
|
||
//Control Texture Creation or Recuperation
|
||
string AssetName = AssetDatabase.GetAssetPath(sourceTerrain) as string;
|
||
UnityEngine.Object[] AssetName2 = AssetDatabase.LoadAllAssetsAtPath(AssetName);
|
||
if (AssetName2 != null && AssetName2.Length > 1)
|
||
{
|
||
for (var b = 0; b < AssetName2.Length; b++)
|
||
{
|
||
if (AssetName2[b].name == "SplatAlpha 0")
|
||
{
|
||
Texture2D texture = AssetName2[b] as Texture2D;
|
||
byte[] bytes = texture.EncodeToPNG();
|
||
File.WriteAllBytes(path, bytes);
|
||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||
}
|
||
}
|
||
}
|
||
|
||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||
|
||
//Modification de la Texture
|
||
TextureImporter TextureI = AssetImporter.GetAtPath(path) as TextureImporter;
|
||
TextureI.textureFormat = TextureImporterFormat.ARGB32;
|
||
TextureI.isReadable = true;
|
||
TextureI.anisoLevel = 9;
|
||
TextureI.mipmapEnabled = false;
|
||
TextureI.wrapMode = TextureWrapMode.Clamp;
|
||
AssetDatabase.Refresh();
|
||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||
}
|
||
}
|
||
|
||
private static void SetTextureReadable(Texture tex)
|
||
{
|
||
string path = AssetDatabase.GetAssetPath(tex);
|
||
if(File.Exists(path))
|
||
{
|
||
TextureImporter ti = TextureImporter.GetAtPath(path) as TextureImporter;
|
||
|
||
if (ti.isReadable)
|
||
return;
|
||
|
||
ti.isReadable = true;
|
||
AssetDatabase.Refresh();
|
||
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 把terrain分成若干个块,并且计算每个块的边界
|
||
/// </summary>
|
||
/// <param name="xVertexCount">x方向顶点个数</param>
|
||
/// <param name="yVertexCount">y方向顶点个数</param>
|
||
/// <param name="scale">像素缩放比</param>
|
||
/// <param name="blockSize">块大小</param>
|
||
/// <returns></returns>
|
||
private List<MyTerrainData> CalcBounds(int xVertexCount, int yVertexCount, float scale, float blockSize)
|
||
{
|
||
List<MyTerrainData> terrainDatas = new List<MyTerrainData>();
|
||
MyTerrainData bound = new MyTerrainData(_terrainName);
|
||
|
||
bool needCreateBound = true;
|
||
int startX = 0;
|
||
int startY = 0;
|
||
int blockCollumns = (int)((xVertexCount * scale) / blockSize) + 1;
|
||
if (blockCollumns == 0)
|
||
blockCollumns = 1;
|
||
for(int y = 0; y <= yVertexCount; ++y)
|
||
{
|
||
int curCollumn = terrainDatas.Count % blockCollumns;
|
||
int curRow = terrainDatas.Count / blockCollumns;
|
||
|
||
for (int x = startX; x <= xVertexCount; ++x)
|
||
{
|
||
if (needCreateBound)
|
||
{
|
||
bound = new MyTerrainData(_terrainName);
|
||
bound.StartZ = startX;
|
||
bound.StartX = startY;
|
||
needCreateBound = false;
|
||
}
|
||
|
||
//当前x方向坐标值大于第n列的块大小,则说明到临界值了
|
||
//x方向总是先达到临界值,然后等待y方向到临界值
|
||
if(x*scale >= blockSize * (curCollumn + 1))
|
||
{
|
||
startX = x;
|
||
break;
|
||
}
|
||
|
||
startX = x;
|
||
}
|
||
bound.Width = startX;
|
||
if(startX == xVertexCount)
|
||
{
|
||
bound.IsRightBound = true;
|
||
}
|
||
|
||
//当前y方向坐标值大于第n行的块大小,则说明到临界值了
|
||
//或者到最后,已经没有能满足大于第n行的块大小了,则直接添加进来
|
||
if (y * scale >= blockSize * (curRow + 1) || y + 1 > yVertexCount)
|
||
{
|
||
bound.Height = y;
|
||
if( y == yVertexCount)
|
||
{
|
||
bound.IsBottomBound = true;
|
||
}
|
||
needCreateBound = true;
|
||
|
||
bound.PixelScaleX1000 = (PixelScale * 1000);
|
||
terrainDatas.Add(bound);
|
||
|
||
var newRow = terrainDatas.Count / blockCollumns;
|
||
//换行了,则x从0开始
|
||
if (newRow > curRow)
|
||
{
|
||
startY = y;
|
||
startX = 0;
|
||
|
||
//到这里,已经换行了,但是y已经到头了,则退出计算
|
||
if (y + 1 > yVertexCount)
|
||
break;
|
||
}
|
||
|
||
//这里减1是因为下次循环会自动加1
|
||
y = y - 1;
|
||
}
|
||
}
|
||
|
||
return terrainDatas;
|
||
}
|
||
|
||
private static void AddVerticeIndex(int index, List<int> oldIndexList, List<int> newIndexList)
|
||
{
|
||
if (oldIndexList.IndexOf(index) >= 0)
|
||
return;
|
||
if (newIndexList.IndexOf(index) >= 0)
|
||
return;
|
||
|
||
newIndexList.Add(index);
|
||
}
|
||
|
||
static void UpdateProgress() { }
|
||
|
||
private static void DeleteOldMeshPrefab()
|
||
{
|
||
var path = ConfigDefine.GetSavePrefabPath();
|
||
var fileList = Directory.GetFiles(path, "*.prefab");
|
||
for(int i = 0; i < fileList.Length; ++i)
|
||
{
|
||
if (fileList[i].StartsWith("Mesh_"))
|
||
File.Delete(fileList[i]);
|
||
}
|
||
}
|
||
|
||
private static void DeleteOldMeshFiles(string mapName)
|
||
{
|
||
var path = ConfigDefine.GetSaveTerrainPath(mapName);
|
||
var fileList = Directory.GetFiles(path, "*");
|
||
for(int i = 0; i < fileList.Length; ++i)
|
||
{
|
||
File.Delete(fileList[i]);
|
||
}
|
||
}
|
||
}
|
||
}
|