// Use this for initialization
    void Start()
    {
        float voxelSize = 1.0f;
        int   size      = 33;

        Array3 <Voxel> voxels = WorldGenerator.CreateVoxels(size, 0, voxelSize, Vector3.zero);
        MeshData       data   = MarchingCubes.CalculateMeshData(voxels, voxelSize);

        data.CalculateNormals();

        Mesh mesh = null;

        if (data != null)
        {
            mesh = data.CreateMesh();
        }

        ChunkObject obj = SplitManager.GetObject();

        obj.ov.shouldDraw = true;
        obj.mr.material   = mat;
        obj.mf.mesh       = mesh;


        Vector3 center = Vector3.one * (size - 1) / 2.0f;
        Bounds  area   = new Bounds(center, Vector3.one * voxelSize * (size - 1));

        obj.ov.init(0, 0, center, area, null, Color.red);
    }
Example #2
0
    // takes in voxel array and MeshData vertices and returns array of mesh normals
    public static Vector3[] CalculateSmoothNormals(Array3 <sbyte> voxels, float voxelSize, Vector3[] verts)
    {
        // calculates the normal of each voxel. If you have a 3d array of data
        // the normal is the derivitive of the x, y and z axis.
        // normally you need to flip the normal (*-1) but it is not needed in this case.
        int size = voxels.size;

        Vector3[][][] normals = Init3DArray <Vector3>(size); // TODO reuse this
        for (int x = 2; x < size - 2; x++)
        {
            for (int y = 2; y < size - 2; y++)
            {
                for (int z = 2; z < size - 2; z++)
                {
                    float dx = voxels[x + 1, y, z] - voxels[x - 1, y, z];
                    float dy = voxels[x, y + 1, z] - voxels[x, y - 1, z];
                    float dz = voxels[x, y, z + 1] - voxels[x, y, z - 1];

                    normals[x][y][z] = Vector3.Normalize(new Vector3(dx, dy, dz) / 128.0f * voxelSize);
                }
            }
        }
        int numVerts = verts.Length;

        Vector3[] meshNorms = new Vector3[numVerts];
        for (int i = 0; i < numVerts; ++i)
        {
            meshNorms[i] = TriLerpNormals(verts[i] / voxelSize, normals);
        }
        return(meshNorms);
    }
Example #3
0
    public static MeshData GenLodCell(Array3 <sbyte> chunk, int lod)
    {
        MeshBuilder mesh = new MeshBuilder();

        sbyte[] density = new sbyte[8];
        int     size    = chunk.size;

        for (int x = 0; x < size; ++x)
        {
            for (int y = 0; y < size; ++y)
            {
                for (int z = 0; z < size; ++z)
                {
                    // send this cell the 8 density values at its corners
                    for (int i = 0; i < 8; ++i)
                    {
                        density[i] = chunk[
                            x + Tables.vertexOffset[i, 0],
                            y + Tables.vertexOffset[i, 1],
                            z + Tables.vertexOffset[i, 2]];
                    }

                    //PolygonizeCell(new Vector3i(x, y, z), mesh, lod);
                }
            }
        }

        return(mesh.ToMeshData());
    }
Example #4
0
File: Chunk.cs Project: mbolt35/Rox
        public Chunk(Vector3 worldPosition)
        {
            World  = worldPosition;
            Bounds = new AxisAlignedBoundingBox(worldPosition, worldPosition + Dimensions);

            _blocks = new Array3 <Block>(Width, Height, Depth);
        }
Example #5
0
    void Start()
    {
        miner = GameObject.Find("Player").GetComponent <VoxelMining>();

        Array3 <Voxel> voxels = WorldGenerator.CreateVoxels(33, 0, 1.0f, Vector3.zero);
        //Array3<Voxel> voxels = WorldGenerator.CreateVoxels(64, 0, 1.0f, Vector3.zero);

        MeshData data = MarchingCubes.CalculateMeshData(voxels, 1.0f);
        Mesh     mesh = data.CreateMesh();

        go                      = SplitManager.GetObject();
        go.mr.enabled           = false;
        go.mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
        go.mf.sharedMesh        = mesh;
        go.mr.material          = testMat;

        MeshData data2 = MarchingTetrahedra.CalculateMeshData(voxels, 1.0f);

        data2.CalculateSharedNormals();
        Mesh mesh2 = data2.CreateMesh();

        go2 = SplitManager.GetObject();
        go2.mf.sharedMesh        = mesh2;
        go2.mr.material          = testMat;
        go2.mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.TwoSided;
        miner.forceDrawChunk     = go2;
    }
        private static UIVertex PickupUIVertexFromTriangle(Vector2 pickupPosition, VerticesOffset vertexPack, Vector2[] vertices2d)
        {
            var inverseArea = 1.0f / GetAreaOfTriangle(vertices2d[0], vertices2d[1], vertices2d[2]);
            var vertices    = vertexPack.GetEnumerable();
            var weights     = new Array3 <float>(GetAreaOfTriangle(pickupPosition, vertices2d[1], vertices2d[2]) * inverseArea
                                                 , GetAreaOfTriangle(vertices2d[0], pickupPosition, vertices2d[2]) * inverseArea
                                                 , GetAreaOfTriangle(vertices2d[0], vertices2d[1], pickupPosition) * inverseArea
                                                 ).GetEnumerable();
            var result = WeightedAverage(vertices, weights);

            return(result);
        }
Example #7
0
    private static Vector3 Interp(Vector3 v0, Vector3 v1, Vector3i p0, Vector3i p1, Array3 <sbyte> voxels)
    {
        sbyte s0 = voxels[p0];
        sbyte s1 = voxels[p1];

        int t = (s1 << 8) / (s1 - s0);
        int u = 0x0100 - t;


        if ((t & 0x00ff) == 0)
        {
            // The generated vertex lies at one of the corners so there
            // is no need to subdivide the interval.
            if (t == 0)
            {
                return(v1);
            }
            return(v0);
        }
        else
        {
            Vector3  vm = (v0 + v1) / 2;
            Vector3i pm = (p0 + p1) / 2;

            sbyte sm = voxels[pm];

            // Determine which of the sub-intervals that contain
            // the intersection with the isosurface.
            if (Sign(s0) != Sign(sm))
            {
                v1 = vm;
                p1 = pm;
                s1 = sm;
            }
            else
            {
                v0 = vm;
                p0 = pm;
                s0 = sm;
            }

            t = (s1 << 8) / (s1 - s0);
            u = 0x0100 - t;

            return(v0 * t * ONE_OVER_256 + v1 * u * ONE_OVER_256);
        }
    }
Example #8
0
        public Chunk(World world, ChunkPosition position, IEnvironmentGenerator environmentGenerator, IChunkRenderer renderer, BlockPrototypeMap prototypeMap)
        {
            Position = position;
            this.world = world;
            this.environmentGenerator = environmentGenerator;
            this.renderer = renderer;

            blockArray = new BlockArray(prototypeMap, XDimension, YDimension, ZDimension);
            lightArray = new Array3<byte>(XDimension, YDimension, ZDimension);

            OriginInWorld = new BlockPosition(Position, new RelativeBlockPosition(0, 0, 0));
            neighborhoodPositions = new[]
            {
                Position,
                Position.Left,
                Position.Right,
                Position.Front,
                Position.Back
            };
        }
Example #9
0
    public override T this[int x, int y, int z]
    {
        get
        {
            if (x < 0 || y < 0 || z < 0)
            {
                return(default(T));
            }

            Vector3i v = new Vector3i(x / chunkSize, y / chunkSize, z / chunkSize) * chunkSize;

            Array3 <T> a;
            if (data.TryGetValue(v, out a))
            {
                return(a[x % chunkSize, y % chunkSize, z % chunkSize]);
            }
            else
            {
                return(default(T));
            }
        }

        set
        {
            Vector3i   v = new Vector3i(x / chunkSize, y / chunkSize, z / chunkSize) * chunkSize;
            Array3 <T> a;

            if (!data.ContainsKey(v))
            {
                a       = new Array3 <T>(chunkSize, v);
                data[v] = a;
            }
            else
            {
                a = data[v];
            }

            a[x % chunkSize, y % chunkSize, z % chunkSize] = value;
        }
    }
    public static MeshData CalculateMeshData(Array3 <Voxel> voxels, float voxelSize)
    {
        List <Vector3> verts = new List <Vector3>();
        List <int>     tris  = new List <int>();

        sbyte[] density = new sbyte[8];

        int end = voxels.size - 1;

        for (int z = 0; z < end; ++z)
        {
            for (int y = 0; y < end; ++y)
            {
                for (int x = 0; x < end; ++x)
                {
                    for (int i = 0; i < 8; ++i)
                    {
                        density[i] = voxels[
                            x + vertexOffset[i][0],
                            y + vertexOffset[i][1],
                            z + vertexOffset[i][2]].density;
                    }

                    March(x, y, z, density, verts, tris);
                }
            }
        }

        MeshData       md     = new MeshData(verts.ToArray(), tris.ToArray());
        List <Color32> colors = new List <Color32>();

        for (int i = 0; i < verts.Count; ++i)
        {
            colors.Add(new Color32(216, 202, 168, 255));
        }
        md.colors = colors.ToArray();
        return(md);
    }
Example #11
0
    public MeshData GenerateMesh(bool createVoxels)
    {
        // so while SIZE is 16, which means theres 16 cells/blocks in grid
        // you need 17 values to be able to construct those blocks
        // (think of 17 points in a grid and the blocks are the 16 spaces in between)
        // if smoothing then need a buffer of 2 around (front and back so +4) for smoothing and normal calculation
        // (so mesh goes from 2-19 basically (0, 1, 20, 21) are not visible in final result)

#if (SMOOTH_SHADING)
        if (createVoxels)
        {
            voxels = WorldGenerator.CreateVoxels(SIZE + 5, depth, voxelSize, pos);
        }
        MeshData data = MarchingCubes.CalculateMeshData(voxels, voxelSize, 2, 2);
        data.CalculateVertexSharing();
        //Simplification simp = new Simplification(data.vertices, data.triangles);
        //data.normals = VoxelUtils.CalculateSmoothNormals(voxels, voxelSize, data.vertices);
        //data.SplitEdgesCalcSmoothness();
        data.CalculateSharedNormals();  // todo figure out why this doesnt make it smoothed...
#else
        if (createVoxels)
        {
            voxels = WorldGenerator.CreateVoxels(SIZE + 1, depth, voxelSize, worldPos);
        }

        //if (!needsMesh) {
        //    return null;
        //}

        //MeshData data = MarchingTetrahedra.CalculateMeshData(voxels, voxelSize);

        MeshData data = MarchingCubes.CalculateMeshData(voxels, voxelSize);
        data.CalculateNormals();
#endif

        //data.CalculateColorsByDepth(depth);
        return(data);
    }
Example #12
0
        public BackwardUpdates(ref Vp9BackwardUpdates counts)
        {
            InterModeCounts = new Array7 <Array3 <Array2 <uint> > >();

            for (int i = 0; i < 7; i++)
            {
                InterModeCounts[i][0][0] = counts.InterMode[i][2];
                InterModeCounts[i][0][1] = counts.InterMode[i][0] + counts.InterMode[i][1] + counts.InterMode[i][3];
                InterModeCounts[i][1][0] = counts.InterMode[i][0];
                InterModeCounts[i][1][1] = counts.InterMode[i][1] + counts.InterMode[i][3];
                InterModeCounts[i][2][0] = counts.InterMode[i][1];
                InterModeCounts[i][2][1] = counts.InterMode[i][3];
            }

            YModeCounts            = counts.YMode;
            UvModeCounts           = counts.UvMode;
            PartitionCounts        = counts.Partition;
            SwitchableInterpsCount = counts.SwitchableInterp;
            IntraInterCount        = counts.IntraInter;
            CompInterCount         = counts.CompInter;
            SingleRefCount         = counts.SingleRef;
            CompRefCount           = counts.CompRef;
            Tx32x32     = counts.Tx32x32;
            Tx16x16     = counts.Tx16x16;
            Tx8x8       = counts.Tx8x8;
            MbSkipCount = counts.Skip;
            Joints      = counts.Joints;
            Sign        = counts.Sign;
            Classes     = counts.Classes;
            Class0      = counts.Class0;
            Bits        = counts.Bits;
            Class0Fp    = counts.Class0Fp;
            Fp          = counts.Fp;
            Class0Hp    = counts.Class0Hp;
            Hp          = counts.Hp;
            CoefCounts  = counts.Coef;
            EobCounts   = counts.EobBranch;
        }
Example #13
0
        public static void SetupDstPlanes(
            ref Array3 <MacroBlockDPlane> planes,
            ref Surface src,
            int miRow,
            int miCol)
        {
            Span <ArrayPtr <byte> > buffers = stackalloc ArrayPtr <byte> [Constants.MaxMbPlane];

            buffers[0] = src.YBuffer;
            buffers[1] = src.UBuffer;
            buffers[2] = src.VBuffer;
            Span <int> strides = stackalloc int[Constants.MaxMbPlane];

            strides[0] = src.Stride;
            strides[1] = src.UvStride;
            strides[2] = src.UvStride;
            int i;

            for (i = 0; i < Constants.MaxMbPlane; ++i)
            {
                ref MacroBlockDPlane pd = ref planes[i];
                SetupPredPlanes(ref pd.Dst, buffers[i], strides[i], miRow, miCol, Ptr <ScaleFactors> .Null, pd.SubsamplingX, pd.SubsamplingY);
            }
Example #14
0
 /// <summary>
 /// Create a new instance of <see cref="GamepadStateSnapshot"/>.
 /// </summary>
 /// <param name="joysticksState">The joysticks state</param>
 /// <param name="buttonsState">The buttons state</param>
 public GamepadStateSnapshot(Array3 <Array2 <float> > joysticksState, Array28 <bool> buttonsState)
 {
     _joysticksState = joysticksState;
     _buttonsState   = buttonsState;
 }
Example #15
0
    // size is length of arrays
    // depth is octree depth
    // voxelSize is how big each voxel should be
    // radius is radius of planet
    // position is starting position of quadtree
    public static Array3 <Voxel> CreateVoxels(
        int size, int depth, float voxelSize, Vector3 pos)
    {
        //float[][][] voxels = VoxelUtils.Init3DArray<float>(size);

        // Vector3i is for some hash lookup shit i think if i wanted to do
        // like hashset<Vector3i, Array3?)
        Array3 <Voxel> voxels = new Array3 <Voxel>(size, Vector3i.Zero);

        //int s = (depth == 0) ? 0 : 1;
        //s = 0;  // temp until figure out data inheritance
        //for (int x = s; x < size; x += s + 1) {
        //    for (int y = s; y < size; y += s + 1) {
        //        for (int z = s; z < size; z += s + 1) {
        //            Vector3 worldPos = new Vector3(x, y, z) * voxelSize + pos;

        //            voxels[x, y, z] = Density.Eval(worldPos);
        //            //voxels[x,y,z] = (sbyte)Mathf.Clamp(Density.Eval(worldPos), -128.0f, 127.0f);

        //        }
        //    }
        //}

        // figure this out more..
        // add command to regenerate with different levels here so you can see changes in realtime
        // to better understand why it isnt working with r as voxelSize / 2.0f

        // sbyte goes from -128 to 127 (so -128, -1 and 0 to 127 should be range)

        int x, y, z;

        for (z = 0; z < size; ++z)
        {
            for (y = 0; y < size; ++y)
            {
                for (x = 0; x < size; ++x)
                {
                    Vector3 worldPos = new Vector3(x, y, z) * voxelSize + pos;
                    voxels[x, y, z] = Density.EvalBigPlanet(worldPos, voxelSize);
                }
            }
        }

        // could incorporate into loops above probably (this is probably microoptimization tho i dunno)
        //// figure out if chunk will have a mesh
        //bool set = false;
        //bool positive = false;
        //for (x = 0; x < size; ++x) {
        //    for (y = 0; y < size; ++y) {
        //        for (z = 0; z < size; ++z) {
        //            if (!set) {
        //                positive = voxels[x, y, z] >= MarchingCubes.isoLevel;
        //                set = true;
        //            }else if((positive && voxels[x,y,z] < MarchingCubes.isoLevel)||
        //                    (!positive && voxels[x,y,z] >= MarchingCubes.isoLevel)) {
        //                needsMesh = true;
        //                return voxels;
        //            }
        //        }
        //    }
        //}
        //needsMesh = false;
        return(voxels);
    }
Example #16
0
 public BlockArray(BlockPrototypeMap prototypeMap, int xDimension, int yDimension, int zDimension)
 {
     this.prototypeMap = prototypeMap;
     blockIndexes = new Array3<byte>(xDimension, yDimension, zDimension);
 }
Example #17
0
 public static byte GetPredProbSegId(ref Array3 <byte> segPredProbs, ref MacroBlockD xd)
 {
     return(segPredProbs[xd.GetPredContextSegId()]);
 }
Example #18
0
    // start and end are used to add padding before and after basically
    public static MeshData CalculateMeshData(Array3 <Voxel> voxels, float voxelSize, int start = 0, int end = 0)
    {
        List <Vector3> verts  = new List <Vector3>();
        List <Color32> colors = new List <Color32>();

        // used to be static but now in here so can be multithreaded
        Vector3[] edgeVertices = new Vector3[12];

        // later should do different materials and stuff using seperate submeshes or multimaterial on same mesh i dunno
        // if want to have cool shiny ore!!!!
        Color32[] edgeColors = new Color32[12];

        Voxel[] cube = new Voxel[8];        // temp storage for 8 voxels corners of cube

        int endLen = voxels.size - 1 - end; // voxel array is always cube
        int materialColorLength = materialColors.Length;
        int x, y, z, i;

        for (z = start; z < endLen; z++)
        {
            for (y = start; y < endLen; y++)
            {
                for (x = start; x < endLen; x++)
                {
                    // get density values of 8 corners for each cube
                    for (i = 0; i < 8; i++)
                    {
                        cube[i] = voxels[
                            x + vertexOffset[i, 0],
                            y + vertexOffset[i, 1],
                            z + vertexOffset[i, 2]];
                    }
                    //MarchCube(new Vector4(x, y, z, voxelSize), density, block, verts, colors, edgeVertices, edgeColors);

                    int   flagIndex = 0;
                    float offset    = 0.0f;

                    //Find which vertices are inside of the surface and which are outside
                    // positive density is inside, negative is outside
                    for (i = 0; i < 8; i++)
                    {
                        if (cube[i].density >= ISOLEVEL)
                        {
                            flagIndex |= 1 << i;
                        }
                    }

                    //Find which edges are intersected by the surface
                    int edgeFlags = cubeEdgeFlags[flagIndex];

                    //If the cube is entirely inside or outside of the surface, then there will be no intersections
                    if (edgeFlags == 0)
                    {
                        continue;
                    }

                    //Check each edge to see if it has a point of intersection with the surface
                    for (i = 0; i < 12; i++)
                    {
                        //if there is an intersection on this edge
                        if ((edgeFlags & (1 << i)) != 0)
                        {
                            // find approximate point of intersection of the surface between two points
                            float v1    = cube[edgeConnection[i, 0]].density;
                            float v2    = cube[edgeConnection[i, 1]].density;
                            float delta = (v2 - v1);
                            offset = (delta == 0.0f) ? 0.5f : (ISOLEVEL - v1) / delta;

                            edgeVertices[i].x = x + (vertexOffset[edgeConnection[i, 0], 0] + offset * edgeDirection[i, 0]);
                            edgeVertices[i].y = y + (vertexOffset[edgeConnection[i, 0], 1] + offset * edgeDirection[i, 1]);
                            edgeVertices[i].z = z + (vertexOffset[edgeConnection[i, 0], 2] + offset * edgeDirection[i, 2]);
                            edgeVertices[i]  *= voxelSize;  // corresponds to voxel size

                            byte m1 = cube[edgeConnection[i, 0]].material;
                            byte m2 = cube[edgeConnection[i, 1]].material;

                            if (m1 < materialColorLength && m2 < materialColorLength)
                            {
                                Color32 c = materialColors[m1];
                                edgeColors[i] = Color32.Lerp(materialColors[m1], materialColors[m2], offset);
                            }
                            else
                            {
                                edgeColors[i] = new Color32(255, 0, 255, 255);  // magenta error color I guess? lols
                            }
                        }
                    }

                    //Save the triangles that were found. There can be up to five per cube
                    for (i = 0; i < 5; i++)
                    {
                        if (triangleConnectionTable[flagIndex, 3 * i] < 0)
                        {
                            break;
                        }

                        int t1 = triangleConnectionTable[flagIndex, 3 * i];
                        int t2 = triangleConnectionTable[flagIndex, 3 * i + 1];
                        int t3 = triangleConnectionTable[flagIndex, 3 * i + 2];

                        verts.Add(edgeVertices[t1]);
                        verts.Add(edgeVertices[t2]);
                        verts.Add(edgeVertices[t3]);

                        colors.Add(edgeColors[t1]);
                        colors.Add(edgeColors[t2]);
                        colors.Add(edgeColors[t3]);
                    }

                    //PolygonizeCell(new Vector3i(x, y, z), density, verts);
                    //TransvoxelExtractor.PolygonizeRegularCell(new Vector3i(x, y, z), voxels, verts, tris);
                }
            }
        }

        MeshData md = new MeshData(verts.ToArray());

        md.colors = colors.ToArray();
        return(md);
        //return new MeshData(verts.ToArray(), tris.ToArray());
    }
Example #19
0
    // pos is xyz position of cell and density is values at corners
    public static void PolygonizeRegularCell(Vector3i pos, Array3 <sbyte> voxels, List <Vector3> verts, List <int> indices)
    {
        byte dirMask = (byte)((pos.x > 0 ? 1 : 0) | ((pos.y > 0 ? 1 : 0) << 1) | ((pos.z > 0 ? 1 : 0) << 2));

        //byte near = 0;
        // compute which six faces of the block that vertex is near (in boundary cell)
        // skip this for now since no transitions

        Vector3i[] corners = Tables.CornerIndex;
        for (int i = 0; i < corners.Length; ++i)
        {
            corners[i] += pos;
        }

        sbyte[] density = new sbyte[] {
            voxels[corners[0]],
            voxels[corners[1]],
            voxels[corners[2]],
            voxels[corners[3]],
            voxels[corners[4]],
            voxels[corners[5]],
            voxels[corners[6]],
            voxels[corners[7]]
        };

        uint caseCode = (uint)(
            ((density[0] >> 7) & 0x01)
            | ((density[1] >> 6) & 0x02)
            | ((density[2] >> 5) & 0x04)
            | ((density[3] >> 4) & 0x08)
            | ((density[4] >> 3) & 0x10)
            | ((density[5] >> 2) & 0x20)
            | ((density[6] >> 1) & 0x40)
            | (density[7] & 0x80));

        var c    = Tables.RegularCellClass[caseCode];
        var data = Tables.RegularCellData[c];

        byte numTris  = (byte)data.GetTriangleCount();
        byte numVerts = (byte)data.GetVertexCount();

        int[] localVertexMapping = new int[12];

        // generate vertex positions by interpolating along
        // each of the edges that intersect the isosurface
        for (int i = 0; i < numVerts; ++i)
        {
            ushort edgeCode = Tables.RegularVertexData[caseCode][i];
            byte   v0       = HiNibble((byte)(edgeCode & 0xFF));
            byte   v1       = LoNibble((byte)(edgeCode & 0xFF));

            Vector3i p0 = corners[v0];
            Vector3i p1 = corners[v1];

            int d0 = voxels[p0];
            int d1 = voxels[p1];

            Debug.Assert(v0 < v1);

            int t = (d1 << 8) / (d1 - d0);
            int u = 0x0100 - t;

            float t0 = t * ONE_OVER_256;
            float t1 = u * ONE_OVER_256;

            if ((t & 0x00ff) != 0)
            {
                // vertex lies in the interior of the edge
                byte dir = HiNibble((byte)(edgeCode >> 8));
                byte idx = LoNibble((byte)(edgeCode >> 8));
                //bool present = (dir & dirMask) == dir;

                localVertexMapping[i] = verts.Count;
                Vector3 pi = Interp(p0.ToVector3(), p1.ToVector3(), p0, p1, voxels);
                verts.Add(pi);
            }
            else if (true || t == 0 && v1 == 7)
            {
                // check if this is right
                Vector3 pi = new Vector3(
                    p0.x * t0 + p1.x * t1,
                    p0.y * t0 + p1.y * t1,
                    p0.z * t0 + p1.z * t1);

                localVertexMapping[i] = verts.Count;
                verts.Add(pi);
            }
        }

        for (int t = 0; t < numTris; ++t)
        {
            for (int i = 0; i < 3; ++i)
            {
                indices.Add(localVertexMapping[data.GetIndices()[t * 3 + i]]);
            }
        }
    }