/// <summary> /// remove empty chunks that have loaded from the mesh gen queue /// </summary> /// <param name="chunkLocation"></param> /// <returns></returns> protected override bool isAValidQueueItem(Coordinate chunkLocation) { IVoxelChunk chunk = level.getChunk(chunkLocation); // the chunk can't be loaded and empty, and it doesn't already have a loaded mesh. return(!(chunk.isLoaded && chunk.isEmpty)); }
/// <summary> /// Get the chunk at the given location (if it's loaded) /// </summary> /// <param name="chunkLocation">the location of the chunk to grab</param> /// <param name="withMeshes">get the chunk with it's mesh</param> /// <param name="withNeighbors">get the chunk with neighbors linked</param> /// <returns>the chunk data or null if there's none loaded</returns> public IVoxelChunk getChunk(Coordinate chunkLocation, bool withMeshes = false, bool withNeighbors = false, bool withNeighborsNeighbors = false, bool fullNeighborEncasement = false) { // just get an empty chunk for this one if this is out of bounds if (!chunkLocation.isWithin(Coordinate.Zero, chunkBounds)) { return(Chunk.GetEmptyChunk(chunkLocation, withNeighbors)); } IVoxelStorage voxels = chunkDataStorage.getChunkVoxelData(chunkLocation); IVoxelChunk[] neighbors = null; if (withNeighbors) { neighbors = new IVoxelChunk[Directions.All.Length]; foreach (Directions.Direction direction in Directions.All) { Coordinate neighborLocation = chunkLocation + direction.Offset; neighbors[direction.Value] = getChunk(neighborLocation, withMeshes, withNeighborsNeighbors, fullNeighborEncasement); } } return(new Chunk( chunkLocation, voxels, neighbors, withMeshes ? chunkDataStorage.getChunkMesh(chunkLocation) : null )); }
/// <summary> /// remove empty chunks that have loaded from the mesh gen queue /// </summary> /// <param name="chunkLocation"></param> /// <returns></returns> protected override bool isAValidQueueItem(Coordinate chunkLocation) { if (!chunkManager.isWithinManagedBounds(chunkLocation)) { return(false); } IVoxelChunk chunk = chunkManager.level.getChunk(chunkLocation, true); // the chunk can't be loaded and empty, or meshed with an empty mesh. if ((chunk.isLoaded && chunk.isEmpty) || (chunk.isMeshed && chunk.mesh.isEmpty)) { return(false); } /// in this case, we want to make sure the mesh job didn't drop or loose our chunk // @TODO: cull this better, instead of isfull, maybe use a system to determine if it's visible to a player or not, // this notification should only be sent if a chunk needs to be rendered badly because it's missing. if (chunk.isLoaded && !chunk.isMeshed && !chunk.isEmpty && !chunk.isFull) { new Thread(() => { World.EventSystem.notifyChannelOf( new ChunkWaitingForActiveMissingMeshEvent(chunkLocation), Evix.EventSystems.WorldEventSystem.Channels.ChunkActivationUpdates ); }) { Name = "Missing Mesh Messenger" }.Start(); } return(true); }
/// <summary> /// Threaded function, loads all the voxel data for this chunk /// </summary> protected override void doWork(Coordinate chunkLocation) { IVoxelChunk chunk = jobManager.chunkManager.level.getChunk(chunkLocation); if (chunk.isEmpty && !chunk.isLoaded) { VoxelStorageType voxelData = jobManager.chunkManager.getVoxelDataForChunkFromFile(chunkLocation); jobManager.chunkManager.level.chunkDataStorage.setChunkVoxelData(chunkLocation, voxelData); } }
/// <summary> /// Generate the mesh for the voxeldata at the given chunk location /// </summary> /// <param name="chunkLocation"></param> /// <returns></returns> internal IMesh generateMeshDataForChunk(Coordinate chunkLocation) { IVoxelChunk chunk = level.getChunk(chunkLocation, false, true, true, true); if (!chunk.isEmpty) { return(level.meshGenerator.generateMesh(chunk)); } return(new Mesh()); }
/// <summary> /// Threaded function, loads all the voxel data for this chunk /// </summary> protected override void doWork(Coordinate chunkLocation) { // if the chunk is empty, lets try to fill it. IVoxelChunk chunk = jobManager.chunkManager.level.getChunk(chunkLocation); if (chunk.isEmpty && !chunk.isLoaded) { IVoxelStorage voxelData = jobManager.chunkManager.generateVoxelDataForChunk(chunkLocation); jobManager.chunkManager.level.chunkDataStorage.setChunkVoxelData(chunkLocation, voxelData); } }
/// <summary> /// deactivate and free up this object for use again by the level controller /// </summary> public void deactivateAndClear() { gameObject.SetActive(false); currentChunkMesh = new UnityEngine.Mesh(); currentChunkMesh.Clear(); currentChunk = null; chunkLocation = default; colliderBakerHandler = default; isMeshed = false; isActive = false; }
/// <summary> /// generate the chunk mesh if the level doesn't have it yet. /// </summary> protected override void doWork(Coordinate chunkLocation) { IVoxelChunk chunk = jobManager.chunkManager.level.getChunk(chunkLocation, true); if (chunk.isMeshed && !chunk.mesh.isEmpty) { World.EventSystem.notifyChannelOf( new SetChunkObjectActiveEvent(chunkLocation), Evix.EventSystems.WorldEventSystem.Channels.ChunkActivationUpdates ); } }
/// <summary> /// Only to be used by jobs /// Save a chunk to file /// </summary> /// <param name="chunkLocation"></param> internal void saveChunkDataToFile(Coordinate chunkLocation) { IVoxelChunk chunkData = level.getChunk(chunkLocation); if (!chunkData.isEmpty) { IFormatter formatter = new BinaryFormatter(); checkForSaveDirectory(); Stream stream = new FileStream(getChunkFileName(chunkLocation), FileMode.Create, FileAccess.Write, FileShare.None); formatter.Serialize(stream, chunkData.voxels); stream.Close(); } }
/// <summary> /// Set the chunk to render. Returns true if the data was set up /// </summary> /// <param name="chunk"></param> /// <param name="chunkLevelLocation"></param> public bool setChunkToRender(IVoxelChunk chunk, Vector3 chunkLevelLocation) { if (chunk.isLoaded && chunk.mesh != null && !chunk.isEmpty) { currentChunk = chunk; if (chunkLevelLocation.Equals(new Vector3(42, 1, 55))) { Debug.Log("test"); } chunkLocation = chunkLevelLocation; isMeshed = false; return(true); } return(false); }
/// <summary> /// Get notifications from other observers, EX: /// block breaking and placing /// player chunk location changes /// </summary> /// <param name="event">The event to notify this observer of</param> /// <param name="origin">(optional) the source of the event</param> public void notifyOf(IEvent @event, IObserver origin = null) { // ignore events if we have no level to control if (!isLoaded || level == null) { return; } switch (@event) { // when a player spawns in the level case Player.SpawnEvent pse: level.initializeAround(pse.spawnLocation.toChunkLocation()); break; // When the player moves to a new chunk, adjust the loaded level focus case Player.ChangeChunkLocationEvent pccle: level.adjustFocusTo(pccle.newChunkLocation); break; // when the level finishes loading a chunk's mesh. Render it in world case Level <VoxelDictionary> .ChunkMeshGenerationFinishedEvent lcmgfe: UnityChunkController unusedChunkController = getUnusedChunkController(); if (unusedChunkController == null) { Debug.LogError($"No free chunk controller found for {lcmgfe.chunkLocation.ToString()}"); } else { IVoxelChunk chunk = level.getChunk(lcmgfe.chunkLocation, true); if (unusedChunkController.setChunkToRender(chunk, lcmgfe.chunkLocation.vec3)) { chunkControllerActivationQueue.Enqueue(unusedChunkController); } } break; case Level <VoxelDictionary> .ChunkDataLoadingFinishedEvent lcdlfe: loadedChunkLocations.Add(lcdlfe.chunkLocation.vec3); break; // ignore other events default: return; } }
/// <summary> /// remove empty chunks that have loaded from the mesh gen queue /// </summary> /// <param name="chunkLocation"></param> /// <returns></returns> protected override bool isAValidQueueItem(Coordinate chunkLocation) { if (!chunkManager.isWithinManagedBounds(chunkLocation)) { //World.Debugger.log($"{threadName} dropped {chunkLocation} due to it being out of bounds"); return(false); } IVoxelChunk chunk = chunkManager.level.getChunk(chunkLocation); // the chunk can't be loaded and empty, we'll generate nothing. if (chunk.isLoaded && chunk.isEmpty) { //World.Debugger.log($"{threadName} dropped {chunkLocation} due to it being loaded and empty"); return(false); } return(true); }
/// <summary> /// Get notifications from other observers, EX: /// block breaking and placing /// player chunk location changes /// </summary> /// <param name="event">The event to notify this observer of</param> /// <param name="origin">(optional) the source of the event</param> public void notifyOf(IEvent @event, IObserver origin = null) { // ignore events if we have no level to control if (!isLoaded || level == null) { return; } switch (@event) { // when a chunk mesh comes into focus, or loads, set the mesh to a chunkManager case LoadedChunkMeshDataResolutionAperture.ChunkMeshLoadingFinishedEvent cmfle: IVoxelChunk chunk = level.getChunk(cmfle.chunkLocation, true); if (!tryToAssignNewlyMeshedChunkToController(chunk)) { chunksWaitingForAFreeController.Add(chunk); newChunkCount++; } break; // when the level finishes loading a chunk's mesh. Render it in world case ActivateGameobjectResolutionAperture.SetChunkObjectActiveEvent scoae: newlyActivatedChunks.Add(scoae.chunkLocation); newlyActivatedChunksCount++; break; case ActivateGameobjectResolutionAperture.SetChunkObjectInactiveEvent scoie: newlyDeactivatedChunks.Add(scoie.chunkLocation); newlyDeactivatedChunksCount++; break; case LoadedChunkMeshDataResolutionAperture.ChunkMeshMovedOutOfFocusEvent smmoof: if (tryToGetAssignedChunkController(smmoof.chunkLocation, out ChunkController assignedChunkController)) { chunksWithOutOfFocusMeshes.Add(assignedChunkController); outOfFocusMeshesCount++; } break; default: return; } }
///// SUB FUNCTIONS /// <summary> /// Try to assign a chunk to an unused controller. /// </summary> /// <param name="chunkLocation"></param> /// <returns>A bool for being used in Removeall, if the chunk should be removed from the wait queue.</returns> bool tryToAssignNewlyMeshedChunkToController(IVoxelChunk chunk) { if (chunk.isLoaded && chunk.isMeshed && !chunk.isEmpty && !chunk.mesh.isEmpty) { // try to find an unused chunk controller and add it to the queue if it's valid if (getUnusedChunkController(chunk.location.vec3, out ChunkController unusedChunkController)) { unusedChunkController.setChunkToRender(chunk); chunksWithNewlyGeneratedMeshes.Add(unusedChunkController); newlyGeneratedMeshesCount++; return(true); // don't drop it yet, we didn't find a chunk controller. } else { return(false); } // if the chunk isn't meshable, just drop it from the queue } else { return(true); } }
///// PUBLIC FUNCTIONS /// <summary> /// Set the chunk to render. Returns true if the data was set up /// </summary> /// <param name="chunk"></param> /// <param name="chunkLevelLocation"></param> public void setChunkToRender(IVoxelChunk chunk) { currentChunk = chunk; chunkLocation = chunk.location.vec3; isMeshed = false; }
/// <summary> /// Don't activate the chunk until a mesh is loaded for the loaded chunk /// </summary> /// <param name="queueItem"></param> /// <returns></returns> protected override bool itemIsReady(Coordinate chunkLocation) { IVoxelChunk chunk = chunkManager.level.getChunk(chunkLocation, true); return(chunk.isLoaded && chunk.isMeshed && !chunk.mesh.isEmpty); }
/// <summary> /// If the chunk never finished loading, we should not save it /// </summary> /// <param name="chunkLocation"></param> /// <returns></returns> protected override bool isAValidQueueItem(Coordinate chunkLocation) { IVoxelChunk chunk = chunkManager.level.getChunk(chunkLocation); return(chunk.isLoaded); }
/// <summary> /// Don't generate a mesh until a chunk's data is loaded /// </summary> /// <param name="queueItem"></param> /// <returns></returns> protected override bool itemIsReady(Coordinate chunkLocation) { IVoxelChunk chunk = chunkManager.level.getChunk(chunkLocation, false, true, true, true); return(chunk.isLoaded && chunk.neighborsNeighborsAreLoaded); }