I’m currently working on a college project focused on procedural generation, particularly in creating an endless terrain simulation. I’ve managed to generate the terrain using a method I implemented. Initially, everything worked smoothly. However, upon relaunching the simulation, I encountered an issue where the chunks are now separated by void margins. Despite thoroughly inspecting every script and ensuring the correct initialization of chunk sizes as well as the noise generation script, I’m unable to pinpoint the source of the problem.
I’ve included my project scripts below for reference. I’ve added numerous comments to aid understanding, although they may appear redundant. I’d deeply appreciate any assistance or insights into resolving this issue. Thank you in advance for your help!
Noise.cs
using UnityEngine;
using System.Collections;
// A static class to generate noise maps using Perlin noise
public static class Noise {
// Enumeration to specify the normalization mode
public enum NormalizeMode { Local, Global };
// Method to generate a 2D noise map
public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset, NormalizeMode normalizeMode) {
// Create a 2D array to store the noise map
float[,] noiseMap = new float[mapWidth,mapHeight];
// Initialize a pseudo-random number generator with the given seed
System.Random prng = new System.Random (seed);
// Array to store offsets for each octave
Vector2[] octaveOffsets = new Vector2[octaves];
// Variables to calculate maximum possible height for normalization
float maxPossibleHeight = 0;
float amplitude = 1;
float frequency = 1;
// Calculate octave offsets and maximum possible height
for (int i = 0; i < octaves; i++) {
float offsetX = prng.Next (-100000, 100000) + offset.x;
float offsetY = prng.Next (-100000, 100000) - offset.y;
octaveOffsets [i] = new Vector2 (offsetX, offsetY);
maxPossibleHeight += amplitude;
amplitude *= persistance;
}
// Ensure scale is not zero to prevent division by zero
if (scale <= 0) {
scale = 0.0001f;
}
// Variables to track local min and max noise heights
float maxLocalNoiseHeight = float.MinValue;
float minLocalNoiseHeight = float.MaxValue;
// Calculate half-width and half-height for centering the map
float halfWidth = mapWidth / 2f;
float halfHeight = mapHeight / 2f;
// Generate noise values for each point in the map
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
amplitude = 1;
frequency = 1;
float noiseHeight = 0;
// Combine octaves to generate Perlin noise
for (int i = 0; i < octaves; i++) {
float sampleX = (x - halfWidth + octaveOffsets[i].x) / scale * frequency;
float sampleY = (y - halfHeight + octaveOffsets[i].y) / scale * frequency;
float perlinValue = Mathf.PerlinNoise (sampleX, sampleY) * 2 - 1;
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
// Update local min and max noise heights
if (noiseHeight > maxLocalNoiseHeight) {
maxLocalNoiseHeight = noiseHeight;
} else if (noiseHeight < minLocalNoiseHeight) {
minLocalNoiseHeight = noiseHeight;
}
// Store the noise value in the map
noiseMap [x, y] = noiseHeight;
}
}
// Normalize the noise map based on the specified mode
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
if (normalizeMode == NormalizeMode.Local) {
// Normalize locally between min and max noise heights
noiseMap [x, y] = Mathf.InverseLerp (minLocalNoiseHeight, maxLocalNoiseHeight, noiseMap [x, y]);
} else {
// Normalize globally using max possible height
float normalizedHeight = (noiseMap [x, y] + 1) / (maxPossibleHeight / 0.9f);
noiseMap [x, y] = Mathf.Clamp(normalizedHeight, 0, int.MaxValue);
}
}
}
// Return the generated noise map
return noiseMap;
}
}
MeshGenerator.cs
using UnityEngine;
using System.Collections;
// A static class responsible for generating terrain meshes
public static class MeshGenerator {
// Method to generate a terrain mesh based on a height map
public static MeshData GenerateTerrainMesh(float[,] heightMap, float heightMultiplier, AnimationCurve _heightCurve, int levelOfDetail, bool useFlatShading) {
// Create a new animation curve from the provided keys
AnimationCurve heightCurve = new AnimationCurve (_heightCurve.keys);
// Determine the mesh simplification increment based on the level of detail
int meshSimplificationIncrement = (levelOfDetail == 0)?1:levelOfDetail * 2;
// Calculate the bordered size of the height map
int borderedSize = heightMap.GetLength (0);
int meshSize = borderedSize - 2*meshSimplificationIncrement;
int meshSizeUnsimplified = borderedSize - 2;
// Calculate the coordinates of the top-left corner of the mesh
float topLeftX = (meshSizeUnsimplified - 1) / -2f;
float topLeftZ = (meshSizeUnsimplified - 1) / 2f;
// Calculate the number of vertices per line in the mesh
int verticesPerLine = (meshSize - 1) / meshSimplificationIncrement + 1;
// Create a new MeshData object to store mesh information
MeshData meshData = new MeshData (verticesPerLine,useFlatShading);
// Create a mapping of vertex indices for border vertices
int[,] vertexIndicesMap = new int[borderedSize,borderedSize];
int meshVertexIndex = 0;
int borderVertexIndex = -1;
// Populate the vertex indices map
for (int y = 0; y < borderedSize; y += meshSimplificationIncrement) {
for (int x = 0; x < borderedSize; x += meshSimplificationIncrement) {
bool isBorderVertex = y == 0 || y == borderedSize - 1 || x == 0 || x == borderedSize - 1;
if (isBorderVertex) {
vertexIndicesMap [x, y] = borderVertexIndex;
borderVertexIndex--;
} else {
vertexIndicesMap [x, y] = meshVertexIndex;
meshVertexIndex++;
}
}
}
// Generate vertices, UVs, and triangles for the mesh
for (int y = 0; y < borderedSize; y += meshSimplificationIncrement) {
for (int x = 0; x < borderedSize; x += meshSimplificationIncrement) {
int vertexIndex = vertexIndicesMap [x, y];
Vector2 percent = new Vector2 ((x-meshSimplificationIncrement) / (float)meshSize, (y-meshSimplificationIncrement) / (float)meshSize);
float height = heightCurve.Evaluate (heightMap [x, y]) * heightMultiplier;
Vector3 vertexPosition = new Vector3 (topLeftX + percent.x * meshSizeUnsimplified, height, topLeftZ - percent.y * meshSizeUnsimplified);
meshData.AddVertex (vertexPosition, percent, vertexIndex);
if (x < borderedSize - 1 && y < borderedSize - 1) {
int a = vertexIndicesMap [x, y];
int b = vertexIndicesMap [x + meshSimplificationIncrement, y];
int c = vertexIndicesMap [x, y + meshSimplificationIncrement];
int d = vertexIndicesMap [x + meshSimplificationIncrement, y + meshSimplificationIncrement];
meshData.AddTriangle (a,d,c);
meshData.AddTriangle (d,a,b);
}
vertexIndex++;
}
}
// Process the mesh data (flat shading or normal baking)
meshData.ProcessMesh ();
// Return the generated mesh data
return meshData;
}
}
// A class representing mesh data (vertices, triangles, and UVs)
public class MeshData {
Vector3[] vertices;
int[] triangles;
Vector2[] uvs;
Vector3[] bakedNormals;
Vector3[] borderVertices;
int[] borderTriangles;
int triangleIndex;
int borderTriangleIndex;
bool useFlatShading;
// Constructor to initialize mesh data arrays
public MeshData(int verticesPerLine, bool useFlatShading) {
this.useFlatShading = useFlatShading;
vertices = new Vector3[verticesPerLine * verticesPerLine];
uvs = new Vector2[verticesPerLine * verticesPerLine];
triangles = new int[(verticesPerLine-1)*(verticesPerLine-1)*6];
borderVertices = new Vector3[verticesPerLine * 4 + 4];
borderTriangles = new int[24 * verticesPerLine];
}
// Method to add a vertex to the mesh data
public void AddVertex(Vector3 vertexPosition, Vector2 uv, int vertexIndex) {
if (vertexIndex < 0) {
borderVertices [-vertexIndex - 1] = vertexPosition;
} else {
vertices [vertexIndex] = vertexPosition;
uvs [vertexIndex] = uv;
}
}
// Method to add a triangle to the mesh data
public void AddTriangle(int a, int b, int c) {
if (a < 0 || b < 0 || c < 0) {
borderTriangles [borderTriangleIndex] = a;
borderTriangles [borderTriangleIndex + 1] = b;
borderTriangles [borderTriangleIndex + 2] = c;
borderTriangleIndex += 3;
} else {
triangles [triangleIndex] = a;
triangles [triangleIndex + 1] = b;
triangles [triangleIndex + 2] = c;
triangleIndex += 3;
}
}
// Method to calculate vertex normals
Vector3[] CalculateNormals() {
Vector3[] vertexNormals = new Vector3[vertices.Length];
int triangleCount = triangles.Length / 3;
for (int i = 0; i < triangleCount; i++) {
int normalTriangleIndex = i * 3;
int vertexIndexA = triangles [normalTriangleIndex];
int vertexIndexB = triangles [normalTriangleIndex + 1];
int vertexIndexC = triangles [normalTriangleIndex + 2];
Vector3 triangleNormal = SurfaceNormalFromIndices (vertexIndexA, vertexIndexB, vertexIndexC);
vertexNormals [vertexIndexA] += triangleNormal;
vertexNormals [vertexIndexB] += triangleNormal;
vertexNormals [vertexIndexC] += triangleNormal;
}
int borderTriangleCount = borderTriangles.Length / 3;
for (int i = 0; i < borderTriangleCount; i++) {
int normalTriangleIndex = i * 3;
int vertexIndexA = borderTriangles [normalTriangleIndex];
int vertexIndexB = borderTriangles [normalTriangleIndex + 1];
int vertexIndexC = borderTriangles [normalTriangleIndex + 2];
Vector3 triangleNormal = SurfaceNormalFromIndices (vertexIndexA, vertexIndexB, vertexIndexC);
if (vertexIndexA >= 0) {
vertexNormals [vertexIndexA] += triangleNormal;
}
if (vertexIndexB >= 0) {
vertexNormals [vertexIndexB] += triangleNormal;
}
if (vertexIndexC >= 0) {
vertexNormals [vertexIndexC] += triangleNormal;
}
}
for (int i = 0; i < vertexNormals.Length; i++) {
vertexNormals [i].Normalize ();
}
return vertexNormals;
}
Vector3 SurfaceNormalFromIndices(int indexA, int indexB, int indexC) {
Vector3 pointA = (indexA < 0)?borderVertices[-indexA-1] : vertices [indexA];
Vector3 pointB = (indexB < 0)?borderVertices[-indexB-1] : vertices [indexB];
Vector3 pointC = (indexC < 0)?borderVertices[-indexC-1] : vertices [indexC];
Vector3 sideAB = pointB - pointA;
Vector3 sideAC = pointC - pointA;
return Vector3.Cross (sideAB, sideAC).normalized;
}
// Method to process the mesh data (flat shading or normal baking)
public void ProcessMesh() {
if (useFlatShading) {
FlatShading ();
} else {
BakeNormals ();
}
}
// Method to bake normals
void BakeNormals() {
bakedNormals = CalculateNormals ();
}
// Method to perform flat shading
void FlatShading() {
Vector3[] flatShadedVertices = new Vector3[triangles.Length];
Vector2[] flatShadedUvs = new Vector2[triangles.Length];
for (int i = 0; i < triangles.Length; i++) {
flatShadedVertices [i] = vertices [triangles [i]];
flatShadedUvs [i] = uvs [triangles [i]];
triangles [i] = i;
}
vertices = flatShadedVertices;
uvs = flatShadedUvs;
}
// Method to create a mesh from the mesh data
public Mesh CreateMesh() {
Mesh mesh = new Mesh ();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uvs;
if (useFlatShading) {
mesh.RecalculateNormals ();
} else {
mesh.normals = bakedNormals;
}
return mesh;
}
}
MapGenerator.cs
using UnityEngine;
using System.Collections;
using System;
using System.Threading;
using System.Collections.Generic;
public class MapGenerator : MonoBehaviour {
// Enumeration for different draw modes
public enum DrawMode {NoiseMap, ColourMap, Mesh};
public DrawMode drawMode;
// Normalization mode for noise generation
public Noise.NormalizeMode normalizeMode;
// public const int mapChunkSize = 95;
// Boolean to enable/disable flat shading
public bool useFlatShading;
// Editor preview level of detail (LOD)
[Range(0,6)]
public int editorPreviewLOD;
// Scale of the noise
public float noiseScale;
// Parameters for noise generation
public int octaves;
[Range(0,1)]
public float persistance;
public float lacunarity;
// Seed for random number generation
public int seed;
// Offset for noise generation
public Vector2 offset;
// Multiplier for mesh height
public float meshHeightMultiplier;
// Curve for mapping noise values to mesh heights
public AnimationCurve meshHeightCurve;
// Boolean to auto-update the map
public bool autoUpdate;
// Array of terrain types
public TerrainType[] regions;
// Queues for storing map and mesh thread information
Queue<MapThreadInfo<MapData>> mapDataThreadInfoQueue = new Queue<MapThreadInfo<MapData>>();
Queue<MapThreadInfo<MeshData>> meshDataThreadInfoQueue = new Queue<MapThreadInfo<MeshData>>();
// Property to get map chunk size based on flat shading
public static int mapChunkSize {
get {
// Property to get map chunk size based on flat shading
if (Instance == null) {
Instance = FindObjectOfType<MapGenerator> ();
}
// Return chunk size based on flat shading
if (Instance.useFlatShading) {
return 95;
}
return 239;
}
}
// Method to draw the map in the editor
public void DrawMapInEditor() {
// Generate map data
MapData mapData = GenerateMapData (Vector2.zero);
// Get the MapDisplay instance
MapDisplay display = FindObjectOfType<MapDisplay> ();
// Draw based on draw mode
if (drawMode == DrawMode.NoiseMap) {
display.DrawTexture (TextureGenerator.TextureFromHeightMap (mapData.heightMap));
} else if (drawMode == DrawMode.ColourMap) {
display.DrawTexture (TextureGenerator.TextureFromColourMap (mapData.colourMap, mapChunkSize, mapChunkSize));
} else if (drawMode == DrawMode.Mesh) {
display.DrawMesh (MeshGenerator.GenerateTerrainMesh (mapData.heightMap, meshHeightMultiplier, meshHeightCurve, editorPreviewLOD, useFlatShading), TextureGenerator.TextureFromColourMap (mapData.colourMap, mapChunkSize, mapChunkSize));
}
}
// Method to request map data
public void RequestMapData(Vector2 centre, Action<MapData> callback) {
// Start a new thread for map data generation
ThreadStart threadStart = delegate {
MapDataThread (centre, callback);
};
new Thread (threadStart).Start ();
}
// Method for map data thread
void MapDataThread(Vector2 centre, Action<MapData> callback) {
// Generate map data
MapData mapData = GenerateMapData (centre);
// Enqueue map data thread info
lock (mapDataThreadInfoQueue) {
mapDataThreadInfoQueue.Enqueue (new MapThreadInfo<MapData> (callback, mapData));
}
}
// Method to request mesh data
public void RequestMeshData(MapData mapData, int lod, Action<MeshData> callback) {
// Start a new thread for mesh data generation
ThreadStart threadStart = delegate {
MeshDataThread (mapData, lod, callback);
};
new Thread (threadStart).Start ();
}
// Method for mesh data thread
void MeshDataThread(MapData mapData, int lod, Action<MeshData> callback) {
// Generate mesh data
MeshData meshData = MeshGenerator.GenerateTerrainMesh (mapData.heightMap, meshHeightMultiplier, meshHeightCurve, lod, useFlatShading);
// Enqueue mesh data thread info
lock (meshDataThreadInfoQueue) {
meshDataThreadInfoQueue.Enqueue (new MapThreadInfo<MeshData> (callback, meshData));
}
}
// Update method to process thread info queues
void Update() {
if (mapDataThreadInfoQueue.Count > 0) {
for (int i = 0; i < mapDataThreadInfoQueue.Count; i++) {
MapThreadInfo<MapData> threadInfo = mapDataThreadInfoQueue.Dequeue ();
threadInfo.callback (threadInfo.parameter);
}
}
if (meshDataThreadInfoQueue.Count > 0) {
for (int i = 0; i < meshDataThreadInfoQueue.Count; i++) {
MapThreadInfo<MeshData> threadInfo = meshDataThreadInfoQueue.Dequeue ();
threadInfo.callback (threadInfo.parameter);
}
}
}
// Method to generate map data
MapData GenerateMapData(Vector2 centre) {
// Generate noise map
float[,] noiseMap = Noise.GenerateNoiseMap (mapChunkSize, mapChunkSize, seed, noiseScale, octaves, persistance, lacunarity, centre + offset, normalizeMode);
// Generate color map
Color[] colourMap = new Color[mapChunkSize * mapChunkSize];
for (int y = 0; y < mapChunkSize; y++) {
for (int x = 0; x < mapChunkSize; x++) {
float currentHeight = noiseMap [x, y];
for (int i = 0; i < regions.Length; i++) {
if (currentHeight >= regions [i].height) {
colourMap [y * mapChunkSize + x] = regions [i].colour;
} else {
break;
}
}
}
}
// Return the generated map data
return new MapData (noiseMap, colourMap);
}
// Method to ensure valid values for parameters
void OnValidate() {
if (lacunarity < 1) {
lacunarity = 1;
}
if (octaves < 0) {
octaves = 0;
}
}
// Method to set the seed for random number generation
public void SetSeed(string seedValue, Vector2 centre)
{
// if (long.TryParse(seedValue, out long parsedSeed))
// {
// seed = (int)parsedSeed;
// GenerateMapData();
// Update();
// }
seed = int.Parse(seedValue);
GenerateMapData(centre);
Update();
}
public static MapGenerator Instance { get; private set; } // Singletone property instance
// Awake method to handle object instantiation
void Awake()
{
// Check if an instance already exists
if (Instance == null)
{
// Check if an instance already exists
Instance = this;
// Don't destroy this object when loading a new scene
DontDestroyOnLoad(gameObject); // This ensures the object isn't destroyed when loading a new scene
}
else
{
// Destroy the duplicate instance
Destroy(gameObject);
}
}
// Structure to store thread information
struct MapThreadInfo<T> {
public readonly Action<T> callback;
public readonly T parameter;
// Constructor
public MapThreadInfo (Action<T> callback, T parameter)
{
this.callback = callback;
this.parameter = parameter;
}
}
}
// Serializable struct for defining terrain types
[System.Serializable]
public struct TerrainType {
public string name;
public float height;
public Color colour;
}
// Structure to store map data
public struct MapData {
public readonly float[,] heightMap;
public readonly Color[] colourMap;
// Constructor
public MapData (float[,] heightMap, Color[] colourMap)
{
this.heightMap = heightMap;
this.colourMap = colourMap;
}
}
GenerateTerrain.cs (EndlessChunks)
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class GenerateTerrain : MonoBehaviour {
// Scale factor for positioning
const float scale = 5f;
// Thresholds for updating chunks based on viewer movement
const float viewerMoveThresholdForChunkUpdate = 25f;
const float sqrViewerMoveThresholdForChunkUpdate = viewerMoveThresholdForChunkUpdate * viewerMoveThresholdForChunkUpdate;
// Thresholds for updating chunks based on viewer movement// Array of detail levels for LOD (Level of Detail)
public LODInfo[] detailLevels;
// Maximum view distance
public static float maxViewDst;
// Reference to the viewer's transform
public Transform viewer;
// Material to apply to the terrain
public Material mapMaterial;
// Current position of the viewer
public static Vector2 viewerPosition;
// Previous position of the viewer
Vector2 viewerPositionOld;
// Reference to the MapGenerator script
static MapGenerator mapGenerator;
// Size of each terrain chunk
int chunkSize;
// Number of visible chunks in view distance
int chunksVisibleInViewDst;
// Dictionary to store terrain chunks
Dictionary<Vector2, TerrainChunk> terrainChunkDictionary = new Dictionary<Vector2, TerrainChunk>();
// List of terrain chunks visible in the last update
static List<TerrainChunk> terrainChunksVisibleLastUpdate = new List<TerrainChunk>();
void Start() {
// List of terrain chunks visible in the last update
mapGenerator = FindObjectOfType<MapGenerator> ();
// Set the maximum view distance to the threshold of the last detail level
maxViewDst = detailLevels [detailLevels.Length - 1].visibleDstThreshold;
// Calculate chunk size based on map chunk size
chunkSize = MapGenerator.mapChunkSize - 1;
// Calculate the number of chunks visible in view distance
chunksVisibleInViewDst = Mathf.RoundToInt(maxViewDst / chunkSize);
// Update visible chunks when starting
UpdateVisibleChunks ();
}
void Update() {
// Update viewer position
viewerPosition = new Vector2 (viewer.position.x, viewer.position.z) / scale;
// Check if viewer has moved enough to update visible chunks
if ((viewerPositionOld - viewerPosition).sqrMagnitude > sqrViewerMoveThresholdForChunkUpdate) {
viewerPositionOld = viewerPosition;
UpdateVisibleChunks ();
}
}
void UpdateVisibleChunks() {
// Hide all terrain chunks from the last update
for (int i = 0; i < terrainChunksVisibleLastUpdate.Count; i++) {
terrainChunksVisibleLastUpdate [i].SetVisible (false);
}
terrainChunksVisibleLastUpdate.Clear ();
// Calculate current chunk coordinates based on viewer position
int currentChunkCoordX = Mathf.RoundToInt (viewerPosition.x / chunkSize);
int currentChunkCoordY = Mathf.RoundToInt (viewerPosition.y / chunkSize);
// Loop through visible chunks in view distance
for (int yOffset = -chunksVisibleInViewDst; yOffset <= chunksVisibleInViewDst; yOffset++) {
for (int xOffset = -chunksVisibleInViewDst; xOffset <= chunksVisibleInViewDst; xOffset++) {
Vector2 viewedChunkCoord = new Vector2 (currentChunkCoordX + xOffset, currentChunkCoordY + yOffset);
// Check if the chunk is already generated (If the chunk is in the dictionary, update it)
if (terrainChunkDictionary.ContainsKey (viewedChunkCoord)) {
terrainChunkDictionary [viewedChunkCoord].UpdateTerrainChunk ();
} else {
// Generate new terrain chunk if not found
terrainChunkDictionary.Add (viewedChunkCoord, new TerrainChunk (viewedChunkCoord, chunkSize, detailLevels, transform, mapMaterial));
}
}
}
}
// Class to represent a terrain chunk
public class TerrainChunk {
GameObject meshObject;
Vector2 position;
Bounds bounds;
MeshRenderer meshRenderer;
MeshFilter meshFilter;
MeshCollider meshCollider;
LODInfo[] detailLevels;
LODMesh[] lodMeshes;
MapData mapData;
bool mapDataReceived;
int previousLODIndex = -1;
// Constructor for terrain chunk
public TerrainChunk(Vector2 coord, int size, LODInfo[] detailLevels, Transform parent, Material material) {
this.detailLevels = detailLevels;
position = coord * size;
bounds = new Bounds(position,Vector2.one * size);
Vector3 positionV3 = new Vector3(position.x,0,position.y);
// Create mesh object and components
meshObject = new GameObject("Terrain Chunk");
meshRenderer = meshObject.AddComponent<MeshRenderer>();
meshFilter = meshObject.AddComponent<MeshFilter>();
meshCollider = meshObject.AddComponent<MeshCollider>();
meshRenderer.material = material;
// Set mesh object's position and parent
meshObject.transform.position = positionV3 * scale;
meshObject.transform.parent = parent;
meshObject.transform.localScale = Vector3.one * scale;
SetVisible(false);
// Initialize LOD meshes
lodMeshes = new LODMesh[detailLevels.Length];
for (int i = 0; i < detailLevels.Length; i++) {
lodMeshes[i] = new LODMesh(detailLevels[i].lod, UpdateTerrainChunk);
}
// Request map data
mapGenerator.RequestMapData(position,OnMapDataReceived);
}
// Callback for when map data is received
void OnMapDataReceived(MapData mapData) {
this.mapData = mapData;
mapDataReceived = true;
Texture2D texture = TextureGenerator.TextureFromColourMap (mapData.colourMap, MapGenerator.mapChunkSize, MapGenerator.mapChunkSize);
meshRenderer.material.mainTexture = texture;
UpdateTerrainChunk ();
}
// Method to update the terrain chunk based on LOD and visibility
public void UpdateTerrainChunk() {
if (mapDataReceived) {
float viewerDstFromNearestEdge = Mathf.Sqrt (bounds.SqrDistance (viewerPosition));
bool visible = viewerDstFromNearestEdge <= maxViewDst;
if (visible) {
int lodIndex = 0;
for (int i = 0; i < detailLevels.Length - 1; i++) {
if (viewerDstFromNearestEdge > detailLevels [i].visibleDstThreshold) {
lodIndex = i + 1;
} else {
break;
}
}
if (lodIndex != previousLODIndex) {
LODMesh lodMesh = lodMeshes [lodIndex];
if (lodMesh.hasMesh) {
previousLODIndex = lodIndex;
meshFilter.mesh = lodMesh.mesh;
meshCollider.sharedMesh = lodMesh.mesh;
} else if (!lodMesh.hasRequestedMesh) {
lodMesh.RequestMesh (mapData);
}
}
terrainChunksVisibleLastUpdate.Add (this);
}
SetVisible (visible);
}
}
// Method to set the visibility of the terrain chunk
public void SetVisible(bool visible) {
meshObject.SetActive (visible);
}
// Method to check if the terrain chunk is visible
public bool IsVisible() {
return meshObject.activeSelf;
}
}
// Class for LOD mesh
class LODMesh {
public Mesh mesh;
public bool hasRequestedMesh;
public bool hasMesh;
int lod;
System.Action updateCallback;
// Constructor for LODMesh
public LODMesh(int lod, System.Action updateCallback) {
this.lod = lod;
this.updateCallback = updateCallback;
}
// Callback for when mesh data is received
void OnMeshDataReceived(MeshData meshData) {
mesh = meshData.CreateMesh ();
hasMesh = true;
updateCallback ();
}
// Method to request mesh data
public void RequestMesh(MapData mapData) {
hasRequestedMesh = true;
mapGenerator.RequestMeshData (mapData, lod, OnMeshDataReceived);
}
}
// Struct for LOD information
[System.Serializable]
public struct LODInfo {
public int lod;
public float visibleDstThreshold;
}
}
Here are screenshots of the gap in question:
Without flatshading
With flatshading
When I ran into this problem, I searched everywhere for mistakes in how I set the chunk sizes and how I generated noise, but I couldn’t find anything.
Rayan Derradji is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.