void ReleaseNode(TerrainChunk chunk) { if (chunk != null) { chunk.DestroyChunk(); } }
// Load terrain chunk data from file, this function is called from loading thread void StreamInNodeData_t(TerrainChunk chunk) { try { if (chunk.cancelLoading) { return; } int index = chunk.row * chunkInRow + chunk.col; int offset = dataOffset + nodeDataSize * index; file.Seek(offset, SeekOrigin.Begin); using (BinaryReader br = new BinaryReader(file, Encoding.UTF8, true)) { br.Read(dataBuffer, 0, nodeDataSize); Buffer.BlockCopy(dataBuffer, 0, vertBuffer, 0, nodeDataSize); } int vertNum = (chunkGrid + 1) * (chunkGrid + 1); chunk.FillData(vertBuffer, vertNum, chunkGrid); } catch { Debug.Log("TestTerrain.CreateNode failed!"); } }
// add a loading request for specified chunk void AddLoadingRequest(TerrainChunk chunk) { chunk.inLoadingList = true; lock (loadingRequestList) { #if DEBUG if (loadingRequestList.FindIndex(n => n == chunk) >= 0) { Debug.Log("TestTerrain.AddLoadingRequest, loading request already exist."); } #endif loadingRequestList.Add(chunk); autoLoadingEvents[(int)eLoadingEventsID.TO_LOAD].Set(); } }
// update terrain chunks void UpdateTerrainNodes(Vector3 centerPos) { if (file == null) { return; } // two cache take turns storing terrain chunks List <TerrainChunk> cache = nodeCaches[curCache]; List <TerrainChunk> tempCache = nodeCaches[curCache ^ 1]; float sx = -(widthInGrids / 2) * gridSize; float sz = -sx; float nodeSize = chunkGrid * gridSize; float halfNodeSize = nodeSize * 0.5f; int nodeInView = (int)(viewDistance / nodeSize) + 1; int loadFlag = 0; // 0: remain; 1: load; 2: unload // Lambda function used to move chunk between two caches Action <int, int> MoveNodeInCache = (int r, int c) => { int index = nodeCacheIndices[r, c]; int newIndex = tempCache.Count; tempCache.Add(cache[index]); nodeCacheIndices[r, c] = newIndex; }; // traverse all nodes to see which should be streamed in and which should be streamed out. for (int r = 0; r < chunkInRow; r++) { float nz = sz - nodeSize * r; float nx = sx; for (int c = 0; c < chunkInRow; c++) { loadFlag = 0; float deltaX = Mathf.Abs(centerPos.x - (nx + halfNodeSize)); float deltaZ = Mathf.Abs(centerPos.z - (nz - halfNodeSize)); // check if chunk is in view distance. we give a larger checking distance for unloading // terrain chunks than for loading them, this can prevent chunks from being loaded/unloaded // frequently when camera hovering around a point. if (deltaX - halfNodeSize < viewDistance && deltaZ - halfNodeSize < viewDistance) { // chunk is in view distance loadFlag = 1; } else if (deltaX > viewDistance + halfNodeSize || deltaZ > viewDistance + halfNodeSize) { // chunk is out of view distance loadFlag = 2; } if (loadFlag == 0) { // remain current state. // if chunk has been created, just move it into the other cache if (nodeCacheIndices[r, c] >= 0) { MoveNodeInCache(r, c); } } else if (loadFlag == 1) { // the chunk needs to be loaded if (nodeCacheIndices[r, c] >= 0) { // chunk has been loaded, just move it into the other cache MoveNodeInCache(r, c); } else { // create an empty chunk to hold a place in cache TerrainChunk chunk = new TerrainChunk(r, c); int newIndex = tempCache.Count; tempCache.Add(chunk); nodeCacheIndices[r, c] = newIndex; // send out a loading request for the chunk AddLoadingRequest(chunk); } } else if (loadFlag == 2) { // the chunk needs to be unloaded if (nodeCacheIndices[r, c] >= 0) { int index = nodeCacheIndices[r, c]; TerrainChunk chunk = cache[index]; if (chunk.inLoadingList) { // the chunk has been put into loading list, so we just set its cancelLoading flag // instead of destroying it by calling ReleaseNode() immediately, for loading thread // may be loading data for it right now. In this way, the chunk's destruction is postponed // to GenerateNodeObjects() after its data has been loaded. chunk.cancelLoading = true; } else { // this chunk has finished loading, so it can be released safely. ReleaseNode(chunk); } nodeCacheIndices[r, c] = -1; } } nx += nodeSize; } } // Clear current cache cache.Clear(); // exchange cache curCache ^= 1; }