void ClipToIndexSpace(Vector3Int pos, int subdiv, out Vector3Int outMinpos, out Vector3Int outMaxpos, CellIndexUpdateInfo cellInfo) { // to relative coordinates int cellSize = ProbeReferenceVolume.CellSize(subdiv); // The position here is in global space, however we want to constraint this voxel update to the valid cell area var minValidPosition = cellInfo.cellPositionInBricksAtMaxRes + cellInfo.minValidBrickIndexForCellAtMaxRes; var maxValidPosition = cellInfo.cellPositionInBricksAtMaxRes + cellInfo.maxValidBrickIndexForCellAtMaxResPlusOne - Vector3Int.one; int minpos_x = pos.x - m_CenterRS.x; int minpos_y = pos.y; int minpos_z = pos.z - m_CenterRS.z; int maxpos_x = minpos_x + cellSize; int maxpos_y = minpos_y + cellSize; int maxpos_z = minpos_z + cellSize; // clip to valid region minpos_x = Mathf.Max(minpos_x, minValidPosition.x); minpos_y = Mathf.Max(minpos_y, minValidPosition.y); minpos_z = Mathf.Max(minpos_z, minValidPosition.z); maxpos_x = Mathf.Min(maxpos_x, maxValidPosition.x); maxpos_y = Mathf.Min(maxpos_y, maxValidPosition.y); maxpos_z = Mathf.Min(maxpos_z, maxValidPosition.z); outMinpos = new Vector3Int(minpos_x, minpos_y, minpos_z); outMaxpos = new Vector3Int(maxpos_x, maxpos_y, maxpos_z); }
void MapBrickToVoxels(ProbeBrickIndex.Brick brick, HashSet <Vector3Int> voxels) { // create a list of all voxels this brick will touch int brick_subdiv = brick.subdivisionLevel; int voxels_touched_cnt = (int)Mathf.Pow(3, Mathf.Max(0, brick_subdiv - m_VoxelSubdivLevel)); Vector3Int ipos = brick.position; int brick_size = ProbeReferenceVolume.CellSize(brick.subdivisionLevel); int voxel_size = ProbeReferenceVolume.CellSize(m_VoxelSubdivLevel); if (voxels_touched_cnt <= 1) { Vector3 pos = brick.position; pos = pos * (1.0f / voxel_size); ipos = new Vector3Int(Mathf.FloorToInt(pos.x) * voxel_size, Mathf.FloorToInt(pos.y) * voxel_size, Mathf.FloorToInt(pos.z) * voxel_size); } for (int z = ipos.z; z < ipos.z + brick_size; z += voxel_size) { for (int y = ipos.y; y < ipos.y + brick_size; y += voxel_size) { for (int x = ipos.x; x < ipos.x + brick_size; x += voxel_size) { voxels.Add(new Vector3Int(x, y, z)); } } } }
public static void Subdivide(ProbeReferenceVolume.Volume cellVolume, ProbeReferenceVolume refVol, List <ProbeReferenceVolume.Volume> influencerVolumes, ref Vector3[] positions, ref List <ProbeBrickIndex.Brick> bricks) { // TODO move out var indicatorVolumes = new List <ProbeReferenceVolume.Volume>(); foreach (ProbeVolume pv in UnityEngine.Object.FindObjectsOfType <ProbeVolume>()) { if (!pv.enabled) { continue; } indicatorVolumes.Add(new ProbeReferenceVolume.Volume(Matrix4x4.TRS(pv.transform.position, pv.transform.rotation, pv.GetExtents()), pv.parameters.maxSubdivisionMultiplier, pv.parameters.minSubdivisionMultiplier)); } ProbeReferenceVolume.SubdivisionDel subdivDel = (RefTrans refTrans, int subdivisionLevel, List <Brick> inBricks, List <Flags> outFlags) => { SubdivisionAlgorithm(cellVolume, indicatorVolumes, influencerVolumes, refTrans, inBricks, subdivisionLevel, outFlags); }; bricks = new List <ProbeBrickIndex.Brick>(); // get a list of bricks for this volume int numProbes; refVol.CreateBricks(new List <ProbeReferenceVolume.Volume>() { cellVolume }, influencerVolumes, subdivDel, bricks, out numProbes); positions = new Vector3[numProbes]; refVol.ConvertBricks(bricks, positions); }
void UpdateIndexForVoxel(Vector3Int voxel, List <ReservedBrick> bricks, List <ushort> indices) { // clip voxel to index space Vector3Int vx_min, vx_max; ClipToIndexSpace(voxel, m_VoxelSubdivLevel, out vx_min, out vx_max); foreach (var rbrick in bricks) { // clip brick to clipped voxel int brick_cell_size = ProbeReferenceVolume.CellSize(rbrick.brick.subdivisionLevel); Vector3Int brick_min = rbrick.brick.position; Vector3Int brick_max = rbrick.brick.position + Vector3Int.one * brick_cell_size; brick_min.x = Mathf.Max(vx_min.x, brick_min.x - m_CenterRS.x); brick_min.y = Mathf.Max(vx_min.y, brick_min.y); brick_min.z = Mathf.Max(vx_min.z, brick_min.z - m_CenterRS.z); brick_max.x = Mathf.Min(vx_max.x, brick_max.x - m_CenterRS.x); brick_max.y = Mathf.Min(vx_max.y, brick_max.y); brick_max.z = Mathf.Min(vx_max.z, brick_max.z - m_CenterRS.z); Vector3Int bSize = brick_max - brick_min; Vector3Int posIS = m_CenterIS + brick_min; UpdateIndexData(posIS, bSize, rbrick.flattenedIdx); } }
void UpdatePhysicalIndex(Vector3Int brickMin, Vector3Int brickMax, int value, CellIndexUpdateInfo cellInfo) { // We need to do our calculations in local space to the cell, so we move the brick to local space as a first step. // Reminder that at this point we are still operating at highest resolution possible, not necessarily the one that will be // the final resolution for the chunk. brickMin = brickMin - cellInfo.cellPositionInBricksAtMaxRes; brickMax = brickMax - cellInfo.cellPositionInBricksAtMaxRes; // Since the index is spurious (not same resolution, but varying per cell) we need to bring to the output resolution the brick coordinates // Before finding the locations inside the Index for the current cell/chunk. brickMin /= ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell); brickMax /= ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell); // Verify we are actually in local space now. int maxCellSizeInOutputRes = ProbeReferenceVolume.CellSize(ProbeReferenceVolume.instance.GetMaxSubdivision() - 1 - cellInfo.minSubdivInCell); Debug.Assert(brickMin.x >= 0 && brickMin.y >= 0 && brickMin.z >= 0 && brickMax.x >= 0 && brickMax.y >= 0 && brickMax.z >= 0); Debug.Assert(brickMin.x < maxCellSizeInOutputRes && brickMin.y < maxCellSizeInOutputRes && brickMin.z < maxCellSizeInOutputRes && brickMax.x <= maxCellSizeInOutputRes && brickMax.y <= maxCellSizeInOutputRes && brickMax.z <= maxCellSizeInOutputRes); // We are now in the right resolution, but still not considering the valid area, so we need to still normalize against that. // To do so first let's move back the limits to the desired resolution var cellMinIndex = cellInfo.minValidBrickIndexForCellAtMaxRes / ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell); var cellMaxIndex = cellInfo.maxValidBrickIndexForCellAtMaxResPlusOne / ProbeReferenceVolume.CellSize(cellInfo.minSubdivInCell); // Then perform the rescale of the local indices for min and max. brickMin -= cellMinIndex; brickMax -= cellMinIndex; // In theory now we are all positive since we clipped during the voxel stage. Keeping assert for debugging, but can go later. Debug.Assert(brickMin.x >= 0 && brickMin.y >= 0 && brickMin.z >= 0 && brickMax.x >= 0 && brickMax.y >= 0 && brickMax.z >= 0); // Compute the span of the valid part var size = (cellMaxIndex - cellMinIndex); // Loop through all touched indices int chunkStart = cellInfo.firstChunkIndex * kIndexChunkSize; for (int z = brickMin.z; z < brickMax.z; ++z) { for (int y = brickMin.y; y < brickMax.y; ++y) { for (int x = brickMin.x; x < brickMax.x; ++x) { int localFlatIdx = z * (size.x * size.y) + x * size.y + y; int actualIdx = chunkStart + localFlatIdx; m_PhysicalIndexBufferData[actualIdx] = value; m_UpdateMinIndex = Math.Min(actualIdx, m_UpdateMinIndex); m_UpdateMaxIndex = Math.Max(actualIdx, m_UpdateMaxIndex); } } } m_NeedUpdateIndexComputeBuffer = true; }
internal void UpdateCell(int cellFlatIdx, ProbeBrickIndex.CellIndexUpdateInfo cellUpdateInfo) { int minSubdivCellSize = ProbeReferenceVolume.CellSize(cellUpdateInfo.minSubdivInCell); IndexMetaData metaData = new IndexMetaData(); metaData.minSubdiv = cellUpdateInfo.minSubdivInCell; metaData.minLocalIdx = cellUpdateInfo.minValidBrickIndexForCellAtMaxRes / minSubdivCellSize; metaData.maxLocalIdx = cellUpdateInfo.maxValidBrickIndexForCellAtMaxResPlusOne / minSubdivCellSize; metaData.firstChunkIndex = cellUpdateInfo.firstChunkIndex; metaData.Pack(out uint[] packedVals); for (int i = 0; i < kUintPerEntry; ++i) { m_IndexOfIndicesData[cellFlatIdx * kUintPerEntry + i] = packedVals[i]; } m_NeedUpdateComputeBuffer = true; }
void ClipToIndexSpace(Vector3Int pos, int subdiv, out Vector3Int outMinpos, out Vector3Int outMaxpos) { // to relative coordinates int cellSize = ProbeReferenceVolume.CellSize(subdiv); int minpos_x = pos.x - m_CenterRS.x; int minpos_y = pos.y; int minpos_z = pos.z - m_CenterRS.z; int maxpos_x = minpos_x + cellSize; int maxpos_y = minpos_y + cellSize; int maxpos_z = minpos_z + cellSize; // clip to index region minpos_x = Mathf.Max(minpos_x, -m_IndexDim.x / 2); minpos_z = Mathf.Max(minpos_z, -m_IndexDim.z / 2); maxpos_x = Mathf.Min(maxpos_x, m_IndexDim.x / 2); maxpos_z = Mathf.Min(maxpos_z, m_IndexDim.z / 2); outMinpos = new Vector3Int(minpos_x, minpos_y, minpos_z); outMaxpos = new Vector3Int(maxpos_x, maxpos_y, maxpos_z); }
void UpdateIndex(Vector3Int voxel, List <ReservedBrick> bricks, List <ushort> indices) { int base_offset = m_IndexDim.x * m_IndexDim.z; // clip voxel to index space Vector3Int vx_min, vx_max; ClipToIndexSpace(voxel, m_VoxelSubdivLevel, out vx_min, out vx_max); foreach (var rbrick in bricks) { // clip brick to clipped voxel int brick_cell_size = ProbeReferenceVolume.CellSize(rbrick.brick.subdivisionLevel); Vector3Int brick_min = rbrick.brick.position; Vector3Int brick_max = rbrick.brick.position + Vector3Int.one * brick_cell_size; brick_min.x = Mathf.Max(vx_min.x, brick_min.x - m_CenterRS.x); brick_min.y = Mathf.Max(vx_min.y, brick_min.y); brick_min.z = Mathf.Max(vx_min.z, brick_min.z - m_CenterRS.z); brick_max.x = Mathf.Min(vx_max.x, brick_max.x - m_CenterRS.x); brick_max.y = Mathf.Min(vx_max.y, brick_max.y); brick_max.z = Mathf.Min(vx_max.z, brick_max.z - m_CenterRS.z); int bsize_x = brick_max.x - brick_min.x; int bsize_z = brick_max.z - brick_min.z; if (bsize_x <= 0 || bsize_z <= 0) { continue; } for (int idx = 0; idx < brick_cell_size; idx++) { m_TmpUpdater[idx] = rbrick.flattenedIdx; } int posIS_x = m_CenterIS.x + brick_min.x; int posIS_z = m_CenterIS.z + brick_min.z; // iterate over z then x, as y needs special handling for updating the base offset for (int z = 0; z < bsize_z; z++) { for (int x = 0; x < bsize_x; x++) { int mx = (posIS_x + x) % m_IndexDim.x; int mz = (posIS_z + z) % m_IndexDim.z; int hoff_idx = mz * m_IndexDim.x + mx; HeightRange hr = m_HeightRanges[hoff_idx]; if (hr.min == -1) // untouched column { hr.min = brick_min.y; hr.cnt = Mathf.Min(brick_cell_size, m_IndexDim.y); UpdateIndexData(m_TmpUpdater, 0, base_offset + TranslateIndex(mx, 0, mz), hr.cnt); } else { // shift entire column upwards, but without pushing out existing indices int lowest_limit = hr.min - (m_IndexDim.y - hr.cnt); lowest_limit = Mathf.Max(brick_min.y, lowest_limit); int shift_cnt = Mathf.Max(0, hr.min - lowest_limit); int highest_limit = hr.min + m_IndexDim.y; if (shift_cnt == 0) { hr.cnt = Mathf.Max(0, Mathf.Min(m_IndexDim.y, brick_min.y + brick_cell_size - hr.min)); UpdateIndexData(m_TmpUpdater, 0, base_offset + TranslateIndex(mx, brick_min.y - hr.min, mz), Mathf.Min(brick_cell_size, highest_limit - brick_min.y)); } else { int indexTrans = TranslateIndex(mx, 0, mz); GetIndexData(ref m_TmpUpdater, shift_cnt, base_offset + indexTrans, hr.cnt); hr.min = lowest_limit; hr.cnt += shift_cnt; UpdateIndexData(m_TmpUpdater, 0, base_offset + indexTrans, hr.cnt); // restore pool idx array for (int cidx = shift_cnt; cidx < brick_cell_size; cidx++) { m_TmpUpdater[cidx] = rbrick.flattenedIdx; } } } // update the column offset m_HeightRanges[hoff_idx] = hr; m_TmpUpdater[m_TmpUpdater.Length - 1] = hr.min; UpdateIndexData(m_TmpUpdater, m_TmpUpdater.Length - 1, hoff_idx, 1); } } } }
void ClearVoxel(Vector3Int pos) { // clip voxel to index space Vector3Int volMin, volMax; ClipToIndexSpace(pos, m_VoxelSubdivLevel, out volMin, out volMax); int base_offset = m_IndexDim.x * m_IndexDim.z; int volCellSize = ProbeReferenceVolume.CellSize(m_VoxelSubdivLevel); int bsize_x = volMax.x - volMin.x; int bsize_z = volMax.z - volMin.z; if (bsize_x <= 0 || bsize_z <= 0) { return; } for (int idx = 0; idx < volCellSize; idx++) { m_TmpUpdater[idx] = -1; } int posIS_x = m_CenterIS.x + volMin.x; int posIS_z = m_CenterIS.z + volMin.z; // iterate over z then x, as y needs special handling for updating the base offset for (int z = 0; z < bsize_z; z++) { for (int x = 0; x < bsize_x; x++) { int mx = (posIS_x + x) % m_IndexDim.x; int mz = (posIS_z + z) % m_IndexDim.z; int hoff_idx = mz * m_IndexDim.x + mx; HeightRange hr = m_HeightRanges[hoff_idx]; if (hr.min == -1) { continue; } int indexTrans = TranslateIndex(mx, 0, mz); GetIndexData(ref m_TmpUpdater, 0, base_offset + indexTrans, hr.cnt); int start = volMin.y - hr.min; int end = Mathf.Min(start + volCellSize, m_IndexDim.y); start = Mathf.Max(start, 0); for (int i = start; i < end; i++) { m_TmpUpdater[i] = -1; } int hmin = m_IndexDim.y, hmax = -1; for (int i = 0; i < m_IndexDim.y; i++) { if (m_TmpUpdater[i] != -1) { hmin = Mathf.Min(hmin, i); hmax = Mathf.Max(hmax, i); } } bool all_cleared = hmin == m_IndexDim.y; if (all_cleared) { hr.min = -1; hr.cnt = 0; UpdateIndexData(m_TmpUpdater, 0, base_offset + indexTrans, m_IndexDim.y); } else { hr.min += hmin; hr.cnt = hmax - hmin; UpdateIndexData(m_TmpUpdater, hmin, base_offset + indexTrans, m_IndexDim.y - hmin); UpdateIndexData(m_TmpUpdater, 0, base_offset + indexTrans, hmin); } // update the column offset m_HeightRanges[hoff_idx] = hr; m_TmpUpdater[m_TmpUpdater.Length - 1] = hr.min; UpdateIndexData(m_TmpUpdater, m_TmpUpdater.Length - 1, hoff_idx, 1); } } }
public void AddBricks(RegId id, List <Brick> bricks, List <Chunk> allocations, int allocationSize, int poolWidth, int poolHeight) { Debug.Assert(bricks.Count <= ushort.MaxValue, "Cannot add more than 65K bricks per RegId."); int largest_cell = ProbeReferenceVolume.CellSize(kMaxSubdivisionLevels); // create a new copy BrickMeta bm = new BrickMeta(); bm.voxels = new HashSet <Vector3Int>(); bm.bricks = new List <ReservedBrick>(bricks.Count); m_BricksToVoxels.Add(id, bm); int brick_idx = 0; // find all voxels each brick will touch for (int i = 0; i < allocations.Count; i++) { Chunk alloc = allocations[i]; int cnt = Mathf.Min(allocationSize, bricks.Count - brick_idx); for (int j = 0; j < cnt; j++, brick_idx++, alloc.x += ProbeBrickPool.kBrickProbeCountPerDim) { Brick brick = bricks[brick_idx]; int cellSize = ProbeReferenceVolume.CellSize(brick.subdivisionLevel); Debug.Assert(cellSize <= largest_cell, "Cell sizes are not correctly sorted."); largest_cell = Mathf.Min(largest_cell, cellSize); MapBrickToVoxels(brick, bm.voxels); ReservedBrick rbrick = new ReservedBrick(); rbrick.brick = brick; rbrick.flattenedIdx = MergeIndex(alloc.flattenIndex(poolWidth, poolHeight), brick.subdivisionLevel); bm.bricks.Add(rbrick); foreach (var v in bm.voxels) { List <VoxelMeta> vm_list; if (!m_VoxelToBricks.TryGetValue(v, out vm_list)) // first time the voxel is touched { vm_list = new List <VoxelMeta>(1); m_VoxelToBricks.Add(v, vm_list); } VoxelMeta vm; int vm_idx = vm_list.FindIndex((VoxelMeta lhs) => lhs.id == id); if (vm_idx == -1) // first time a brick from this id has touched this voxel { vm.id = id; vm.brickIndices = new List <ushort>(4); vm_list.Add(vm); } else { vm = vm_list[vm_idx]; } // add this brick to the voxel under its regId vm.brickIndices.Add((ushort)brick_idx); } } } foreach (var voxel in bm.voxels) { UpdateIndex(voxel); } }
// This is very much modeled to be as close as possible to the way bricks are loaded in the texture pool. // Not necessarily a good thing. static void ComputeValidityMasks(BakingCell bakingCell) { var bricks = bakingCell.bricks; var cell = bakingCell; int chunkSize = ProbeBrickPool.GetChunkSize(); int brickChunksCount = (bricks.Length + chunkSize - 1) / chunkSize; var probeHasEmptySpaceInGrid = new NativeArray <bool>(bakingCell.probePositions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); int chunkIndex = 0; while (chunkIndex < brickChunksCount) { int chunkToProcess = Math.Min(ProbeReferenceVolume.kTemporaryDataLocChunkCount, brickChunksCount - chunkIndex); Vector3Int locSize = ProbeBrickPool.ProbeCountToDataLocSize(ProbeReferenceVolume.kTemporaryDataLocChunkCount * ProbeBrickPool.GetChunkSizeInProbeCount()); int size = locSize.x * locSize.y * locSize.z; int startIndex = chunkIndex * ProbeBrickPool.GetChunkSizeInProbeCount(); int count = chunkToProcess * ProbeBrickPool.GetChunkSizeInProbeCount(); int shidx = startIndex; int bx = 0, by = 0, bz = 0; s_Validity_locData.Resize(size); s_ProbeIndices.Resize(size); Dictionary <Vector3Int, (float, Vector3, Bounds)> probesToRestoreInfo = new Dictionary <Vector3Int, (float, Vector3, Bounds)>(); HashSet <Vector3Int> probesToRestore = new HashSet <Vector3Int>(); for (int brickIdx = startIndex; brickIdx < (startIndex + count); brickIdx += ProbeBrickPool.kBrickProbeCountTotal) { for (int z = 0; z < ProbeBrickPool.kBrickProbeCountPerDim; z++) { for (int y = 0; y < ProbeBrickPool.kBrickProbeCountPerDim; y++) { for (int x = 0; x < ProbeBrickPool.kBrickProbeCountPerDim; x++) { int ix = bx + x; int iy = by + y; int iz = bz + z; if (shidx >= cell.validity.Length) { StoreScratchData(ix, iy, iz, locSize.x, locSize.y, 1.0f, shidx); } else { StoreScratchData(ix, iy, iz, locSize.x, locSize.y, ProbeReferenceVolume.Cell.GetValidityFromPacked(cell.validity[shidx]), shidx); // Check if we need to do some extra check on this probe. bool hasFreeNeighbourhood = false; Bounds invalidatingTouchupBound; if (s_ForceInvalidatedProbesAndTouchupVols.TryGetValue(cell.probePositions[shidx], out invalidatingTouchupBound)) { int actualBrickIdx = brickIdx / ProbeBrickPool.kBrickProbeCountTotal; float brickSize = ProbeReferenceVolume.CellSize(cell.bricks[actualBrickIdx].subdivisionLevel); Vector3 position = cell.probePositions[shidx]; probesToRestore.Add(new Vector3Int(ix, iy, iz)); var searchDistance = (brickSize * m_BakingProfile.minBrickSize) / ProbeBrickPool.kBrickCellCount; hasFreeNeighbourhood = NeighbourhoodIsEmptySpace(position, searchDistance, invalidatingTouchupBound); } probeHasEmptySpaceInGrid[shidx] = hasFreeNeighbourhood; } shidx++; } } } // update the pool index bx += ProbeBrickPool.kBrickProbeCountPerDim; if (bx >= locSize.x) { bx = 0; by += ProbeBrickPool.kBrickProbeCountPerDim; if (by >= locSize.y) { by = 0; bz += ProbeBrickPool.kBrickProbeCountPerDim; } } } for (int x = 0; x < locSize.x; ++x) { for (int y = 0; y < locSize.y; ++y) { for (int z = 0; z < locSize.z; ++z) { int outIdx = ReadProbeIndex(x, y, z, locSize.x, locSize.y); float probeValidity = ReadValidity(x, y, z, locSize.x, locSize.y); if (outIdx < cell.validity.Length) { float[] validities = new float[8]; bool forceAllValid = false; for (int o = 0; o < 8; ++o) { Vector3Int off = GetSampleOffset(o); Vector3Int samplePos = new Vector3Int(Mathf.Clamp(x + off.x, 0, locSize.x - 1), Mathf.Clamp(y + off.y, 0, locSize.y - 1), Mathf.Clamp(z + off.z, 0, ProbeBrickPool.kBrickProbeCountPerDim - 1)); if (probesToRestore.Contains(samplePos)) { if (probeHasEmptySpaceInGrid[outIdx]) { forceAllValid = true; } } validities[o] = ReadValidity(samplePos.x, samplePos.y, samplePos.z, locSize.x, locSize.y); } byte mask = forceAllValid ? (byte)255 : Convert.ToByte(PackValidity(validities)); float validity = probeValidity; cell.validity[outIdx] = ProbeReferenceVolume.Cell.PackValidityAndMask(validity, mask); } } } } chunkIndex += ProbeReferenceVolume.kTemporaryDataLocChunkCount; } probeHasEmptySpaceInGrid.Dispose(); }
static void DoApplyVirtualOffsets(Vector3[] probePositions, out Vector3[] probeOffsets, VirtualOffsetSettings voSettings) { // Limit memory usage based on ray cast / hit structures (of which there are lots per position) int maxPositionsPerBatch; { var rayCastBytesPerPosition = UnsafeUtility.SizeOf <RaycastCommand>() * kRayDirectionsPerPosition; var rayHitBytesPerPosition = UnsafeUtility.SizeOf <RaycastHit>() * kRayDirectionsPerPosition * voSettings.maxHitsPerRay; var rayDataBytesPerPosition = rayCastBytesPerPosition + rayHitBytesPerPosition; maxPositionsPerBatch = (kMaxMemoryUsage / 2) / rayDataBytesPerPosition; #if VERBOSE Debug.Log($"Running virtual offset over {(probePositions.Length + maxPositionsPerBatch - 1)/maxPositionsPerBatch} batches."); #endif } // This data is shared across all jobs var positions = new NativeArray <Vector3>(probePositions, Allocator.TempJob); var offsets = new NativeArray <Vector3>(probePositions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var searchDistanceForPosition = new NativeArray <float>(positions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); var positionHasColliders = new NativeArray <bool>(positions.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); // Allocate ray cast/hit data var raycastCommands = new[] { new NativeArray <RaycastCommand>(maxPositionsPerBatch * kRayDirectionsPerPosition, Allocator.TempJob, NativeArrayOptions.UninitializedMemory), new NativeArray <RaycastCommand>(maxPositionsPerBatch * kRayDirectionsPerPosition, Allocator.TempJob, NativeArrayOptions.UninitializedMemory) }; { // We need to set a known per-ray maxHits up-front since raycast command schedule reads this at schedule time. This is a bit annoying but it's a // price we'll have to pay right now to be able to create commands from a job. var defaultRaycastCommand = new RaycastCommand(Vector3.zero, Vector3.zero, 0f, 0, voSettings.maxHitsPerRay); for (var i = 0; i < maxPositionsPerBatch * kRayDirectionsPerPosition; ++i) { raycastCommands[0][i] = raycastCommands[1][i] = defaultRaycastCommand; } } var raycastHits = new[] { new NativeArray <RaycastHit>(maxPositionsPerBatch * kRayDirectionsPerPosition * voSettings.maxHitsPerRay, Allocator.TempJob, NativeArrayOptions.UninitializedMemory), new NativeArray <RaycastHit>(maxPositionsPerBatch * kRayDirectionsPerPosition * voSettings.maxHitsPerRay, Allocator.TempJob, NativeArrayOptions.UninitializedMemory) }; // Create job data var createRayCastCommandsJob = new CreateRayCastCommandsJob { voSettings = voSettings, positions = positions, positionHasColliders = positionHasColliders, searchDistanceForPosition = searchDistanceForPosition }; var pushOutGeometryJob = new PushOutGeometryJob { voSettings = voSettings, positions = positions, offsets = offsets, positionHasColliders = positionHasColliders, }; var jobHandles = new JobHandle[2]; try { #if VERBOSE var positionsWithColliders = 0; #endif for (int globalPosIdx = 0, nextBatchIdx = -1; globalPosIdx < positions.Length; globalPosIdx += maxPositionsPerBatch) { // Run a quick overlap check for each search box before setting up rays for the position var batchPosStart = globalPosIdx; var batchPosEnd = Mathf.Min(positions.Length, batchPosStart + maxPositionsPerBatch); for (var batchPosIdx = batchPosStart; batchPosIdx < batchPosEnd; ++batchPosIdx) { m_BakingBatch.uniqueBrickSubdiv.TryGetValue(positions[batchPosIdx], out var subdivLevel); var brickSize = ProbeReferenceVolume.CellSize(subdivLevel); var searchDistance = (brickSize * m_BakingProfile.minBrickSize) / ProbeBrickPool.kBrickCellCount; var scaleForSearchDist = voSettings.searchMultiplier; var distanceSearch = scaleForSearchDist * searchDistance; var positionHasCollider = Physics.CheckBox(positions[batchPosIdx], new Vector3(distanceSearch, distanceSearch, distanceSearch), Quaternion.identity, voSettings.collisionMask); #if VERBOSE if (positionHasCollider) { ++positionsWithColliders; } #endif searchDistanceForPosition[batchPosIdx] = distanceSearch; positionHasColliders[batchPosIdx] = positionHasCollider; } // Swap buffers and sync any already running job at that slot nextBatchIdx = (nextBatchIdx + 1) % 2; jobHandles[nextBatchIdx].Complete(); // Assign ranges and ray/hit arrays createRayCastCommandsJob.startIdx = batchPosStart; createRayCastCommandsJob.endIdx = batchPosEnd; createRayCastCommandsJob.raycastCommands = raycastCommands[nextBatchIdx]; pushOutGeometryJob.startIdx = batchPosStart; pushOutGeometryJob.endIdx = batchPosEnd; pushOutGeometryJob.raycastCommands = raycastCommands[nextBatchIdx]; pushOutGeometryJob.raycastHits = raycastHits[nextBatchIdx]; #if VERBOSE Debug.Log($"Dispatching batch {batchPosStart/maxPositionsPerBatch} {batchPosStart} - {batchPosEnd} using index {nextBatchIdx} (accumulated colliders {positionsWithColliders}"); #endif #if USE_JOBS // Kick off jobs immediately var createRayCastCommandsJobHandle = createRayCastCommandsJob.Schedule(); var raycastCommandsJobHandle = RaycastCommand.ScheduleBatch(raycastCommands[nextBatchIdx], raycastHits[nextBatchIdx], kMinCommandsPerJob, createRayCastCommandsJobHandle); jobHandles[nextBatchIdx] = pushOutGeometryJob.Schedule(raycastCommandsJobHandle); JobHandle.ScheduleBatchedJobs(); #else // Run jobs in-place for easier debugging createRayCastCommandsJob.Run(); RaycastCommand.ScheduleBatch(raycastCommands[nextBatchIdx], raycastHits[nextBatchIdx], kMinCommandsPerJob).Complete(); pushOutGeometryJob.Run(); #endif } // Sync any in-flight jobs (order doesn't matter) JobHandle.CompleteAll(ref jobHandles[0], ref jobHandles[1]); // Copy out result data positions.CopyTo(probePositions); probeOffsets = offsets.ToArray(); #if VERBOSE Debug.Log($"Earlied out {positions.Length - positionsWithColliders}/{positions.Length} probe positions from virtual offset."); Debug.Log($"Working memory used: {(raycastCommands[0].Length * UnsafeUtility.SizeOf<RaycastCommand>() * 2 + raycastHits[0].Length * UnsafeUtility.SizeOf<RaycastHit>() * 2) / 1024 / 1024} MB"); #endif } catch (System.Exception e) { Debug.LogException(e); JobHandle.CompleteAll(ref jobHandles[0], ref jobHandles[1]); probeOffsets = null; } finally { positions.Dispose(); offsets.Dispose(); searchDistanceForPosition.Dispose(); positionHasColliders.Dispose(); raycastCommands[0].Dispose(); raycastCommands[1].Dispose(); raycastHits[0].Dispose(); raycastHits[1].Dispose(); } }