private void MapBrickToVoxels(ProbeBrickIndex.Brick brick, HashSet <Vector3Int> voxels)
        {
            // create a list of all voxels this brick will touch
            int brick_subdiv       = brick.size;
            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.size);
            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));
                    }
                }
            }
        }
        private 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);
        }
        private void UpdateIndex(Vector3Int voxel, List <ReservedBrick> bricks, List <ushort> indices)
        {
            int base_offset = kAPVConstantsSize + 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.size);
                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);
                            m_IndexBuffer.SetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(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.Min(m_IndexDim.y, brick_min.y + brick_cell_size - hr.min);
                                m_IndexBuffer.SetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(mx, brick_min.y - hr.min, mz)), Mathf.Min(brick_cell_size, highest_limit - brick_min.y));
                            }
                            else
                            {
                                m_IndexBuffer.GetData(m_TmpUpdater, shift_cnt, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), hr.cnt);

                                hr.min  = lowest_limit;
                                hr.cnt += shift_cnt;

                                m_IndexBuffer.SetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), 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;
                        m_IndexBuffer.SetData(m_TmpUpdater, m_TmpUpdater.Length - 1, kAPVConstantsSize + hoff_idx, 1);
                    }
                }
            }
        }
        private void ClearVoxel(Vector3Int pos)
        {
            // clip voxel to index space
            Vector3Int volMin, volMax;

            ClipToIndexSpace(pos, m_VoxelSubdivLevel, out volMin, out volMax);

            int base_offset = kAPVConstantsSize + 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;
                    }


                    m_IndexBuffer.GetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), 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;
                        m_IndexBuffer.SetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), m_IndexDim.y);
                    }
                    else
                    {
                        hr.min += hmin;
                        hr.cnt  = hmax - hmin;
                        m_IndexBuffer.SetData(m_TmpUpdater, hmin, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), m_IndexDim.y - hmin);
                        m_IndexBuffer.SetData(m_TmpUpdater, 0, base_offset + TranslateIndex(new Vector3Int(mx, 0, mz)), hmin);
                    }

                    // update the column offset
                    m_HeightRanges[hoff_idx] = hr;
                    m_TmpUpdater[m_TmpUpdater.Length - 1] = hr.min;
                    m_IndexBuffer.SetData(m_TmpUpdater, m_TmpUpdater.Length - 1, kAPVConstantsSize + 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(15);

            // 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.size);
                    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.size);
                    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);
            }
        }