public PackedUniformVolume ReduceOnce(PackedUniformVolume srcPackedUniformVolume)
        {
            var srcPackedVolumeElementCount = srcPackedUniformVolume.Voxels.Length;

            var srcPackedVolume = new ComputeBuffer(srcPackedVolumeElementCount, sizeof(uint));

            srcPackedVolume.SetData(srcPackedUniformVolume.Voxels);
            _reduceOneComputeShader.SetBuffer(_reduceOneKernelId, "src_packed_volume", srcPackedVolume);

            var srcPackedVolumeDimensions = srcPackedUniformVolume.GetVolumeBitDimensions();

            _reduceOneComputeShader.SetInts("src_packed_volume_bit_dimensions",
                                            srcPackedVolumeDimensions.x,
                                            srcPackedVolumeDimensions.y,
                                            srcPackedVolumeDimensions.z);



            var dstPackedVolumeElementCount = math.max(1, srcPackedVolumeElementCount / 2);
            var dstPackedVolume             = new ComputeBuffer(dstPackedVolumeElementCount, sizeof(uint));

            _reduceOneComputeShader.SetBuffer(_reduceOneKernelId, "dst_packed_volume", dstPackedVolume);

            var dstPackedVolumeDimensions = srcPackedVolumeDimensions / 2;

            _reduceOneComputeShader.SetInts("dst_packed_volume_bit_dimensions",
                                            dstPackedVolumeDimensions.x,
                                            dstPackedVolumeDimensions.y,
                                            dstPackedVolumeDimensions.z);



            var dispatchVolumeDimensions = math.max(new int3(1), dstPackedVolumeDimensions / 8);

            _reduceOneComputeShader.Dispatch(
                _reduceOneKernelId,
                dispatchVolumeDimensions.x,
                dispatchVolumeDimensions.y,
                dispatchVolumeDimensions.z
                );



            var dstPackedVolumeData = new uint[srcPackedVolumeElementCount / 2];

            dstPackedVolume.GetData(dstPackedVolumeData);



            srcPackedVolume.Release();
            dstPackedVolume.Release();

            return(new PackedUniformVolume(
                       srcPackedUniformVolume.VoxelWorldScaleInMeters * 2,
                       srcPackedUniformVolume.Depth - 1)
            {
                Voxels = dstPackedVolumeData
            });
        }
        private void CreateVoxelData()
        {
            PackedUniformVolume =
                new PackedUniformVolume(PackedUniformVolume.VoxelWorldScaleInMeters, PackedUniformVolume.Depth);

            var voxelHalfScale = PackedUniformVolume.GetVolumeWorldScale() / PackedUniformVolume.GetSideBitCount() /
                                 2.0f;

            var index            = 0;
            var volumeDimensions = PackedUniformVolume.GetVolumeBitDimensions();

            for (var y = 0; y < volumeDimensions.y; y++)
            {
                for (var z = 0; z < volumeDimensions.z; z++)
                {
                    for (var x = 0; x < volumeDimensions.x; x++)
                    {
                        var position      = new float3(x, y, z);
                        var worldPosition = (float3)transform.position +
                                            position * PackedUniformVolume.VoxelWorldScaleInMeters;

                        if (Physics.OverlapBox(worldPosition, voxelHalfScale).Length > 0)
                        {
                            var packedIndex = index / 32;
                            var bitIndex    = index % 32;

                            PackedUniformVolume.Voxels[packedIndex] |= 1u << bitIndex;
                        }

                        index++;
                    }
                }
            }

            Debug.Log($"Volume dimensions: {PackedUniformVolume.GetVolumeBitDimensions()}");
        }
        public static Mesh CreateDebugMesh(PackedUniformVolume packedUniformVolume)
        {
            int[] GetQuadIndicesArray(int i0, int i1, int i2, int i3)
            {
                return(new[] {
                    i0,
                    i1,
                    i2,
                    i2,
                    i3,
                    i0,
                });
            }

            var vertices = new List <Vector3>();
            var indices  = new List <int>();

            var voxelHalfScale = packedUniformVolume.GetVolumeWorldScale() / packedUniformVolume.GetSideBitCount() / 2.0f;

            var index            = 0;
            var volumeDimensions = packedUniformVolume.GetVolumeBitDimensions();

            for (var y = 0; y < volumeDimensions.y; y++)
            {
                for (var z = 0; z < volumeDimensions.z; z++)
                {
                    for (var x = 0; x < volumeDimensions.x; x++)
                    {
                        var position      = new float3(x, y, z);
                        var worldPosition = position * packedUniformVolume.VoxelWorldScaleInMeters;

                        var packedIndex = index / 32;
                        var bitIndex    = index % 32;

                        var isOccupied = (packedUniformVolume.Voxels[packedIndex] & (1u << bitIndex)) > 0;

                        if (isOccupied)
                        {
                            vertices.AddRange(new Vector3[] {
                                worldPosition + new float3(-voxelHalfScale.x, -voxelHalfScale.y, -voxelHalfScale.z),
                                worldPosition + new float3(-voxelHalfScale.x, -voxelHalfScale.y, +voxelHalfScale.z),
                                worldPosition + new float3(+voxelHalfScale.x, -voxelHalfScale.y, +voxelHalfScale.z),
                                worldPosition + new float3(+voxelHalfScale.x, -voxelHalfScale.y, -voxelHalfScale.z),

                                worldPosition + new float3(-voxelHalfScale.x, +voxelHalfScale.y, -voxelHalfScale.z),
                                worldPosition + new float3(-voxelHalfScale.x, +voxelHalfScale.y, +voxelHalfScale.z),
                                worldPosition + new float3(+voxelHalfScale.x, +voxelHalfScale.y, +voxelHalfScale.z),
                                worldPosition + new float3(+voxelHalfScale.x, +voxelHalfScale.y, -voxelHalfScale.z),
                            });

                            var vertexCount = vertices.Count;
                            indices.AddRange(GetQuadIndicesArray(vertexCount - 4, vertexCount - 3, vertexCount - 2, vertexCount - 1));
                            indices.AddRange(GetQuadIndicesArray(vertexCount - 5, vertexCount - 6, vertexCount - 7, vertexCount - 8));

                            indices.AddRange(GetQuadIndicesArray(vertexCount - 5, vertexCount - 1, vertexCount - 2, vertexCount - 6));
                            indices.AddRange(GetQuadIndicesArray(vertexCount - 8, vertexCount - 7, vertexCount - 3, vertexCount - 4));

                            indices.AddRange(GetQuadIndicesArray(vertexCount - 8, vertexCount - 4, vertexCount - 1, vertexCount - 5));
                            indices.AddRange(GetQuadIndicesArray(vertexCount - 7, vertexCount - 6, vertexCount - 2, vertexCount - 3));
                        }

                        index++;
                    }
                }
            }

            var mesh = new Mesh
            {
                indexFormat = UnityEngine.Rendering.IndexFormat.UInt32,
                vertices    = vertices.ToArray(),
                triangles   = indices.ToArray()
            };

            mesh.RecalculateNormals();
            return(mesh);
        }
        public List <PackedUniformVolume> Reduce(PackedUniformVolume packedUniformVolume)
        {
            // Calculate the size of the array containing all the voxel data for all layers
            var bitCount = 0;

            for (var i = 0; i <= packedUniformVolume.Depth; i++)
            {
                bitCount += PackedUniformVolume.GetVolumeBitCount(i);
            }

            var dataCount = (int)math.ceil(bitCount / 32.0);

            // Setup an buffer that will contain all the reduction layers and fill it with the finest layer values
            // To start the reduction process
            var packedVolumes = new ComputeBuffer(dataCount, sizeof(uint));

            packedVolumes.SetData(packedUniformVolume.Voxels);
            _reduceComputeShader.SetBuffer(_reduceKernelId, "packed_volumes", packedVolumes);

            // Setup an buffer that will contain all the hashed reduction layers
            var hashedVolumes = new ComputeBuffer(bitCount, sizeof(uint));

            _reduceComputeShader.SetBuffer(_reduceKernelId, "hashed_volumes", hashedVolumes);

            // Bit offsets to use when reading from src layer or writing to the dst layer
            var dstPackedVolumeStartOffsetBitIndex = 0;

            var dstHashedVolumeStartOffsetIndex = 0;

            for (var i = packedUniformVolume.Depth; i > 0; i--)
            {
                // Set the src packed volume to the dst packed volume for next iteration
                var srcPackedVolumeStartOffsetBitIndex = dstPackedVolumeStartOffsetBitIndex;

                var srcHashedVolumeStartOffsetIndex = dstHashedVolumeStartOffsetIndex;

                // Assign the dimensions of the source volume
                var srcPackedVolumeBitDimensions = PackedUniformVolume.GetVolumeBitDimensions(i);
                _reduceComputeShader.SetInts("src_packed_volume_bit_dimensions", srcPackedVolumeBitDimensions.x, srcPackedVolumeBitDimensions.y, srcPackedVolumeBitDimensions.z);
                // Assign the offset to use when reading bits from the src layer
                _reduceComputeShader.SetInt("src_packed_volume_start_offset_bit_index", srcPackedVolumeStartOffsetBitIndex);

                // Assign dimensions of the destination volume
                var dstPackedVolumeBitDimensions = srcPackedVolumeBitDimensions / 2;
                _reduceComputeShader.SetInts("dst_packed_volume_bit_dimensions", dstPackedVolumeBitDimensions.x, dstPackedVolumeBitDimensions.y, dstPackedVolumeBitDimensions.z);
                // Increment destination offset so we write to the area reserved for the next layer
                dstPackedVolumeStartOffsetBitIndex += PackedUniformVolume.GetVolumeBitCount(i);
                _reduceComputeShader.SetInt("dst_packed_volume_start_offset_bit_index", dstPackedVolumeStartOffsetBitIndex);


                _reduceComputeShader.SetInts("src_hashed_volume_dimensions", srcPackedVolumeBitDimensions.x, srcPackedVolumeBitDimensions.y, srcPackedVolumeBitDimensions.z);
                _reduceComputeShader.SetInt("src_hashed_volume_start_offset_index", srcHashedVolumeStartOffsetIndex);

                _reduceComputeShader.SetInts("dst_hashed_volume_dimensions", dstPackedVolumeBitDimensions.x, dstPackedVolumeBitDimensions.y, dstPackedVolumeBitDimensions.z);
                // Increment destination offset so we write to the area reserved for the next layer
                dstHashedVolumeStartOffsetIndex += PackedUniformVolume.GetVolumeBitCount(i);
                _reduceComputeShader.SetInt("dst_hashed_volume_start_offset_index", dstHashedVolumeStartOffsetIndex);


                // Dispatch to the GPU. /8 is used for better utilization of the GPU
                var dispatchVolumeDimensions = math.max(new int3(1), dstPackedVolumeBitDimensions / 8);
                _reduceComputeShader.Dispatch(
                    _reduceKernelId,
                    dispatchVolumeDimensions.x,
                    dispatchVolumeDimensions.y,
                    dispatchVolumeDimensions.z
                    );
            }

            //Collect all the reduction data from the gpu
            var reducedPackedVolumes = new uint[dataCount];

            packedVolumes.GetData(reducedPackedVolumes);

            var reducedHashedVolumes = new uint[bitCount];

            hashedVolumes.GetData(reducedHashedVolumes);

            var packedVolumeList        = new List <PackedUniformVolume>();
            var voxelWorldScaleInMeters = packedUniformVolume.VoxelWorldScaleInMeters;

            var bitOffset = 0;

            for (var i = packedUniformVolume.Depth; i > 0; i--)
            {
                bitCount = PackedUniformVolume.GetVolumeBitCount(i);
                var intCount  = (int)math.ceil(bitCount / 32.0);
                var intOffset = (int)math.ceil(bitOffset / 32.0);
                packedVolumeList.Add(new PackedUniformVolume(voxelWorldScaleInMeters, i)
                {
                    Voxels = reducedPackedVolumes.Skip(intOffset).Take(intCount).ToArray(),
                    Hashes = reducedHashedVolumes.Skip(bitOffset).Take(bitCount).ToArray()
                });

                bitOffset += bitCount;

                voxelWorldScaleInMeters *= 2;
            }

            packedVolumes.Dispose();

            DEBUG = packedVolumeList;

            return(packedVolumeList);
        }
        private void Update()
        {
            if (RecreateVoxels)
            {
                RecreateVoxels = false;

                var startTime = Time.realtimeSinceStartup;
                CreateVoxelData();
                var endTime = Time.realtimeSinceStartup;

                Debug.Log($"Create voxel data time: {endTime - startTime}s");
            }

            if (CreateDebugMesh)
            {
                CreateDebugMesh = false;

                var mesh = VoxelizationVisualizer.CreateDebugMesh(PackedUniformVolume);

                var visualizerGameObject = new GameObject("Debug mesh");
                visualizerGameObject.AddComponent <MeshFilter>().mesh             = mesh;
                visualizerGameObject.AddComponent <MeshRenderer>().sharedMaterial = VisualizerMaterial;
            }

            if (ReduceOneCpu)
            {
                ReduceOneCpu = false;

                SrcPackedVolume = PackedUniformVolume.Voxels;
                SrcPackedVolumeBitDimensions = (uint3)PackedUniformVolume.GetVolumeBitDimensions();

                DstPackedVolume = new uint[SrcPackedVolume.Length / 2];
                DstPackedVolumeBitDimensions = SrcPackedVolumeBitDimensions / 2;

                Reduce(SrcPackedVolume, SrcPackedVolumeBitDimensions, DstPackedVolume, DstPackedVolumeBitDimensions);

                var srcPackedUniformVolume = new PackedUniformVolume(0.1f, 5)
                {
                    Voxels = SrcPackedVolume
                };

                var srcMesh       = VoxelizationVisualizer.CreateDebugMesh(srcPackedUniformVolume);
                var srcGameObject = new GameObject("Reduce one CPU source debug mesh");
                srcGameObject.AddComponent <MeshFilter>().mesh             = srcMesh;
                srcGameObject.AddComponent <MeshRenderer>().sharedMaterial = VisualizerMaterial;

                var dstPackedUniformVolume = new PackedUniformVolume(0.2f, 4)
                {
                    Voxels = DstPackedVolume
                };

                var dstMesh       = VoxelizationVisualizer.CreateDebugMesh(dstPackedUniformVolume);
                var dstGameObject = new GameObject("Reduce one CPU destination debug mesh");
                dstGameObject.AddComponent <MeshFilter>().mesh             = dstMesh;
                dstGameObject.AddComponent <MeshRenderer>().sharedMaterial = VisualizerMaterial;
            }

            if (ReduceOneGpu)
            {
                ReduceOneGpu = false;

                var startTime = Time.realtimeSinceStartup;
                var dstPackedUniformVolume = _svdagManager.ReduceOnce(PackedUniformVolume);
                var endTime = Time.realtimeSinceStartup;

                Debug.Log($"Reduce one GPU time: {endTime-startTime}s");

                var dstMesh       = VoxelizationVisualizer.CreateDebugMesh(dstPackedUniformVolume);
                var dstGameObject = new GameObject("Reduce one GPU debug mesh");
                dstGameObject.AddComponent <MeshFilter>().mesh             = dstMesh;
                dstGameObject.AddComponent <MeshRenderer>().sharedMaterial = VisualizerMaterial;
            }

            if (ReduceGpu)
            {
                ReduceGpu = false;

                var startTime = Time.realtimeSinceStartup;
                var reducedPackedUniformVolumes = _svdagManager.Reduce(PackedUniformVolume);
                var endTime = Time.realtimeSinceStartup;

                Debug.Log($"Reduce GPU time: {endTime - startTime}s");

                foreach (var reducedPackedUniformVolume in reducedPackedUniformVolumes)
                {
                    var dstMesh       = VoxelizationVisualizer.CreateDebugMesh(reducedPackedUniformVolume);
                    var dstGameObject = new GameObject("Reduced GPU debug mesh");
                    dstGameObject.AddComponent <MeshFilter>().mesh             = dstMesh;
                    dstGameObject.AddComponent <MeshRenderer>().sharedMaterial = VisualizerMaterial;
                }
            }
        }