/// <summary> /// Initialises a new instance of the ChunkJobQueue class. /// </summary> /// <param name="chunk">The chunk.</param> /// <param name="masterJobs">The current master jobs which must be immediately queued.</param> public ChunkJobQueue(Vector2I chunk, List<Job> masterJobs) { this.Chunk = chunk; this.State = new ChunkJobQueueState(chunk); // Enqueue the existing master jobs if (masterJobs.Count > 0) { foreach (Job job in masterJobs) { job.AddOwners(this); this.Enqueue(job); } } }
/// <summary> /// Gets the chunk indices within the given bounds. /// </summary> /// <param name="bounds">The bounds.</param> /// <returns>The chunk indices.</returns> public static Vector2I[] GetChunks(RectangleI bounds) { var chunks = new Vector2I[bounds.Width * bounds.Height]; int i = 0; for (int x = bounds.X; x < bounds.X + bounds.Width; x++) { for (int y = bounds.Y; y > bounds.Y - bounds.Height; y--) { chunks[i++] = new Vector2I(x, y); } } return chunks; }
/// <summary> /// Initialises a new instance of the TerrainChunk class. /// </summary> /// <param name="chunkIndex">The chunk index.</param> public TerrainChunk(Vector2I chunkIndex) { this.Index = chunkIndex; this.Points = new TerrainPoint[Metrics.ChunkWidth, Metrics.ChunkHeight]; this.Mesh = new TerrainChunkMesh(); // Initialise the points for (int x = 0; x < Metrics.ChunkWidth; x++) { for (int y = 0; y < Metrics.ChunkHeight; y++) { this.Points[x, y] = new TerrainPoint(); } } }
/// <summary> /// Rebuild the MeshData for the given chunk. This is a heavy operation which takes a few frames to complete, /// so is best handled asynchronously. /// </summary> /// <param name="chunkIndex">The chunk index.</param> public void RebuildMesh(Vector2I chunkIndex) { TerrainChunk chunk = this.terrain.GetChunk(chunkIndex); // Initialise the arrays of shared indices var sharedIndices = new SharedIndices(Metrics.ChunkWidth, Metrics.ChunkWidth); // Clear the mesh data chunk.Mesh.Data.Clear(); // Create the mesh for each cell in the chunk var chunkOrigin = Metrics.GetChunkOrigin(chunkIndex); for (int z = -1; z < Metrics.ChunkDepth; z++) { for (int x = chunkOrigin.X; x < chunkOrigin.X + Metrics.ChunkWidth; x++) { for (int y = chunkOrigin.Y; y < chunkOrigin.Y + Metrics.ChunkHeight; y++) { this.CreateMeshCell(new Vector3I(x, y, z), chunk.Mesh.Data, sharedIndices); } } } }
/// <summary> /// Gets the chunks being synchronised. /// </summary> /// <returns>The chunks being synchronised.</returns> public Vector2I[] GetChunks() { var chunks = new Vector2I[this.Chunks.Count]; this.Chunks.Keys.CopyTo(chunks, 0); return chunks; }
/// <summary> /// Indicates that a chunk is ready. /// </summary> /// <param name="chunk">The chunk.</param> public void SetReady(Vector2I chunk) { this.content.SetMeshFilterUpdateReady(chunk); }
/// <summary> /// Remove a chunk. /// </summary> /// <param name="chunkIndex">The chunk index.</param> public void RemoveChunk(Vector2I chunkIndex) { bool removed; lock (this.chunksLock) { removed = this.chunks.Remove(chunkIndex); } if (removed && this.ChunkRemoved != null) { this.ChunkRemoved(this, chunkIndex); } }
/// <summary> /// Gets the chunks. This is a thread-safe operation to be used when accessing the terrain from outside of a /// scheduled job. /// </summary> /// <returns>The chunks.</returns> public Vector2I[] GetChunksThreadSafe() { lock (this.chunksLock) { var chunks = new Vector2I[this.chunks.Count]; this.chunks.Keys.CopyTo(chunks, 0); return chunks; } }
/// <summary> /// Gets the label for the given chunk. /// </summary> /// <param name="chunkIndex">The chunk index.</param> /// <returns>The chunk label.</returns> public static string GetLabel(Vector2I chunkIndex) { return "Chunk[" + chunkIndex.X + "," + chunkIndex.Y + "]"; }
/// <summary> /// Update the MeshFilter data for this chunk. /// </summary> /// <param name="chunksToSync">The chunks that need to have their mesh filter updated in the same frame as /// this. Null if no chunk sync is required.</param> private void UpdateMeshFilterJob(Vector2I[] chunksToSync) { // The chunk GameObject (this) is destroyed after the logical instance, so check that it still exists TerrainChunk chunk; if (chunksToSync == null) { if (TerrainSystem.Instance.Terrain.TryGetChunk(this.Chunk, out chunk)) { // Copy the mesh data into arrays var mesh = new MeshArrays( this.Chunk, chunk.Mesh.Data.Vertices.ToArray(), chunk.Mesh.Data.Normals.ToArray(), chunk.Mesh.Data.Indices.ToArray(), chunk.Mesh.Data.Light.ToArray()); // Update the mesh filter geometry GameScheduler.Instance.Invoke( () => { this.cMeshFilter.mesh.Clear(); this.cMeshFilter.mesh.vertices = mesh.Vertices; this.cMeshFilter.mesh.normals = mesh.Normals; this.cMeshFilter.mesh.triangles = mesh.Triangles; this.cMeshFilter.mesh.colors = mesh.Colors; }); } } else { var meshes = new MeshArrays[chunksToSync.Length]; for (int i = 0; i < meshes.Length; i++) { Vector2I chunkIndex = chunksToSync[i]; if (TerrainSystem.Instance.Terrain.TryGetChunk(chunkIndex, out chunk)) { // Copy the mesh data into arrays meshes[i] = new MeshArrays( chunkIndex, chunk.Mesh.Data.Vertices.ToArray(), chunk.Mesh.Data.Normals.ToArray(), chunk.Mesh.Data.Indices.ToArray(), chunk.Mesh.Data.Light.ToArray()); } } // Update the mesh filter geometry GameScheduler.Instance.Invoke( () => { foreach (MeshArrays mesh in meshes) { if (mesh == null) { continue; } Transform chunkTransform = this.transform.parent.FindChild(TerrainChunkComponent.GetLabel(mesh.Chunk)); if (chunkTransform == null) { continue; } TerrainChunkComponent cChunk = chunkTransform.GetComponent<TerrainChunkComponent>(); cChunk.cMeshFilter.mesh.Clear(); cChunk.cMeshFilter.mesh.vertices = mesh.Vertices; cChunk.cMeshFilter.mesh.normals = mesh.Normals; cChunk.cMeshFilter.mesh.triangles = mesh.Triangles; cChunk.cMeshFilter.mesh.colors = mesh.Colors; } }); } }
/// <summary> /// Check whether a LoadPoints job can execute. /// </summary> /// <param name="chunk">The chunk being loaded.</param> /// <returns>True if the job can be enqueued.</returns> public bool CanLoadPoints(Vector2I chunk) { return this.Chunk != chunk || !this.loadPointsInProgress; }
/// <summary> /// Check whether a DigCircle job can execute. /// </summary> /// <param name="chunk">The chunk in which the origin lies.</param> /// <param name="origin">The circle origin.</param> /// <param name="radius">The circle radius.</param> /// <returns>True if the job can be enqueued.</returns> public bool CanDigCircle(Vector2I chunk, Vector2I origin, int radius) { if (chunk == this.Chunk) { bool exists; int existing; lock ((this.digCircleInProgress as ICollection).SyncRoot) { exists = this.digCircleInProgress.TryGetValue(origin, out existing); } return !exists || radius > existing; } else { return true; } }
/// <summary> /// Determine if the vector is equal. /// </summary> /// <param name="other">The vector to test.</param> /// <returns>True if the vectors are equal.</returns> public bool Equals(Vector2I other) { return other.X == this.X && other.Y == this.Y; }
/// <summary> /// Dig a circle in the terrain with the given origin and radius. /// </summary> /// <param name="origin">The origin.</param> /// <param name="radius">The radius.</param> public void DigCircle(Vector2 origin, float radius) { Vector2I originI = new Vector2I((int)Math.Round(origin.x), (int)Math.Round(origin.y)); int radiusI = (int)radius; Vector2I chunk = Metrics.ChunkIndex(originI.X, originI.Y); // Get the affected chunks var worldBounds = new RectangleI( (int)(origin.x - radius - 0.5f) - 1, (int)(origin.y + radius + 0.5f) + 1, (int)((radius * 2) + 0.5f) + 3, (int)((radius * 2) + 0.5f) + 3); RectangleI chunkBounds = Metrics.WorldToChunk(worldBounds); Vector2I[] chunks = TerrainChunk.GetChunks(chunkBounds); SynchronisedUpdate toSync = chunks.Length > 1 ? new SynchronisedUpdate(chunks) : null; // Enqueue the job JobSystem.Instance.Scheduler.BeginEnqueueChunks(); try { if (JobSystem.Instance.Scheduler.ForAllChunks( chunks, (q) => q.State.CanDigCircle(chunk, originI, radiusI), MissingQueue.Skip)) { JobSystem.Instance.Scheduler.EnqueueChunks( () => this.DigCircleJob(origin, radius), (q) => q.State.ReserveDigCircle(chunk, originI, radiusI, toSync), (q) => q.State.UnreserveDigCircle(chunk, originI, radiusI), false, chunks); } } finally { JobSystem.Instance.Scheduler.EndEnqueueChunks(); } }
/// <summary> /// Set the foreground density of the given point. /// </summary> /// <param name="position">The position.</param> /// <param name="density">The density.</param> private void SetDensityPoint(Vector2I position, byte density) { Vector2I chunkIndex = Metrics.ChunkIndex(position.X, position.Y); // Get the chunk TerrainChunk chunk; if (this.terrain.TryGetChunk(chunkIndex, out chunk)) { Vector2I chunkPos = Metrics.WorldToChunk(position); TerrainPoint point = chunk.Points[chunkPos.X, chunkPos.Y]; if (point != null) { if (point.Foreground < density) { point.Foreground = density; } } } }
/// <summary> /// Un-reserves a RebuildMesh job. /// </summary> /// <param name="chunk">The chunk being rebuilt.</param> public void UnreserveRebuildMesh(Vector2I chunk) { if (this.Chunk == chunk) { this.rebuildMeshInProgress = false; } }
/// <summary> /// Initialises a new instance of the ChunkJobQueueState class. /// </summary> /// <param name="chunk">The chunk.</param> public ChunkJobQueueState(Vector2I chunk) { this.Chunk = chunk; this.digCircleInProgress = new Dictionary<Vector2I, int>(); }
/// <summary> /// Check whether a RebuildMesh job can execute. /// </summary> /// <param name="chunk">The chunk being rebuilt.</param> /// <returns>True if the job can be enqueued.</returns> public bool CanRebuildMesh(Vector2I chunk) { return this.Chunk != chunk || (!this.rebuildMeshInProgress && this.rebuildMeshRequired); }
/// <summary> /// Initialises a new instance of the MeshArrays class. /// </summary> /// <param name="chunk">The chunk.</param> /// <param name="vertices">The vertices.</param> /// <param name="normals">The normal vectors.</param> /// <param name="triangles">The triangles.</param> /// <param name="colors">The colours.</param> public MeshArrays(Vector2I chunk, Vector3[] vertices, Vector3[] normals, int[] triangles, Color[] colors) { this.Chunk = chunk; this.Vertices = vertices; this.Normals = normals; this.Triangles = triangles; this.Colors = colors; }
/// <summary> /// Check whether a UpdateMeshFilter job can execute. /// </summary> /// <param name="chunksToSync">The chunks that need to have their mesh filter updated in the same frame as /// this. Null if no chunk sync is required.</param> /// <returns>True if the job can be enqueued.</returns> public bool CanUpdateMeshFilter(out Vector2I[] chunksToSync) { if (!this.updateMeshFilterInProgress && this.updateMeshFilterRequired) { if (this.meshFilterSync == null) { chunksToSync = null; return true; } else if (this.meshFilterSync.IsSynchronised) { chunksToSync = this.meshFilterSync.GetChunks(); return true; } } chunksToSync = null; return false; }
/// <summary> /// Get the chunk. /// </summary> /// <param name="chunkIndex">The chunk index.</param> /// <returns>The chunk.</returns> public TerrainChunk GetChunk(Vector2I chunkIndex) { return this.chunks[chunkIndex]; }
/// <summary> /// Reserves a DigCircle job. /// </summary> /// <param name="chunk">The chunk in which the origin lies.</param> /// <param name="origin">The circle origin.</param> /// <param name="radius">The circle radius.</param> /// <param name="toSync">The chunks requiring synchronisation such that their mesh filters are updated in /// the same frame.</param> public void ReserveDigCircle(Vector2I chunk, Vector2I origin, int radius, SynchronisedUpdate toSync) { if (chunk == this.Chunk) { lock ((this.digCircleInProgress as ICollection).SyncRoot) { if (this.digCircleInProgress.ContainsKey(origin)) { this.digCircleInProgress[origin] = radius; } else { this.digCircleInProgress.Add(origin, radius); } } } this.rebuildMeshRequired = true; this.meshFilterSync = SynchronisedUpdate.MergeAndReset(this.meshFilterSync, toSync); }
/// <summary> /// Determine if the given chunk exists. /// </summary> /// <param name="chunkIndex">The chunk index.</param> /// <returns>True if the chunk exists.</returns> public bool HasChunk(Vector2I chunkIndex) { return this.chunks.ContainsKey(chunkIndex); }
/// <summary> /// Reserves a LoadPoints job. /// </summary> /// <param name="chunk">The chunk being loaded.</param> public void ReserveLoadPoints(Vector2I chunk) { if (this.Chunk == chunk) { this.loadPointsInProgress = true; } this.rebuildMeshRequired = true; }
/// <summary> /// Try to get the chunk. /// </summary> /// <param name="chunkIndex">The chunk index.</param> /// <param name="chunk">The chunk.</param> /// <returns>True if the chunk exists.</returns> public bool TryGetChunk(Vector2I chunkIndex, out TerrainChunk chunk) { return this.chunks.TryGetValue(chunkIndex, out chunk); }
/// <summary> /// Reserves a RebuildMesh job. /// </summary> /// <param name="chunk">The chunk being rebuilt.</param> public void ReserveRebuildMesh(Vector2I chunk) { if (this.Chunk == chunk) { this.rebuildMeshInProgress = true; // Merge the rebuild mesh sync chunks into the update mesh filter chunks this.updateMeshFilterRequired = true; if (this.meshFilterSync != null) { this.meshFilterSync.SetReady(this.Chunk); } // Reset the rebuild mesh required flags this.rebuildMeshRequired = false; } }
/// <summary> /// Initialises a new instance of the Content class. /// </summary> /// <param name="chunks">The chunks being synchronised.</param> public Content(Vector2I[] chunks) { this.Chunks = new Dictionary<Vector2I, bool>(); foreach (Vector2I chunk in chunks) { this.Chunks.Add(chunk, false); } }
/// <summary> /// Un-reserves a DigCircle job. /// </summary> /// <param name="chunk">The chunk in which the origin lies.</param> /// <param name="origin">The circle origin.</param> /// <param name="radius">The circle radius.</param> public void UnreserveDigCircle(Vector2I chunk, Vector2I origin, int radius) { if (chunk == this.Chunk) { lock ((this.digCircleInProgress as ICollection).SyncRoot) { if (this.digCircleInProgress[origin] == radius) { this.digCircleInProgress.Remove(origin); } } } }
/// <summary> /// Indicates that a chunk is ready for a mesh filter update. /// </summary> /// <param name="chunk">The chunk.</param> public void SetMeshFilterUpdateReady(Vector2I chunk) { if (!this.Chunks[chunk]) { this.Chunks[chunk] = true; this.meshFilterReadyCount++; } }
/// <summary> /// Un-reserves a LoadPoints job. /// </summary> /// <param name="chunk">The chunk being loaded.</param> public void UnreserveLoadPoints(Vector2I chunk) { if (this.Chunk == chunk) { this.loadPointsInProgress = false; this.LoadPointsCompleted = true; } }