Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
@@ -0,0 +1,635 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Windows;
namespace XGame
{
class TerrainBlock
{
public Color32 m_BaseMapIndex;
public Color32[] m_MapIndex;
public Color[] m_MapMix;
public float[] m_Height;
}
class TerrainObject
{
//m_BlockGridSize = 32
//lv0 512*512 = 32*32单块) * 16 * 16 (总共256块)
//lv1 256*256 = 32*32单块) * 8 * 8 64块)
//lv2 128*128 = 32*32单块) * 4 * 4 16块)
//lv3 64*64 = 32*32单块) * 2 * 2 4块)
class MeshObj
{
public GameObject m_Object;
public Mesh m_Mesh;
public MeshObj[] m_MeshSubObj;//四叉分割 32*32 64*64 128*128 256*256 512*512 1024*1024
}
string[] m_strBaseMapList;
// 预先定义 16 种颜色
static Color[] m_Colors = new Color[]
{
new Color(255, 0, 0), // 红色
new Color(255, 128, 0), // 橙色
new Color(255, 255, 0), // 黄色
new Color(128, 255, 0), // 淡绿色
new Color(0, 255, 0), // 绿色
new Color(0, 255, 128), // 青色
new Color(0, 255, 255), // 蓝绿色
new Color(0, 128, 255), // 淡蓝色
new Color(0, 0, 255), // 蓝色
new Color(128, 0, 255), // 紫色
new Color(255, 0, 255), // 粉红色
new Color(255, 0, 128), // 桃红色
new Color(128, 128, 128), // 灰色
new Color(192, 192, 192), // 淡灰色
new Color(255, 255, 255), // 白色
new Color(0, 0, 0), // 黑色
};
GameObject m_GameObject;
Transform m_Transform;
MeshObj m_Top;
MeshObj[] m_BlockMeshObjList;
TerrainBlock[] m_TerrainBlock;
Texture2D[] m_BaseMapTexture;
Texture2D[] m_NormalMapTexture;
Texture2D m_ControlTexture;
Material m_mtlTerrain;
string m_Name;//xxxzzz.grd
static Color32 m_Index;// = new int[4];
static Color m_Degreed;// = new float[4];
static Color[] m_nearestColors = new Color[4];
public static float m_HeightScale = 20.0f;
public static float m_UVScale = 4.0f;//1-32-贴图大小
public static int m_BlockGridSize = 32;//32*32
void SetCorlorBase(Color[] colors)
{
m_Colors = colors;
}
//通过heightMap生成
public IEnumerator CreateGroundObjectFromPic(int xPos, int zPos, string heightMap, string mixMap, string[] baseMap, string[] normalMap, Transform trParent )
{
Debug.Log("CreateGroundObjectFromPic");
if (baseMap.Length != normalMap.Length)
{
Debug.Log("baseMap Count Must = normalMap Count");
yield break;
}
if (baseMap.Length < 4)
{
Debug.Log("baseMap Must >= 4");
yield break;
}
DateTime oriTime = DateTime.Now;
yield return XResLoader.coLoadAllRes(new string[] { "res/scene/ground/mat/Terrain.mat" }, typeof(Material), objs =>
{
Debug.Log("coLoadAllRes Terrain.mat finish");
if (objs != null && objs[0] != null)
{
m_mtlTerrain = objs[0] as Material;
}
});
if(m_mtlTerrain == null)
{
Debug.Log("Terrian Mtl Load Failed : res/scene/ground/mat/Terrain.mat");
yield break;
}
m_BaseMapTexture = new Texture2D[baseMap.Length];
m_NormalMapTexture = new Texture2D[normalMap.Length];
Texture2D texture = null;
Texture2D mixTexture = null;
//byte[] imageData;
//texture = new Texture2D(2, 2);
//imageData = File.ReadAllBytes(mixMap);
//yield return XResLoader.LoadRes(mixMap, typeof(Texture2D), objs =>
yield return XResLoader.coLoadAllRes(new string[] { mixMap }, typeof(Texture2D), objs =>
{
if (objs != null)
{
mixTexture = objs[0] as Texture2D;
if (mixTexture.width != mixTexture.height)
{
Debug.Log("Height Must == Width");
}
if (!mixTexture.isReadable)
{
Debug.Log("mixTexture.isReadable false");
#if UNITY_EDITOR
string texturePath = UnityEditor.AssetDatabase.GetAssetPath(mixTexture);
UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath);
textureImporter.isReadable = true;
UnityEditor.AssetDatabase.ImportAsset(texturePath);
#endif
}
}
});
if(mixTexture == null)
yield break;
//texture.LoadImage(imageData);
Texture2D texHeight = null;// = new Texture2D(2, 2);
//imageData = File.ReadAllBytes(heightMap);
//texHeight.LoadImage(imageData);
//yield return XResLoader.LoadRes(heightMap, typeof(Texture2D), objs =>
yield return XResLoader.coLoadAllRes(new string[] { heightMap }, typeof(Texture2D), objs =>
{
if (objs != null)
{
texHeight = objs[0] as Texture2D;
if (texHeight.width != mixTexture.width || texHeight.height != mixTexture.height)
{//比较两个图的大小
Debug.Log("HeightMap Size != MixMap Size");
}
if (!texHeight.isReadable)
{
Debug.Log("texHeight.isReadable false");
#if UNITY_EDITOR
string texturePath = UnityEditor.AssetDatabase.GetAssetPath(texHeight);
UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath);
textureImporter.isReadable = true;
UnityEditor.AssetDatabase.ImportAsset(texturePath);
#endif
}
}
});
if (texHeight == null)
yield break;
m_GameObject = new GameObject($"Terrain{xPos}{zPos}");
m_Transform = m_GameObject.transform;
m_Transform.SetParent(trParent);
m_Transform.position = new Vector3(xPos, 0, zPos);
m_strBaseMapList = baseMap;
//baseMap列表生成m_Colors列表
m_Colors = new Color[baseMap.Length];
Color totalColor = Color.black;
int pixelCount = 0;
Debug.Log("baseMap Load");
//XResLoader.LoadRes(baseMap, typeof(Texture2D), objs=>
yield return XResLoader.coLoadAllRes(baseMap, typeof(Texture2D), objs =>
{
Debug.Log($"baseMap load {objs.Length} ");
for (int i = 0; i < objs.Length; ++i)
{
if (objs[i] != null)
m_BaseMapTexture[i] = objs[i] as Texture2D;
else
{
Debug.Log($"m_BaseMapTexture {normalMap[i]} Failed");
}
}
});
Debug.Log("normalMap Load");
yield return XResLoader.coLoadAllRes(normalMap, typeof(Texture2D), objs =>
{
Debug.Log($"normalMap load {objs.Length} ");
for (int i = 0; i < objs.Length; ++i)
{
if (objs[i] != null)
m_NormalMapTexture[i] = objs[i] as Texture2D;
else
{
Debug.Log($"normalMap {normalMap[i]} Failed");
}
}
});
Debug.Log($"m_BaseMapTexture Color {m_BaseMapTexture.Length}");
for (int i = 0; i < m_BaseMapTexture.Length; ++i)
{
texture = m_BaseMapTexture[i];
if (texture == null)
{
Debug.Log($"texture {i} == null");
continue;
}
if (!texture.isReadable)
{
Debug.Log($"texture.isReadable {texture.name} Failed");
#if UNITY_EDITOR
string texturePath = UnityEditor.AssetDatabase.GetAssetPath(texture);
UnityEditor.TextureImporter textureImporter = (UnityEditor.TextureImporter)UnityEditor.AssetImporter.GetAtPath(texturePath);
textureImporter.isReadable = true;
UnityEditor.AssetDatabase.ImportAsset(texturePath);
#endif
}
for (int j = 0; j < texture.width; j = j + 4)
{
for (int k = 0; k < texture.height; k = k + 4)
{
Color pixelColor = texture.GetPixel(j, k);
totalColor += pixelColor;
pixelCount++;
}
}
// 计算平均颜色
m_Colors[i] = totalColor / pixelCount;
}
Debug.Log($"Color List OK {(DateTime.Now - oriTime).TotalMilliseconds}");
//mixMap rgba决定此点是哪四个basemap且分别是多少混合比例
//mix[513*513][4](index, alpha), 32 * 32 缩减到4种地形(g1 g2 g3 g4,
//0-32 33-64 (33列需要特殊处理)
//相邻区块如果没有相同的就把最小那个改为主块的g1
//读取32*32格子关联边的数据提取两个最大的(g1 g2),然后强设置进去只有两种alpha,
//中心点设置为唯一颜色(最大g1
//边界混合问题
int BlockWidth = mixTexture.width / m_BlockGridSize;
int BlockHeight = mixTexture.height / m_BlockGridSize;
int[] BaseMapCount = new int[baseMap.Length];
m_TerrainBlock = new TerrainBlock[BlockWidth* BlockHeight];
for (int j = 0; j < BlockHeight; j++)
{
for (int i = 0; i < BlockWidth; ++i)
{
Color32[] BaseMapIndex = new Color32[m_BlockGridSize * m_BlockGridSize];
Color[] BaseMapMix = new Color[m_BlockGridSize * m_BlockGridSize];
TerrainBlock tBlock = new TerrainBlock();
tBlock.m_MapIndex = BaseMapIndex;
tBlock.m_MapMix = BaseMapMix;
Array.Clear(BaseMapCount, 0, BaseMapCount.Length);
int l, k;
for (k = 0; k < m_BlockGridSize; k++)
{
for (l = 0; l < m_BlockGridSize; l++)
{
Color pixelColor = mixTexture.GetPixel(i* m_BlockGridSize+k, j* m_BlockGridSize+l);
GetNearColorIndex(pixelColor, ref BaseMapIndex[l * m_BlockGridSize + k], ref BaseMapMix[l * m_BlockGridSize + k]);
BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][0]]++;
BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][1]]++;
BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][2]]++;
BaseMapCount[BaseMapIndex[l * m_BlockGridSize + k][3]]++;
}
}
//留下最高数量的四个BaseMapCount[0-3]
Color32 BaseMapIndexMax = new Color32(0,0,0,0);
int[] BaseMapCountMax = new int[4];
for ( k = 0; k < 4; k++)
{
for ( l = 0; l < BaseMapCount.Length; l++)
{
if (BaseMapCountMax[k] < BaseMapCount[l])
{
BaseMapCountMax[k] = BaseMapCount[l];
BaseMapIndexMax[k] = (byte)l; //(byte)l;
}
}
BaseMapCount[BaseMapIndexMax[k]] = 0;//最终选定的最大数位置
}
//全部混合顺序要变为四个统一
byte temp = 0;
float fMix;
for ( k = 0; k < m_BlockGridSize * m_BlockGridSize; k++)
{
for( l = 0; l < 4; ++l)
{
bool bfound = false;
if (BaseMapIndexMax[l] != BaseMapIndex[k][l])
{
if (l == 3)
{
bfound = false;
}
else
for (int m = l+1; m < 4; m++)
{
if (BaseMapIndex[k][m] == BaseMapIndexMax[l])
{//替换
if (l != m)
{
fMix = BaseMapMix[k][m];
BaseMapMix[k][m] = BaseMapMix[k][l];
BaseMapMix[k][l] = fMix;
temp = BaseMapIndex[k][m];
BaseMapIndex[k][m] = BaseMapIndex[k][l];
BaseMapIndex[k][l] = temp;
}
bfound = true;
break;
}
}
if (!bfound)
{
for (int m = l+1; m < 4; m++)
{
if (BaseMapIndexMax[m] == BaseMapIndex[k][l])
{//顺序错了而已
bfound = true;
fMix = BaseMapMix[k][m];
BaseMapMix[k][m] = BaseMapMix[k][l];
BaseMapMix[k][l] = fMix;
temp = BaseMapIndex[k][m];
BaseMapIndex[k][m] = BaseMapIndex[k][l];
BaseMapIndex[k][l] = temp;
if (BaseMapIndexMax[l] != BaseMapIndex[k][l])
{//如果换了后仍然不同,则需要重新判断一遍
m = l;//再判断一遍
bfound = false;
}
}
}
if (!bfound)
{//完全找不到直接替换(不是最大的四个),混合参数BaseMapMix不用改
BaseMapIndex[k][l] = BaseMapIndexMax[l];
}
}
}
}
}
tBlock.m_BaseMapIndex = BaseMapIndexMax;
m_TerrainBlock[j * BlockWidth + i] = tBlock;
}
}
Debug.Log($"Block Create OK{(DateTime.Now - oriTime).TotalMilliseconds}");
//所有区块都初始化好了,但是边缘是可能对不上的,需要修正
//组合出混合控制贴图
m_ControlTexture = new Texture2D(mixTexture.width, mixTexture.height);
for (int j = 0; j < BlockHeight; j++)
{
for (int i = 0; i < BlockWidth; ++i)
{
TerrainBlock tBlock = m_TerrainBlock[j * BlockWidth + i];
int beginX = i * m_BlockGridSize;
int beginY = j * m_BlockGridSize;
for (int y = 0; y < m_BlockGridSize; y++)
{
for (int x = 0; x < m_BlockGridSize; x++)
{
m_ControlTexture.SetPixel(beginX + x, beginY + y, ChangeColor(tBlock.m_MapMix[x + y * m_BlockGridSize]));
}
}
}
}
m_ControlTexture.Apply();
Debug.Log($"ControlTexture Create OK{(DateTime.Now - oriTime).TotalMilliseconds}");
//height map 决定地表多边形的高度值
float[] mapHeight = new float[texHeight.width * texHeight.height];
for (int j = 0; j < texHeight.height; ++j)
{
for (int i = 0; i < texHeight.width; ++i)
{
Color pixelColor = texHeight.GetPixel(i, j);
mapHeight[i + j * texHeight.width] = GetHeight(pixelColor, m_HeightScale);
}
}
Debug.Log($"height map Create OK{(DateTime.Now - oriTime).TotalMilliseconds}");
//构造真正的Mesh
m_BlockMeshObjList = new MeshObj[BlockWidth * BlockHeight];
//修改uv缩放为贴图大小,适应control图,其他图按照缩放比例
m_UVScale = mixTexture.width;
#if !UNITY_WEBGL
Task<int>[] task = new Task<int>[BlockWidth];
for (int i = 0; i < BlockWidth; ++i)
{
task[i] = Task.Run(() => CalMeshTask(m_TerrainBlock, mapHeight, m_BlockMeshObjList, i, BlockWidth));
}
for (int i = 0; i < BlockWidth; ++i)
{
task[i].Wait();
}
#else
for (int i = 0; i < BlockWidth; ++i)// //test
{
CalMeshTask(m_TerrainBlock, mapHeight, m_BlockMeshObjList, i, BlockWidth);
}
#endif
Debug.Log($"Mesh Create OK{(DateTime.Now - oriTime).TotalMilliseconds}");
//区块生成后把所有节点放到总对象下
for (int i = 0; i < m_BlockMeshObjList.Length; ++i)
{
if(m_BlockMeshObjList[i] != null)
m_BlockMeshObjList[i].m_Object.transform.SetParent(m_Transform);
}
}
static float ColorScale = 3f;
Color ChangeColor(Color color)
{ //超过0.66的设置为全色, 0.5以上为增比例,0.5一下为减比例
Color rc = color;
int i = 0, index = -1;
float cmax = 0;
for ( i = 0; i < 4; ++i)
{
if (color[i] > cmax)
{
cmax = color[i];
index = i;
}
//if (color[i] > 0.5f)
//{
// rc[i] = (color[i] - 0.5f) * ColorScale + 0.5f;
//}
//else
//{
// rc[i] = color[i] / ColorScale;
//}
}
rc[index] = rc[index] * ColorScale;
Vector4 v4 = rc;
v4.Normalize();
return v4;
}
//方便大部分平台分线程计算
int CalMeshTask(TerrainBlock[] BlockList, float[] mapHeight, MeshObj[] BlockMeshObjList, int WidthIndex, int BlockWidth)
{
int BlockHeight = BlockWidth;
int mapWidth = BlockWidth * m_BlockGridSize;
for (int j = 0; j < BlockHeight; j++)
{
TerrainBlock tBlock = BlockList[j * BlockWidth + WidthIndex];
MeshObj mObj = new MeshObj();//需要填充的模型对象
BlockMeshObjList[j * BlockWidth + WidthIndex] = mObj;
// 创建一个新的游戏对象
mObj.m_Object = new GameObject($"T{WidthIndex}_{j}");
// 添加MeshFilter组件
MeshFilter meshFilter = mObj.m_Object.AddComponent<MeshFilter>();
// 创建Mesh
Mesh mesh = new Mesh();
mObj.m_Mesh = mesh;
// 设置顶点和三角形数据
Vector3[] vertices = new Vector3[(m_BlockGridSize+1)*(m_BlockGridSize+1)];
Vector3[] normals;// = new Vector3[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)];
Vector2[] uvs = new Vector2[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)];
//Vector2[] uvs2 = new Vector2[(m_BlockGridSize + 1) * (m_BlockGridSize + 1)];
int[] triangles = new int[6* m_BlockGridSize* m_BlockGridSize];
float height = 0;
int x, y;
int beginPos = j * m_BlockGridSize * mapWidth + WidthIndex * m_BlockGridSize;
//顶点 uv
for (int k = 0; k < m_BlockGridSize+1; ++k)
{
for (int l = 0; l < m_BlockGridSize+1; ++l)
{//每个格子是两个三角形
if (WidthIndex == BlockWidth - 1 && l == m_BlockGridSize)
x = l - 1;
else
x = l;
if (j == BlockHeight - 1 && k == m_BlockGridSize)
y = k - 1;
else
y = k;
height = mapHeight[beginPos + mapWidth * y + x];
vertices[k * (m_BlockGridSize+1) + l] = new Vector3(l, height, k);
//normals[k * (m_BlockGridSize + 1) + l] = Vector3.up;//简化
//Control uv
uvs[k * (m_BlockGridSize + 1) + l].x = ((float)l + WidthIndex*m_BlockGridSize) / m_UVScale;
uvs[k * (m_BlockGridSize + 1) + l].y = ((float)k + j*m_BlockGridSize) / m_UVScale;
//Control uv 不需要, 都用比例
//uvs2[k * (m_BlockGridSize + 1) + l] = new Vector2((float)(WidthIndex* BlockWidth+l) / (float)mapWidth, (float)(j * BlockWidth + k)/ (float)mapWidth);
}
}
//法线
//三角形
for (int k = 0; k < m_BlockGridSize; ++k)
{
for (int m = 0; m < m_BlockGridSize; ++m)
{
triangles[6 * (k * m_BlockGridSize + m)] = k * (m_BlockGridSize+1) + m;
triangles[6 * (k * m_BlockGridSize + m)+2] = k * (m_BlockGridSize + 1) + m+1;
triangles[6 * (k * m_BlockGridSize + m)+1] = (k+1) * (m_BlockGridSize + 1) + m;
triangles[6 * (k * m_BlockGridSize + m)+3] = k * (m_BlockGridSize + 1) + m +1;
triangles[6 * (k * m_BlockGridSize + m)+4] = (k+1) * (m_BlockGridSize + 1) + m;
triangles[6 * (k * m_BlockGridSize + m)+5] = (k+1) * (m_BlockGridSize + 1) + m+1;
}
}
// 这里假设已经按照上面的方法设置了顶点和三角形数据
mesh.vertices = vertices;
//mesh.normals = normals;
mesh.triangles = triangles;
mesh.uv = uvs;
//mesh.uv2 = uvs2;//control uv
mesh.RecalculateNormals();
normals = mesh.normals;
for (int k = 0; k < m_BlockGridSize + 1; ++k)
{
normals[k] = Vector3.up;//第一行
normals[m_BlockGridSize * (m_BlockGridSize + 1) + k] = Vector3.up;//最后一行
}
for (int k = 1; k < m_BlockGridSize; ++k)
{
normals[k * (m_BlockGridSize+1)] = Vector3.up;//第一列
normals[(k+1) * (m_BlockGridSize + 1) - 1] = Vector3.up;//最后一列
}
mesh.normals = normals;
// 将Mesh赋值给MeshFilter
meshFilter.mesh = mesh;
// 添加MeshRenderer组件
MeshRenderer meshRenderer = mObj.m_Object.AddComponent<MeshRenderer>();
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
// 设置材质和渲染模式
// 这里假设已经有一个名为"Material"的材质
//meshRenderer.material = Resources.Load<Material>("Material");
meshRenderer.material = new Material(m_mtlTerrain);
meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.On;
meshRenderer.receiveShadows = true;
meshRenderer.material.SetTexture("_Splat0", m_BaseMapTexture[tBlock.m_MapIndex[0][0]]);
meshRenderer.material.SetTexture("_Normal0", m_NormalMapTexture[tBlock.m_MapIndex[0][0]]);
meshRenderer.material.SetTexture("_Splat1", m_BaseMapTexture[tBlock.m_MapIndex[0][1]]);
meshRenderer.material.SetTexture("_Normal1", m_NormalMapTexture[tBlock.m_MapIndex[0][1]]);
meshRenderer.material.SetTexture("_Splat2", m_BaseMapTexture[tBlock.m_MapIndex[0][2]]);
meshRenderer.material.SetTexture("_Normal2", m_NormalMapTexture[tBlock.m_MapIndex[0][2]]);
meshRenderer.material.SetTexture("_Splat3", m_BaseMapTexture[tBlock.m_MapIndex[0][3]]);
meshRenderer.material.SetTexture("_Control", m_ControlTexture);
Vector4 uvScale = new Vector4(48f, 64f, 48f, 48f); // 假设要设置的值是(1.0, 1.0, 0.0, 0.0)
meshRenderer.material.SetVector("_UvScale0", uvScale);
//位置
mObj.m_Object.transform.position = new Vector3(WidthIndex * m_BlockGridSize, 0, j * m_BlockGridSize);
}
return 0;
}
static float GetHeight(Color color, float HeightScale = 1.0f)
{
return HeightScale > 0 ? GetGrayScale(color) * HeightScale : GetGrayScale(color) * HeightScale;
}
static float GetGrayScale(Color color)
{
return 0.299f * color.r + 0.587f * color.g + 0.114f * color.b;
}
static void GetNearColorIndex(Color color, ref Color32 index, ref Color degreed)
{
// 找到与输入颜色最接近的四个颜色
//
//double[] distances = new double[4];
for (int i = 0; i < 4; ++i)
{
m_nearestColors[i] = m_Colors[0];
m_Degreed[i] = 1000;
}
for (int i = 0; i < m_Colors.Length; i++)
{
float distance = GetDistance(color, m_Colors[i]);
// 将当前颜色与已知的最近的四个颜色进行比较
for (int j = 0; j < m_nearestColors.Length; j++)
{
if (m_nearestColors[j] == null || distance < m_Degreed[j])
{
// 将当前颜色插入到最近的四个颜色中,其他颜色推后
for (int k = m_nearestColors.Length - 2; k >= j; k--)
{
m_nearestColors[k + 1] = m_nearestColors[k];
m_Degreed[k + 1] = m_Degreed[k];
m_Index[k + 1] = m_Index[k];
}
m_nearestColors[j] = m_Colors[i];
m_Degreed[j] = distance;
m_Index[j] = (byte)i;
break;
}
}
}
index = m_Index;
degreed = m_Degreed;
}
// 计算两个颜色之间的距离
static float GetDistance(Color color1, Color color2)
{
// 使用欧几里得距离公式计算两个颜色在 RGB 空间中的距离
float r = color1.r - color2.r;
float g = color1.g - color2.g;
float b = color1.b - color2.b;
return (float)Math.Sqrt(r * r + g * g + b * b);
}
}
}