Files
2026-03-04 10:03:45 +08:00

629 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.Rendering;
namespace Artngame.SKYMASTER.PlanetCreator
{
public class PlanetCreator : MonoBehaviour
{
public Transform Sun;
public int ChunkSegments = 32;
public float SphereRadius = 10f;
public float ChunkSize = 128f;
public Material mat;
public float TerrainFreq = 0.006f;
public float TerrainGain = 0.5f;
public float Terrainlacunarity = 2f;
public float TerrainScale = 20f;
public float TerrainBumpScale = 20f;
public Camera MainCamera;
private List<PlanetChunkProperties> rootChunks;
public int MaxLodLevel = 7;
public int LodColliderStart = 5;
public float MaxError = 1f;
public float MaxSplitsPerSecond = 20f;
private Mesh BasicPlane;
private Stack<PlanetChunkObject> MeshPool;
private Queue<PlanetChunkProperties> SplitPool;
public int PoolStartPopulation;
public ComputeBuffer VertexComputeBuffer;
public ComputeBuffer NormalComputeBuffer;
public ComputeBuffer PlanetMapCreatorBuffer;
public ComputeShader VertexComputeShader;
public ComputeShader PlanetMapGeneratorShader;
private ImprovedPerlinNoise m_perlin;
private readonly List<Vector2> uvsTmp = new List<Vector2>();
private readonly List<Vector3> vertexTmp = new List<Vector3>();
private readonly List<Vector3> normalTmp = new List<Vector3>();
private float lastSplitTime;
private static readonly int Frequency = Shader.PropertyToID("_Frequency");
private static readonly int Lacunarity = Shader.PropertyToID("_Lacunarity");
private static readonly int Gain = Shader.PropertyToID("_Gain");
private static readonly int PlanetRadius = Shader.PropertyToID("_PlanetRadius");
private static readonly int PermTable2D = Shader.PropertyToID("_PermTable2D");
private static readonly int Gradient3D = Shader.PropertyToID("_Gradient3D");
public float K { get; set; }
public int MeshPoolSize => MeshPool.Count;
public int SplitPoolSize => SplitPool.Count;
public void Start()
{
Precompute();
MeshPool = new Stack<PlanetChunkObject>();
SplitPool = new Queue<PlanetChunkProperties>();
BasicPlane = GeneratePremadePlane(ChunkSegments);
rootChunks = new List<PlanetChunkProperties>();
CreateRoot(Vector3.left, 0f);
CreateRoot(Vector3.left, 90f);
CreateRoot(Vector3.left, 180f);
CreateRoot(Vector3.left, 270f);
CreateRoot(Vector3.forward, 90f);
CreateRoot(Vector3.forward, 270f);
for (int i = 0; i < PoolStartPopulation; i++)
{
AddToChunkPool(CreateBasicChunkObject());
}
}
private void CreateTemperatureMap()
{
for (int i = 0; i < 4; i++)
{
Texture2D texture2D = new Texture2D(1024, 1024, TextureFormat.RGBA32, mipChain: false);
Vector3[] array = new Vector3[1048576];
Color32[] array2 = new Color32[1048576];
int num = 0;
for (int j = 0; j < 1024; j++)
{
for (int k = 0; k < 1024; k++)
{
array[num++] = new Vector3(j, k, 0f);
}
}
PlanetMapCreatorBuffer.SetData(array);
PlanetMapGeneratorShader.Dispatch(0, 1048576, 1, 1);
PlanetMapCreatorBuffer.GetData(array);
for (int l = 0; l < 1048576; l++)
{
array2[l] = new Color32((byte)(array[l].x * 255f), 0, 0, byte.MaxValue);
}
texture2D.SetPixels32(array2);
using FileStream output = new FileStream("D:/Test/" + i + ".png", FileMode.OpenOrCreate);
new BinaryWriter(output).Write(texture2D.EncodeToPNG());
}
}
public void Precompute()
{
K = (float)Screen.width / (2f * Mathf.Tan(MathF.PI * 13f / 72f));
m_perlin = new ImprovedPerlinNoise(0);
m_perlin.LoadResourcesFor3DNoise();
Texture2D permutationTable2D = m_perlin.GetPermutationTable2D();
Texture2D gradient3D = m_perlin.GetGradient3D();
VertexComputeShader.SetTexture(0, "_PermTable2D", permutationTable2D);
VertexComputeShader.SetTexture(0, "_Gradient3D", gradient3D);
mat.SetTexture(PermTable2D, permutationTable2D);
mat.SetTexture(Gradient3D, gradient3D);
VertexComputeBuffer = new ComputeBuffer((ChunkSegments + 2) * (ChunkSegments + 2), 12);
NormalComputeBuffer = new ComputeBuffer((ChunkSegments + 2) * (ChunkSegments + 2), 12);
PlanetMapCreatorBuffer = new ComputeBuffer(1048576, 32);
VertexComputeShader.SetBuffer(0, "vertexBuffer", VertexComputeBuffer);
VertexComputeShader.SetBuffer(0, "normalBuffer", NormalComputeBuffer);
PlanetMapGeneratorShader.SetBuffer(0, "Output", PlanetMapCreatorBuffer);
PlanetMapGeneratorShader.SetTexture(0, "_PermTable2D", permutationTable2D);
PlanetMapGeneratorShader.SetTexture(0, "_Gradient3D", gradient3D);
UpdateNoise();
}
public void CreateRoot(Vector3 rotationDir, float angle)
{
PlanetChunkProperties planetChunkProperties = CreateChunkProperties(null, Quaternion.AngleAxis(angle, rotationDir), 1f, 0, Vector2.zero);
GetFreeChunkObject(planetChunkProperties);
rootChunks.Add(planetChunkProperties);
}
public void CreateChunk(PlanetChunkProperties parent, Vector2 min, int index)
{
parent.Chunks[index] = CreateChunkProperties(parent, parent.Rotation, parent.Size / 2f, parent.LODLevel + 1, min);
parent.Chunks[index].ChunkObject = GetFreeChunkObject(parent.Chunks[index]);
parent.Chunks[index].Bounds = parent.Chunks[index].ChunkObject.GetComponent<MeshRenderer>().bounds;
}
private PlanetChunkObject CreateBasicChunkObject()
{
PlanetChunkObject component = new GameObject("Chunk", typeof(MeshFilter), typeof(MeshCollider), typeof(MeshRenderer), typeof(PlanetChunkObject)).GetComponent<PlanetChunkObject>();
component.Filter = component.GetComponent<MeshFilter>();
component.Collider = component.GetComponent<MeshCollider>();
component.Renderer = component.GetComponent<Renderer>();
component.Renderer.sharedMaterial = mat;
return component;
}
public PlanetChunkObject UpdateChunkObject(PlanetChunkProperties chunkProperties, PlanetChunkObject chunk)
{
chunk.transform.localPosition = Vector3.zero;
chunk.Properties = chunkProperties;
chunkProperties.ChunkObject = chunk;
UpdateChunkMesh(chunk);
return chunk;
}
public PlanetChunkProperties GetNearestChunkProperties(Vector3 point)
{
if (rootChunks == null)
{
return null;
}
float num = float.PositiveInfinity;
PlanetChunkProperties parent = null;
foreach (PlanetChunkProperties rootChunk in rootChunks)
{
float sqrMagnitude = (rootChunk.Bounds.center - point).sqrMagnitude;
if (sqrMagnitude < num)
{
num = sqrMagnitude;
parent = rootChunk;
}
}
return GetNearestChunkProperties(parent, point);
}
private PlanetChunkProperties GetNearestChunkProperties(PlanetChunkProperties parent, Vector3 point)
{
if (parent.Chunks == null)
{
return parent;
}
float num = float.PositiveInfinity;
PlanetChunkProperties parent2 = null;
PlanetChunkProperties[] chunks = parent.Chunks;
foreach (PlanetChunkProperties planetChunkProperties in chunks)
{
float sqrMagnitude = (planetChunkProperties.Bounds.center - point).sqrMagnitude;
if (sqrMagnitude < num)
{
parent2 = planetChunkProperties;
num = sqrMagnitude;
}
}
return GetNearestChunkProperties(parent2, point);
}
public PlanetChunkObject GetChunkObjectFromPool()
{
if (MeshPool.Count > 0)
{
return MeshPool.Pop();
}
return null;
}
public PlanetChunkObject GetFreeChunkObject(PlanetChunkProperties chunkProperties)
{
PlanetChunkObject planetChunkObject = GetChunkObjectFromPool();
if (planetChunkObject == null)
{
planetChunkObject = CreateBasicChunkObject();
}
UpdateChunkObject(chunkProperties, planetChunkObject);
return planetChunkObject;
}
public void UpdateChunkMesh(PlanetChunkObject chunk)
{
if (chunk.Filter.sharedMesh == null)
{
chunk.Filter.sharedMesh = CopyMesh(BasicPlane);
}
CaluclateVertex(chunk);
}
private void UpdateNoise()
{
VertexComputeShader.SetFloat(Frequency, TerrainFreq);
VertexComputeShader.SetFloat(Lacunarity, Terrainlacunarity);
VertexComputeShader.SetFloat(Gain, TerrainGain);
mat.SetFloat(Frequency, TerrainFreq);
mat.SetFloat(Lacunarity, Terrainlacunarity);
mat.SetFloat(Gain, TerrainGain);
mat.SetFloat(PlanetRadius, SphereRadius);
}
public void AddToChunkPool(PlanetChunkObject chunk)
{
MeshPool.Push(chunk);
}
public PlanetChunkProperties CreateChunkProperties(PlanetChunkProperties parent, Quaternion rotation, float Size, int LodLevel, Vector2 min)
{
PlanetChunkProperties planetChunkProperties = new PlanetChunkProperties();
planetChunkProperties.Rotation = rotation;
planetChunkProperties.Parent = parent;
planetChunkProperties.LODLevel = LodLevel;
planetChunkProperties.Size = Size;
planetChunkProperties.min = min;
planetChunkProperties.Center = (planetChunkProperties.Rotation * new Vector3(planetChunkProperties.Middle.x - 0.5f, 1f, planetChunkProperties.Middle.y - 0.5f)).normalized * SphereRadius;
planetChunkProperties.maxGeoError = Mathf.Pow(2f, MaxLodLevel - planetChunkProperties.LODLevel);
return planetChunkProperties;
}
public void CaluclateVertex(PlanetChunkObject chunk)
{
int num = ChunkSegments + 2;
int num2 = ChunkSegments + 2;
int num3 = num * num2;
vertexTmp.Clear();
normalTmp.Clear();
uvsTmp.Clear();
float num4 = chunk.Properties.Size / (float)ChunkSegments;
for (float num5 = 0f; num5 < (float)num2; num5 += 1f)
{
for (float num6 = 0f; num6 < (float)num; num6 += 1f)
{
float x = chunk.Properties.BottomLeft.x + num6 * num4 - 0.5f;
float y = chunk.Properties.BottomLeft.y + num5 * num4 - 0.5f;
vertexTmp.Add(GeVertex(chunk.Properties.Rotation, SphereRadius, x, y));
uvsTmp.Add(chunk.Properties.BottomLeft + new Vector2(num6 * num4, num5 * num4));
}
}
VertexComputeBuffer.SetData(vertexTmp);
NormalComputeBuffer.SetData(normalTmp);
VertexComputeShader.SetFloat("Scale", num4);
VertexComputeShader.SetFloat("TerrainScale", TerrainScale);
VertexComputeShader.SetFloat("TerrainBumpScale", TerrainBumpScale);
VertexComputeShader.Dispatch(0, num3 / 16, 1, 1);
AsyncGPUReadback.Request(VertexComputeBuffer, chunk.ApplyVertexData);
AsyncGPUReadback.Request(NormalComputeBuffer, chunk.ApplyNormalData);
chunk.MarkCalculatingVertexData();
chunk.name = "Recycled Mesh";
chunk.Filter.sharedMesh.SetUVs(0, uvsTmp);
chunk.SetVisible(visible: true);
if (chunk.Properties != null)
{
if (chunk.Properties.LODLevel >= LodColliderStart)
{
chunk.Collider.sharedMesh = null;
chunk.Collider.sharedMesh = chunk.Filter.sharedMesh;
chunk.Collider.enabled = true;
}
else
{
chunk.Collider.enabled = false;
}
chunk.Properties.Active = true;
}
}
public Vector3 GeVertex(Quaternion rotation, float SphereRadius, float X, float Y)
{
Vector3 vector = rotation * new Vector3(X, 0.5f, Y);
return ToSphere(vector) * SphereRadius;
}
public static void calculateMeshTangents(Mesh mesh)
{
int[] triangles = mesh.triangles;
Vector3[] vertices = mesh.vertices;
Vector2[] uv = mesh.uv;
Vector3[] normals = mesh.normals;
int num = triangles.Length;
int num2 = vertices.Length;
Vector3[] array = new Vector3[num2];
Vector3[] array2 = new Vector3[num2];
Vector4[] array3 = new Vector4[num2];
for (long num3 = 0L; num3 < num; num3 += 3)
{
long num4 = triangles[num3];
long num5 = triangles[num3 + 1];
long num6 = triangles[num3 + 2];
Vector3 vector = vertices[num4];
Vector3 vector2 = vertices[num5];
Vector3 vector3 = vertices[num6];
Vector2 vector4 = uv[num4];
Vector2 vector5 = uv[num5];
Vector2 vector6 = uv[num6];
float num7 = vector2.x - vector.x;
float num8 = vector3.x - vector.x;
float num9 = vector2.y - vector.y;
float num10 = vector3.y - vector.y;
float num11 = vector2.z - vector.z;
float num12 = vector3.z - vector.z;
float num13 = vector5.x - vector4.x;
float num14 = vector6.x - vector4.x;
float num15 = vector5.y - vector4.y;
float num16 = vector6.y - vector4.y;
float num17 = 1f / (num13 * num16 - num14 * num15);
Vector3 vector7 = new Vector3((num16 * num7 - num15 * num8) * num17, (num16 * num9 - num15 * num10) * num17, (num16 * num11 - num15 * num12) * num17);
Vector3 vector8 = new Vector3((num13 * num8 - num14 * num7) * num17, (num13 * num10 - num14 * num9) * num17, (num13 * num12 - num14 * num11) * num17);
array[num4] += vector7;
array[num5] += vector7;
array[num6] += vector7;
array2[num4] += vector8;
array2[num5] += vector8;
array2[num6] += vector8;
}
for (long num18 = 0L; num18 < num2; num18++)
{
Vector3 normal = normals[num18];
Vector3 tangent = array[num18];
Vector3.OrthoNormalize(ref normal, ref tangent);
array3[num18].x = tangent.x;
array3[num18].y = tangent.y;
array3[num18].z = tangent.z;
array3[num18].w = ((Vector3.Dot(Vector3.Cross(normal, tangent), array2[num18]) < 0f) ? (-1f) : 1f);
}
mesh.tangents = array3;
}
public static Mesh CopyMesh(Mesh mesh)
{
return UnityEngine.Object.Instantiate(mesh);
}
public Mesh GeneratePremadePlane(int Segments)
{
Mesh mesh = new Mesh();
int num = Segments + 2;
int num2 = Segments * Segments * 6;
int num3 = num * num;
int num4 = 0;
float num5 = 1f / (float)Segments;
float num6 = 1f / (float)Segments;
float num7 = 1f / (float)Segments;
float num8 = 1f / (float)Segments;
int[] array = new int[num2];
Vector2[] array2 = new Vector2[num3];
Vector3[] array3 = new Vector3[num3];
for (float num9 = 0f; num9 < (float)num; num9 += 1f)
{
for (float num10 = 0f; num10 < (float)num; num10 += 1f)
{
float x = num10 * num7 - 0.5f;
float z = num9 * num8 - 0.5f;
array2[num4] = new Vector2(num10 * num5, num9 * num6);
array3[num4++] = new Vector3(x, 1f, z);
}
}
num4 = 0;
for (int i = 0; i < Segments; i++)
{
for (int j = 0; j < Segments; j++)
{
array[num4] = i * num + j;
array[num4 + 1] = (i + 1) * num + j;
array[num4 + 2] = i * num + j + 1;
array[num4 + 3] = (i + 1) * num + j;
array[num4 + 4] = (i + 1) * num + j + 1;
array[num4 + 5] = i * num + j + 1;
num4 += 6;
}
}
mesh.vertices = array3;
mesh.triangles = array;
mesh.uv = array2;
mesh.RecalculateNormals();
mesh.RecalculateBounds();
calculateMeshTangents(mesh);
calculateMeshTangents(mesh);
return mesh;
}
public static Bounds CopyBounds(Bounds bounds)
{
return new Bounds
{
min = bounds.min,
max = bounds.max,
size = bounds.size,
center = bounds.center
};
}
private Vector3 ToSphere(Vector3 vector)
{
return vector.normalized;
}
private Vector3 SphericalPos(Vector3 pos, float radius)
{
return pos.normalized * radius;
}
private void Update()
{
ManageChunks();
UpdateNoise();
if (SplitPool.Count > 0 && lastSplitTime - Time.time <= 0f)
{
PlanetChunkProperties chunk = SplitPool.Dequeue();
Split(chunk);
lastSplitTime = Time.time + 1f / MaxSplitsPerSecond;
}
}
public bool NeedsSplit(PlanetChunkProperties chunk)
{
return chunk.maxGeoError / Mathf.Sqrt(chunk.ChunkObject.Renderer.bounds.SqrDistance(MainCamera.transform.position)) * K > MaxError;
}
public void AddToSplitPool(PlanetChunkProperties chunk)
{
if (!SplitPool.Contains(chunk))
{
SplitPool.Enqueue(chunk);
}
}
private void ManageChunks()
{
foreach (PlanetChunkProperties rootChunk in rootChunks)
{
ManageRecursive(rootChunk);
}
}
public void ManageRecursive(PlanetChunkProperties chunk)
{
if (NeedsSplit(chunk) && chunk.LODLevel < MaxLodLevel)
{
if (chunk.isSplit)
{
bool flag = false;
PlanetChunkProperties[] children = chunk.Children;
foreach (PlanetChunkProperties planetChunkProperties in children)
{
flag |= planetChunkProperties.ChunkObject.IsCalculating;
ManageRecursive(planetChunkProperties);
}
if (!flag)
{
HideChunk(chunk);
}
chunk.isSpliting = false;
}
else
{
AddToSplitPool(chunk);
}
}
else
{
Merge(chunk);
}
}
public void Split(PlanetChunkProperties chunk)
{
if (!(chunk.ChunkObject != null) || !NeedsSplit(chunk))
{
return;
}
if (chunk.Children == null)
{
chunk.Children = new PlanetChunkProperties[4];
CreateChunk(chunk, chunk.min, 0);
CreateChunk(chunk, chunk.MiddleLeft, 1);
CreateChunk(chunk, chunk.BottomMiddle, 2);
CreateChunk(chunk, chunk.Middle, 3);
}
else
{
PlanetChunkProperties[] children = chunk.Children;
foreach (PlanetChunkProperties chunk2 in children)
{
EnableChunk(chunk2);
}
}
chunk.isMerged = false;
}
public void Merge(PlanetChunkProperties chunk)
{
if (chunk.isMerged)
{
return;
}
if (chunk.Children != null)
{
PlanetChunkProperties[] children = chunk.Children;
foreach (PlanetChunkProperties child in children)
{
MergeChildren(chunk, child);
}
}
EnableChunk(chunk);
chunk.isMerged = true;
}
private void MergeChildren(PlanetChunkProperties parent, PlanetChunkProperties child)
{
if (!child.isMerged && child.Children != null)
{
PlanetChunkProperties[] children = child.Children;
foreach (PlanetChunkProperties child2 in children)
{
MergeChildren(parent, child2);
}
}
DisableChunk(child);
parent.isMerged = true;
}
public void EnableChunk(PlanetChunkProperties chunk)
{
if (chunk.ChunkObject == null)
{
chunk.ChunkObject = GetFreeChunkObject(chunk);
}
chunk.ChunkObject.SetVisible(visible: true);
}
public void HideChunk(PlanetChunkProperties chunk)
{
if (chunk.ChunkObject != null && chunk.ChunkObject.IsVisible)
{
chunk.ChunkObject.SetVisible(visible: false);
}
}
public void DisableChunk(PlanetChunkProperties chunk)
{
if (chunk.ChunkObject.Collider != null)
{
chunk.ChunkObject.Collider.enabled = false;
}
HideChunk(chunk);
AddToChunkPool(chunk.ChunkObject);
chunk.ChunkObject = null;
chunk.Active = false;
}
private void OnDisable()
{
VertexComputeBuffer.Dispose();
NormalComputeBuffer.Dispose();
PlanetMapCreatorBuffer.Dispose();
}
}
}