/// <summary> /// When finished, alert the neighbors too /// </summary> /// <param name="job"></param> public override void onJobComplete(IAdjustmentJob job) { /// since neighbors may need this chunk to load, check if this one loading makes them ready to mesh: /// TODO: check if ForEachDirtiedNeighbor is the right set of neighbors, we may be able to use less if (job.adjustment.type == FocusAdjustmentType.InFocus && lens.tryToGetAperture(resolution + 1, out IChunkResolutionAperture nextApetureInLine) ) { MarchingTetsMeshGenerator.ForEachDirtiedNeighbor(job.adjustment.chunkID, lens.level, chunk => { if (chunk.currentResolution == Chunk.Resolution.Loaded) { #if DEBUG lens.level.getChunk(chunk.id).recordEvent($"Attempting to get apeture job for {(Chunk.Resolution.Meshed, job.adjustment.type)} from neighbor"); #endif if (nextApetureInLine.tryToGetAdjustmentJobHandle( new Adjustment( chunk.id, job.adjustment.type, job.adjustment.resolution + 1, job.adjustment.focusID ), out ApetureJobHandle jobHandle )) { jobHandle.schedule(); #if DEBUG lens.incrementRunningJobCount(job.adjustment.resolution + 1); #endif } } });
protected override bool isValidAndReady(Adjustment adjustment, Chunk chunk) { switch (adjustment.type) { /// for dirty chunks we can just go for it case FocusAdjustmentType.Dirty: return(true); case FocusAdjustmentType.InFocus: /// we can only mesh a newly in focus chunk if it's at the loaded resolution. if (chunk.currentResolution == Chunk.Resolution.Loaded) { bool necessaryNeighborsAreLoaded = true; bool necessaryNeighborsAreEmpty = chunk.isEmpty; bool blockingNeighborsAreSolid = chunk.isSolid; MarchingTetsMeshGenerator.ForEachRequiredNeighbor(adjustment.chunkID, lens.level, (neighborID, neighborChunk) => { bool neighborIsWithinLevelBounds = neighborID.isWithin(Coordinate.Zero, lens.level.chunkBounds); // check if they're loaded. out of bounds chunks will never be loaded and can be skipped if (neighborIsWithinLevelBounds && neighborChunk.currentResolution < Chunk.Resolution.Loaded) { necessaryNeighborsAreLoaded = false; return(false); } // out of bounds chunks will never be loaded and will always be empty, we only need to check in bounds chunks for empty if (neighborIsWithinLevelBounds && !neighborChunk.isEmpty) { necessaryNeighborsAreEmpty = false; } // only in bounds chunks can be solid, so if it's out of bounds or not solid, we mark it so. if (!neighborIsWithinLevelBounds || !neighborChunk.isSolid) { blockingNeighborsAreSolid = false; } return(true); }); if (!necessaryNeighborsAreLoaded) { #if DEBUG chunk.recordEvent($"Chunk is not ready for MeshGenerationAperture job, Necessary neighbors not loaded yet."); #endif return(false); } /// we don't need to load the mesh if it and it's neighbors are all solid or empty if (blockingNeighborsAreSolid || necessaryNeighborsAreEmpty) { /// set mesh as empty if (chunk.adjustmentLockType == (Chunk.Resolution.Meshed, FocusAdjustmentType.InFocus)) { #if DEBUG chunk.recordEvent($"Chunk can skip MeshGenerationAperture queue, {(blockingNeighborsAreSolid ? "solid chunk is hidden" : "required neighbors are all empty")}. Setting empty mesh"); #endif chunk.setMesh(default); return(false); }
/// <summary> /// Capture notifications about dirtied chunks /// </summary> /// <param name="event"></param> public void notifyOf(IEvent @event) { if (@event is Level.ChunkDirtiedEvent cde) { foreach (ChunkResolutionAperture aperture in apeturesByPriority) { if (aperture.resolution == Chunk.Resolution.Meshed && aperture.isWithinManagedBounds(cde.chunkID)) { // throw in jobs to updat the neighbors // TODO: are these the right neighbors? MarchingTetsMeshGenerator.ForEachDirtiedNeighbor( cde.chunkID, level, neighboringChunk => aperture.updateDirtyChunk(neighboringChunk.id, focus) ); // then throw in the current chunk, so it's at position 0 aperture.updateDirtyChunk(cde.chunkID, focus); } } } }
void setUpTestChunk() { // Set up one chunk to load and mesh Level storage = new Level((1, 1, 1), null); Chunk.ID chunkID = new Chunk.ID(0, 0, 0); World.setActiveLevel(storage); levelManager.initializeFor(World.Current.activeLevel); World.EventSystem.subscribe( levelManager, WorldEventSystem.Channels.ChunkActivationUpdates ); // run the load job syncly BiomeMap.GenerateChunkDataFromSourceJob terrainGenJob = BiomeMap.GetTerrainGenerationJob(chunkID, storage); terrainGenJob.Execute(); // get the data from the load job Chunk newlyLoadedChunk = new Chunk(); if (terrainGenJob.solidVoxelCount[0] > 0) { newlyLoadedChunk.setVoxels( terrainGenJob.outVoxels, terrainGenJob.solidVoxelCount[0] ); } terrainGenJob.outVoxels.Dispose(); // add the loaded chunk to storage storage.chunks.Add(chunkID, newlyLoadedChunk); newlyLoadedChunk.isLoaded = true; // get the mesh gen job and run it syncly MarchingTetsMeshGenerator.MarchingTetsMeshGenJob meshGenJob = MarchingTetsMeshGenerator.GetJob(MarchingTetsMeshGenerator.GetVoxelsToMarchOver(chunkID, storage)); meshGenJob.Execute(); // set up the mesh data bool meshIsEmpty = meshGenJob.outVerticies.Length <= 0; VoxelMeshData chunkMeshData = new VoxelMeshData( chunkID, meshIsEmpty, meshGenJob.outVerticies, meshGenJob.outTriangles, meshGenJob.outColors ); // dispose of the allocated resources meshGenJob.outVerticies.Dispose(); meshGenJob.outTriangles.Dispose(); meshGenJob.outColors.Dispose(); // update the chunk data to say if it's meshed if (storage.chunks.TryGetValue(chunkID, out Chunk updatedChunk)) { updatedChunk.meshIsGenerated = true; updatedChunk.meshIsEmpty = meshIsEmpty; } /// notify the chunk manager World.EventSystem.notifyChannelOf( new MeshGenerationAperture.ChunkMeshLoadingFinishedEvent(chunkMeshData), WorldEventSystem.Channels.ChunkActivationUpdates ); // set the chunk active ActiveChunkObjectAperture.ActivateChunkObjectJob activateChunkJob = new ActiveChunkObjectAperture.ActivateChunkObjectJob(chunkID); activateChunkJob.Execute(); }