Beispiel #1
0
 public void OpenNewCell(MyCellCoord coord)
 {
     this.m_cellOpen    = true;
     this.m_currentCell = coord.CoordInLod;
     this.m_packedCoord = coord.PackId64();
     this.m_triangleList.Clear();
 }
Beispiel #2
0
            internal void InvalidateRange(Vector3I lodMin, Vector3I lodMax)
            {
                //              MyLog.Default.WriteLine("InvalidateRange Lod: " + m_lodIndex + " Min: " + lodMin + " Max: " + lodMax);

                var cell = new MyCellCoord(m_lodIndex, lodMin);

                for (var it = new Vector3I_RangeIterator(ref lodMin, ref lodMax);
                     it.IsValid(); it.GetNext(out cell.CoordInLod))
                {
                    MyClipmap_CellData data;
                    var id = cell.PackId64();
//                    MyLog.Default.WriteLine("Setting to: m_lodIndex " + cell.Lod + " Coord: " + cell.CoordInLod);


                    if (m_storedCellData.TryGetValue(id, out data))
                    {
                        data.State = CellState.Invalid;
                        //MyLog.Default.WriteLine("Really set to: m_lodIndex " + cell.Lod + " Coord: " + cell.CoordInLod);
                    }

                    if (MyClipmap.UseCache)
                    {
                        var clipmapCellId = MyCellCoord.GetClipmapCellHash(m_clipmap.Id, id);
                        var cachedCell    = MyClipmap.CellsCache.Read(clipmapCellId);
                        if (cachedCell != null)
                        {
                            cachedCell.State = CellState.Invalid;
                        }
                    }
                }
            }
Beispiel #3
0
 private unsafe void MarkExploredDirections(ref MyNavmeshComponents.ClosedCellInfo cellInfo)
 {
     foreach (Base6Directions.Direction direction in Base6Directions.EnumDirections)
     {
         Base6Directions.DirectionFlags directionFlag = Base6Directions.GetDirectionFlag(direction);
         if (!cellInfo.ExploredDirections.HasFlag(directionFlag))
         {
             Vector3I    intVector = Base6Directions.GetIntVector(direction);
             MyCellCoord coord     = new MyCellCoord {
                 Lod        = 0,
                 CoordInLod = (Vector3I)(this.m_currentCell + intVector)
             };
             if (((coord.CoordInLod.X != -1) && (coord.CoordInLod.Y != -1)) && (coord.CoordInLod.Z != -1))
             {
                 ulong key = coord.PackId64();
                 if (this.m_triangleLists.ContainsKey(key))
                 {
                     this.m_navmeshComponents.MarkExplored(key, Base6Directions.GetFlippedDirection(direction));
                     Base6Directions.DirectionFlags *flagsPtr1 = (Base6Directions.DirectionFlags *) ref cellInfo.ExploredDirections;
                     *((sbyte *)flagsPtr1) = *(((byte *)flagsPtr1)) | Base6Directions.GetDirectionFlag(direction);
                 }
             }
         }
     }
     this.m_navmeshComponents.SetExplored(this.m_packedCoord, cellInfo.ExploredDirections);
 }
Beispiel #4
0
        public void InvalidateRange(Vector3I minVoxelChanged, Vector3I maxVoxelChanged)
        {
            minVoxelChanged -= MyPrecalcComponent.InvalidatedRangeInflate;
            maxVoxelChanged += MyPrecalcComponent.InvalidatedRangeInflate;

            m_voxelMap.Storage.ClampVoxelCoord(ref minVoxelChanged);
            m_voxelMap.Storage.ClampVoxelCoord(ref maxVoxelChanged);

            Vector3I minCell, maxCell;

            MyVoxelCoordSystems.VoxelCoordToGeometryCellCoord(ref minVoxelChanged, out minCell);
            MyVoxelCoordSystems.VoxelCoordToGeometryCellCoord(ref maxVoxelChanged, out maxCell);

            Vector3I currentCell = minCell;

            for (var it = new Vector3I.RangeIterator(ref minCell, ref maxCell); it.IsValid(); it.GetNext(out currentCell))
            {
                if (m_processedCells.Contains(ref currentCell))
                {
                    RemoveCell(currentCell);
                }

                MyCellCoord coord = new MyCellCoord(NAVMESH_LOD, currentCell);
                m_higherLevelHelper.TryClearCell(coord.PackId64());
            }
        }
Beispiel #5
0
        private unsafe void storage_RangeChanged(Vector3I minChanged, Vector3I maxChanged, MyStorageDataTypeFlags changedData)
        {
            Vector3I voxelCoord = minChanged - MyPrecalcComponent.InvalidatedRangeInflate;
            Vector3I vectori2   = (Vector3I)(maxChanged + MyPrecalcComponent.InvalidatedRangeInflate);

            this.m_storage.ClampVoxelCoord(ref voxelCoord, 1);
            this.m_storage.ClampVoxelCoord(ref vectori2, 1);
            Vector3I start = voxelCoord >> 3;
            Vector3I end   = vectori2 >> 3;

            using (this.m_lock.AcquireExclusiveUsing())
            {
                if ((start == Vector3I.Zero) && (end == (this.m_cellsCount - 1)))
                {
                    this.m_cellsByCoordinate.Clear();
                    this.m_coordinateToMesh.Clear();
                    this.m_isEmptyCache.Reset();
                }
                else
                {
                    MyCellCoord cell = new MyCellCoord();
                    if ((this.m_cellsByCoordinate.Count > 0) || (this.m_coordinateToMesh.Count > 0))
                    {
                        cell.CoordInLod = start;
                        Vector3I_RangeIterator iterator = new Vector3I_RangeIterator(ref start, ref end);
                        while (iterator.IsValid())
                        {
                            ulong key = cell.PackId64();
                            this.m_cellsByCoordinate.Remove(key);
                            this.m_coordinateToMesh.Remove(key);
                            iterator.GetNext(out cell.CoordInLod);
                        }
                    }
                    if ((end - start).Volume() <= 0x186a0)
                    {
                        Vector3I_RangeIterator iterator3 = new Vector3I_RangeIterator(ref start, ref end);
                        while (iterator3.IsValid())
                        {
                            this.SetEmpty(ref cell, false);
                            iterator3.GetNext(out cell.CoordInLod);
                        }
                    }
                    else
                    {
                        Vector3I vectori6 = start >> 2;
                        Vector3I vectori7 = (Vector3I)((end >> 2) + 1);
                        cell.CoordInLod = vectori6;
                        Vector3I_RangeIterator iterator2 = new Vector3I_RangeIterator(ref vectori6, ref vectori7);
                        while (iterator2.IsValid())
                        {
                            Vector3I *vectoriPtr1 = (Vector3I *)ref cell.CoordInLod;
                            vectoriPtr1[0] = vectoriPtr1[0] << 2;
                            this.RemoveEmpty(ref cell);
                            iterator2.GetNext(out cell.CoordInLod);
                        }
                    }
                }
            }
        }
Beispiel #6
0
        private unsafe void ComputeIsEmptyLookup(MyCellCoord cell, out ulong outCacheKey, out int outBit)
        {
            Vector3I  vectori     = cell.CoordInLod % 4;
            Vector3I *vectoriPtr1 = (Vector3I *)ref cell.CoordInLod;

            vectoriPtr1[0] = vectoriPtr1[0] >> 2;
            outCacheKey    = cell.PackId64();
            outBit         = vectori.X + (4 * (vectori.Y + (4 * vectori.Z)));
        }
        /// <summary>
        /// Begins processing a voxel geometry cell
        /// </summary>
        public void OpenNewCell(MyCellCoord coord)
        {
            Debug.Assert(m_cellOpen == false, "Cannot open a new cell in MyVoxelHighLevelHelper while another one is open!");

            m_cellOpen    = true;
            m_currentCell = coord.CoordInLod;
            m_packedCoord = coord.PackId64();
            m_triangleList.Clear();
        }
        private void ComputeIsEmptyLookup(MyCellCoord cell, out ulong outCacheKey, out int outBit)
        {
            var offset = cell.CoordInLod % 4;

            cell.CoordInLod >>= 2;
            Debug.Assert(offset.IsInsideInclusive(Vector3I.Zero, new Vector3I(3)));
            outCacheKey = cell.PackId64();
            outBit      = offset.X + 4 * (offset.Y + 4 * offset.Z);
            Debug.Assert((uint)outBit < 64u);
        }
Beispiel #9
0
        internal CellData GetCell(ref MyCellCoord cell)
        {
            MyPrecalcComponent.AssertUpdateThread();

            bool     isEmpty;
            CellData data;

            if (TryGetCell(cell, out isEmpty, out data))
            {
                return(data);
            }

            MyIsoMesh mesh;

            if (!TryGetMesh(cell, out isEmpty, out mesh))
            {
                ProfilerShort.Begin("Cell precalc");
                mesh = MyPrecalcComponent.IsoMesher.Precalc(new MyIsoMesherArgs()
                {
                    Storage      = m_storage,
                    GeometryCell = cell,
                });
                ProfilerShort.End();
            }

            if (mesh != null)
            {
                data = new CellData();
                data.Init(
                    mesh.PositionOffset, mesh.PositionScale,
                    mesh.Positions.GetInternalArray(), mesh.VerticesCount,
                    mesh.Triangles.GetInternalArray(), mesh.TrianglesCount);
            }

            if (cell.Lod == 0)
            {
                using (m_lock.AcquireExclusiveUsing())
                {
                    if (data != null)
                    {
                        var key = cell.PackId64();
                        m_cellsByCoordinate[key] = data;
                    }
                    else
                    {
                        SetEmpty(ref cell, true);
                    }
                }
            }

            return(data);
        }
        public void InvalidateRange(Vector3I minVoxelChanged, Vector3I maxVoxelChanged)
        {
            minVoxelChanged -= MyPrecalcComponent.InvalidatedRangeInflate + 1;
            maxVoxelChanged += MyPrecalcComponent.InvalidatedRangeInflate + 1;
            m_voxelMap.Storage.ClampVoxelCoord(ref minVoxelChanged);
            m_voxelMap.Storage.ClampVoxelCoord(ref maxVoxelChanged);

            Vector3I minCellLod0, maxCellLod0;

            minVoxelChanged -= m_voxelMap.StorageMin;
            maxVoxelChanged -= m_voxelMap.StorageMin;

            MyVoxelCoordSystems.VoxelCoordToRenderCellCoord(0, ref minVoxelChanged, out minCellLod0);
            MyVoxelCoordSystems.VoxelCoordToRenderCellCoord(0, ref maxVoxelChanged, out maxCellLod0);

            MyRenderProxy.InvalidateClipmapRange(m_renderObjectIDs[0], minCellLod0, maxCellLod0);

            if (minCellLod0 == Vector3I.Zero &&
                maxCellLod0 == ((m_voxelMap.Storage.Size - 1) >> MyVoxelCoordSystems.RenderCellSizeInLodVoxelsShift(0)))
            {
                m_renderWorkTracker.InvalidateAll();
                m_mergeWorkTracker.InvalidateAll();
            }
            else
            {
                for (int i = 0; i < MyCellCoord.MAX_LOD_COUNT; ++i)
                {
                    var minCell   = minCellLod0 >> i;
                    var maxCell   = maxCellLod0 >> i;
                    var cellCoord = new MyCellCoord(i, ref minCell);
                    for (var it = new Vector3I_RangeIterator(ref minCell, ref maxCell);
                         it.IsValid(); it.GetNext(out cellCoord.CoordInLod))
                    {
                        m_renderWorkTracker.Invalidate(cellCoord.PackId64());
                        m_mergeWorkTracker.Invalidate(cellCoord.PackId64());
                    }
                }
            }
        }
Beispiel #11
0
 public unsafe void TryClearCell(ulong packedCoord)
 {
     MyNavmeshComponents.CellInfo info;
     if (this.m_triangleLists.ContainsKey(packedCoord))
     {
         this.ClearCachedCell(packedCoord);
     }
     this.RemoveExplored(packedCoord);
     if (this.m_navmeshComponents.TryGetCell(packedCoord, out info))
     {
         for (int i = 0; i < info.ComponentNum; i++)
         {
             int index = info.StartingIndex + i;
             this.m_mesh.HighLevelGroup.RemovePrimitive(index);
         }
         foreach (Base6Directions.Direction direction in Base6Directions.EnumDirections)
         {
             Base6Directions.DirectionFlags directionFlag = Base6Directions.GetDirectionFlag(direction);
             if (info.ExploredDirections.HasFlag(directionFlag))
             {
                 MyNavmeshComponents.CellInfo info2;
                 Vector3I    intVector = Base6Directions.GetIntVector(direction);
                 MyCellCoord coord     = new MyCellCoord();
                 coord.SetUnpack(packedCoord);
                 MyCellCoord *coordPtr1 = (MyCellCoord *)ref coord;
                 coordPtr1->CoordInLod = (Vector3I)(coord.CoordInLod + intVector);
                 if (this.m_navmeshComponents.TryGetCell(coord.PackId64(), out info2))
                 {
                     Base6Directions.DirectionFlags flags2 = Base6Directions.GetDirectionFlag(Base6Directions.GetFlippedDirection(direction));
                     this.m_navmeshComponents.SetExplored(coord.PackId64(), info2.ExploredDirections & ((byte)~flags2));
                 }
             }
         }
         this.m_navmeshComponents.ClearCell(packedCoord, ref info);
     }
 }
Beispiel #12
0
        protected override void ResetInternal(MyStorageDataTypeFlags dataToReset)
        {
            bool resetContent   = (dataToReset & MyStorageDataTypeFlags.Content) != 0;
            bool resetMaterials = (dataToReset & MyStorageDataTypeFlags.Material) != 0;

            if (resetContent)
            {
                m_contentLeaves.Clear();
                m_contentNodes.Clear();
            }

            if (resetMaterials)
            {
                m_materialLeaves.Clear();
                m_materialNodes.Clear();
            }

            if (m_dataProvider != null)
            {
                var cellCoord = new MyCellCoord(m_treeHeight, ref Vector3I.Zero);
                var leafId    = cellCoord.PackId64();
                cellCoord.Lod += LeafLodCount;
                var end = Size - 1;
                if (resetContent)
                {
                    m_contentLeaves.Add(leafId,
                                        new MyProviderLeaf(m_dataProvider, MyStorageDataTypeEnum.Content, ref cellCoord));
                }
                if (resetMaterials)
                {
                    m_materialLeaves.Add(leafId,
                                         new MyProviderLeaf(m_dataProvider, MyStorageDataTypeEnum.Material, ref cellCoord));
                }
            }
            else
            {
                var nodeId = new MyCellCoord(m_treeHeight - 1, ref Vector3I.Zero).PackId64();
                if (resetContent)
                {
                    m_contentNodes.Add(nodeId, new MyOctreeNode());
                }
                if (resetMaterials)
                {
                    m_materialNodes.Add(nodeId, new MyOctreeNode());
                }
            }
        }
Beispiel #13
0
 public void SetMesh(MyCellCoord cell, MyIsoMesh mesh)
 {
     if (cell.Lod == 0)
     {
         using (this.m_lock.AcquireExclusiveUsing())
         {
             if (mesh == null)
             {
                 this.SetEmpty(ref cell, true);
             }
             else
             {
                 ulong num = cell.PackId64();
                 this.m_coordinateToMesh[num] = mesh;
             }
         }
     }
 }
        private MyHighLevelPrimitive GetClosestHighLevelPrimitive(ref Vector3 point, ref float closestDistanceSq)
        {
            MyHighLevelPrimitive retval = null;

            // Convert from world matrix local coords to LeftBottomCorner-based coords
            Vector3 lbcPoint = point + (m_voxelMap.PositionComp.GetPosition() - m_voxelMap.PositionLeftBottomCorner);

            m_tmpIntList.Clear();

            // Collect components from the eight closest cells
            Vector3I closestCellCorner = Vector3I.Round(lbcPoint / m_cellSize);

            for (int i = 0; i < 8; ++i)
            {
                Vector3I cell = closestCellCorner + m_cornerOffsets[i];

                MyCellCoord coord       = new MyCellCoord(NAVMESH_LOD, cell);
                ulong       packedCoord = coord.PackId64();

                m_higherLevelHelper.CollectComponents(packedCoord, m_tmpIntList);
            }

            foreach (int componentIndex in m_tmpIntList)
            {
                var hlPrimitive = m_higherLevel.GetPrimitive(componentIndex);
                Debug.Assert(hlPrimitive != null, "Couldnt' find a high-level primitive for the index given by higher level helper!");
                if (hlPrimitive == null)
                {
                    continue;
                }

                float distSq = Vector3.DistanceSquared(hlPrimitive.Position, point);
                if (distSq < closestDistanceSq)
                {
                    closestDistanceSq = distSq;
                    retval            = hlPrimitive;
                }
            }

            m_tmpIntList.Clear();

            return(retval);
        }
Beispiel #15
0
        private static unsafe void ReadOctreeNodes(Stream stream, ChunkHeader header, ref bool isOldFormat, Dictionary <UInt64, MyOctreeNode> contentNodes)
        {
            switch (header.Version)
            {
            case VERSION_OCTREE_NODES_32BIT_KEY:
            {
                const int entrySize = sizeof(UInt32) + MyOctreeNode.SERIALIZED_SIZE;
                Debug.Assert((header.Size % entrySize) == 0);
                int          nodesCount = header.Size / entrySize;
                MyOctreeNode node;
                MyCellCoord  cell = new MyCellCoord();
                for (int i = 0; i < nodesCount; i++)
                {
                    cell.SetUnpack(stream.ReadUInt32());
                    node.ChildMask = stream.ReadByteNoAlloc();
                    stream.ReadNoAlloc(node.Data, 0, MyOctreeNode.CHILD_COUNT);
                    contentNodes.Add(cell.PackId64(), node);
                }
                isOldFormat = true;
            }
            break;

            case CURRENT_VERSION_OCTREE_NODES:
            {
                const int entrySize = sizeof(UInt64) + MyOctreeNode.SERIALIZED_SIZE;
                Debug.Assert((header.Size % entrySize) == 0);
                int          nodesCount = header.Size / entrySize;
                MyOctreeNode node;
                UInt64       key;
                for (int i = 0; i < nodesCount; i++)
                {
                    key            = stream.ReadUInt64();
                    node.ChildMask = stream.ReadByteNoAlloc();
                    stream.ReadNoAlloc(node.Data, 0, MyOctreeNode.CHILD_COUNT);
                    contentNodes.Add(key, node);
                }
            }
            break;

            default:
                throw new InvalidBranchException();
            }
        }
        private bool RemoveCell(Vector3I cell)
        {
            if (!MyFakes.REMOVE_VOXEL_NAVMESH_CELLS)
            {
                return(true);
            }

            Debug.Assert(m_processedCells.Contains(cell), "Removing a non-existent cell from the navmesh!");
            if (!m_processedCells.Contains(cell))
            {
                return(false);
            }

            MyTrace.Send(TraceWindow.Ai, "Removing cell " + cell);

            ProfilerShort.Begin("Removing navmesh links");
            MyVoxelPathfinding.CellId cellId = new MyVoxelPathfinding.CellId()
            {
                VoxelMap = m_voxelMap, Pos = cell
            };
            m_navmeshCoordinator.RemoveVoxelNavmeshLinks(cellId);
            ProfilerShort.End();

            ProfilerShort.Begin("Removing triangles");
            MyCellCoord    coord        = new MyCellCoord(NAVMESH_LOD, cell);
            ulong          packedCoord  = coord.PackId64();
            MyIntervalList triangleList = m_higherLevelHelper.TryGetTriangleList(packedCoord);

            if (triangleList != null)
            {
                foreach (var triangleIndex in triangleList)
                {
                    RemoveTerrainTriangle(GetTriangle(triangleIndex));
                }
                m_higherLevelHelper.ClearCachedCell(packedCoord);
            }
            ProfilerShort.End();

            Debug.Assert(m_processedCells.Contains(ref cell));
            m_processedCells.Remove(ref cell);

            return(triangleList != null);
        }
        private MyNavigationTriangle GetClosestNavigationTriangle(ref Vector3 point, ref float closestDistanceSq)
        {
            // TODO: When point is completely away (according to BB), return null

            MyNavigationTriangle closestTriangle = null;

            // Convert from world matrix local coords to LeftBottomCorner-based coords
            Vector3 lbcPoint = point + (m_voxelMap.PositionComp.GetPosition() - m_voxelMap.PositionLeftBottomCorner);

            Vector3I closestCellCorner = Vector3I.Round(lbcPoint / m_cellSize);

            for (int i = 0; i < 8; ++i)
            {
                Vector3I cell = closestCellCorner + m_cornerOffsets[i];
                if (!m_processedCells.Contains(cell))
                {
                    continue;
                }

                MyCellCoord    coord       = new MyCellCoord(NAVMESH_LOD, cell);
                ulong          packedCoord = coord.PackId64();
                MyIntervalList triList     = m_higherLevelHelper.TryGetTriangleList(packedCoord);
                if (triList == null)
                {
                    continue;
                }

                foreach (var triIndex in triList)
                {
                    MyNavigationTriangle tri = GetTriangle(triIndex);

                    // TODO: Use triangle centers so far
                    float distSq = Vector3.DistanceSquared(tri.Center, point);
                    if (distSq < closestDistanceSq)
                    {
                        closestDistanceSq = distSq;
                        closestTriangle   = tri;
                    }
                }
            }

            return(closestTriangle);
        }
Beispiel #18
0
        internal void OnCellRequest(MyCellCoord cell, bool highPriority, Func <int> priorityFunction, Action <Color> debugDraw)
        {
            ProfilerShort.Begin("OnCellRequest");

            try
            {
                var workId = cell.PackId64();
                MyPrecalcJobRender job;
                if (m_renderWorkTracker.TryGet(workId, out job))
                {
                    if (!highPriority)
                    { // low priority work, no need to do anything
                        return;
                    }

                    if (job.IsHighPriority)
                    { // both are high priorities, so just invalidate previous one
                        m_renderWorkTracker.Invalidate(workId);
                        return;
                    }

                    // high priority arrived while there was one with low priority ... just cancel lower one
                    m_renderWorkTracker.Cancel(workId);
                }

                MyPrecalcJobRender.Start(new MyPrecalcJobRender.Args()
                {
                    Storage           = m_voxelMap.Storage,
                    ClipmapId         = ClipmapId,
                    Cell              = cell,
                    WorkId            = workId,
                    RenderWorkTracker = m_renderWorkTracker,
                    IsHighPriority    = highPriority,
                    Priority          = priorityFunction,
                    DebugDraw         = debugDraw,
                });
            }
            finally
            {
                ProfilerShort.End();
            }
        }
Beispiel #19
0
        protected void SaveInternal(MemoryStream stream)
        {
            WriteStorageMetaData(stream);
            WriteMaterialTable(stream);
            WriteDataProvider(stream, DataProvider);
            WriteOctreeNodes(stream, ChunkTypeEnum.MacroContentNodes);
            WriteOctreeNodes(stream, ChunkTypeEnum.MacroMaterialNodes);

            var cellCoord = new MyCellCoord(m_treeHeight, ref Vector3I.Zero);
            var leafId    = cellCoord.PackId64();

            cellCoord.Lod += LeafLodCount;

            WriteEmptyProviderLeaf(stream, leafId, ChunkTypeEnum.ContentLeafProvider);
            WriteEmptyProviderLeaf(stream, leafId, ChunkTypeEnum.MaterialLeafProvider);

            new ChunkHeader()
            {
                ChunkType = ChunkTypeEnum.EndOfFile,
            }.WriteTo(stream);
        }
        public void SetMesh(MyCellCoord cell, MyIsoMesh mesh)
        {
            // Don't store anything but the most detailed lod (used in physics and raycasts).
            // This cache is mostly supposed to help physics and raycasts, not render.
            if (cell.Lod != 0)
            {
                return;
            }

            MyPrecalcComponent.AssertUpdateThread();
            using (m_lock.AcquireExclusiveUsing())
            {
                if (mesh != null)
                {
                    var key = cell.PackId64();
                    m_coordinateToMesh[key] = mesh;
                }
                else
                {
                    SetEmpty(ref cell, true);
                }
            }
        }
        public bool TryGetMesh(MyCellCoord cell, out bool isEmpty, out MyIsoMesh nonEmptyMesh)
        {
            using (m_lock.AcquireSharedUsing())
            {
                if (IsEmpty(ref cell))
                {
                    isEmpty      = true;
                    nonEmptyMesh = null;
                    return(true);
                }

                UInt64 key = cell.PackId64();
                if (m_coordinateToMesh.TryGetValue(key, out nonEmptyMesh))
                {
                    isEmpty = false;
                    return(true);
                }

                isEmpty      = default(bool);
                nonEmptyMesh = default(MyIsoMesh);
                return(false);
            }
        }
        /// <param name="minVoxelChanged">Inclusive min.</param>
        /// <param name="maxVoxelChanged">Inclusive max.</param>
        private void storage_RangeChanged(Vector3I minChanged, Vector3I maxChanged, MyStorageDataTypeFlags changedData)
        {
            MyPrecalcComponent.AssertUpdateThread();

            ProfilerShort.Begin("MyVoxelGeometry.storage_RangeChanged");

            minChanged -= MyPrecalcComponent.InvalidatedRangeInflate;
            maxChanged += MyPrecalcComponent.InvalidatedRangeInflate;
            m_storage.ClampVoxelCoord(ref minChanged);
            m_storage.ClampVoxelCoord(ref maxChanged);
            var minCellChanged = minChanged >> MyVoxelConstants.GEOMETRY_CELL_SIZE_IN_VOXELS_BITS;
            var maxCellChanged = maxChanged >> MyVoxelConstants.GEOMETRY_CELL_SIZE_IN_VOXELS_BITS;

            using (m_lock.AcquireExclusiveUsing())
            {
                if (minCellChanged == Vector3I.Zero && maxCellChanged == m_cellsCount - 1)
                {
                    m_cellsByCoordinate.Clear();
                    m_coordinateToMesh.Clear();
                    m_isEmptyCache.Reset();
                }
                else
                {
                    MyCellCoord cell = new MyCellCoord();
                    cell.CoordInLod = minCellChanged;
                    for (var it = new Vector3I.RangeIterator(ref minCellChanged, ref maxCellChanged); it.IsValid(); it.GetNext(out cell.CoordInLod))
                    {
                        var key = cell.PackId64();
                        m_cellsByCoordinate.Remove(key);
                        m_coordinateToMesh.Remove(key);
                        SetEmpty(ref cell, false);
                    }
                }
            }

            ProfilerShort.End();
        }
Beispiel #23
0
        internal CellData GetCell(ref MyCellCoord cell)
        {
            bool     flag;
            CellData data;

            if (!this.TryGetCell(cell, out flag, out data))
            {
                MyIsoMesh mesh;
                if (!this.TryGetMesh(cell, out flag, out mesh))
                {
                    Vector3I lodVoxelMin = cell.CoordInLod << 3;
                    lodVoxelMin -= 1;
                    mesh         = MyPrecalcComponent.IsoMesher.Precalc(this.m_storage, 0, lodVoxelMin, (Vector3I)((lodVoxelMin + 8) + 2), MyStorageDataTypeFlags.Content, 0);
                }
                if (mesh != null)
                {
                    data = new CellData();
                    data.Init((Vector3)mesh.PositionOffset, mesh.PositionScale, mesh.Positions.GetInternalArray <Vector3>(), mesh.VerticesCount, mesh.Triangles.GetInternalArray <MyVoxelTriangle>(), mesh.TrianglesCount);
                }
                if (cell.Lod == 0)
                {
                    using (this.m_lock.AcquireExclusiveUsing())
                    {
                        if (data == null)
                        {
                            this.SetEmpty(ref cell, true);
                        }
                        else
                        {
                            ulong num = cell.PackId64();
                            this.m_cellsByCoordinate[num] = data;
                        }
                    }
                }
            }
            return(data);
        }
        private bool TryGetCell(MyCellCoord cell, out bool isEmpty, out CellData nonEmptyCell)
        {
            MyPrecalcComponent.AssertUpdateThread();
            using (m_lock.AcquireSharedUsing())
            {
                if (IsEmpty(ref cell))
                {
                    isEmpty      = true;
                    nonEmptyCell = null;
                    return(true);
                }

                UInt64 key = cell.PackId64();
                if (m_cellsByCoordinate.TryGetValue(key, out nonEmptyCell))
                {
                    isEmpty = false;
                    return(true);
                }

                isEmpty      = default(bool);
                nonEmptyCell = default(CellData);
                return(false);
            }
        }
        internal void OnCellRequest(MyCellCoord cell, Func <int> priorityFunction, Action <Color> debugDraw)
        {
            ProfilerShort.Begin("OnCellRequest");

            try
            {
                var workId = cell.PackId64();

                MyPrecalcJobRender.Start(new MyPrecalcJobRender.Args
                {
                    Storage           = m_voxelMap.Storage,
                    ClipmapId         = ClipmapId,
                    Cell              = cell,
                    WorkId            = workId,
                    RenderWorkTracker = m_renderWorkTracker,
                    Priority          = priorityFunction,
                    DebugDraw         = debugDraw,
                });
            }
            finally
            {
                ProfilerShort.End();
            }
        }
        internal void OnCellRequestCancelled(MyCellCoord cell)
        {
            var workId = cell.PackId64();

            m_renderWorkTracker.Cancel(workId);
        }
        internal CellData GetCell(ref MyCellCoord cell)
        {
            MyPrecalcComponent.AssertUpdateThread();

            bool     isEmpty;
            CellData data;

            if (TryGetCell(cell, out isEmpty, out data))
            {
                return(data);
            }

            MyIsoMesh mesh;

            if (!TryGetMesh(cell, out isEmpty, out mesh))
            {
                ProfilerShort.Begin("Cell precalc");
                if (true)
                {
                    var min = cell.CoordInLod << MyVoxelConstants.GEOMETRY_CELL_SIZE_IN_VOXELS_BITS;
                    var max = min + MyVoxelConstants.GEOMETRY_CELL_SIZE_IN_VOXELS;
                    // overlap to neighbor; introduces extra data but it makes logic for raycasts simpler (no need to check neighbor cells)
                    min -= 1;
                    max += 2;
                    mesh = MyPrecalcComponent.IsoMesher.Precalc(m_storage, 0, min, max, false, false);
                }
                else
                {
                    mesh = MyPrecalcComponent.IsoMesher.Precalc(new MyIsoMesherArgs()
                    {
                        Storage      = m_storage,
                        GeometryCell = cell,
                    });
                }
                ProfilerShort.End();
            }

            if (mesh != null)
            {
                data = new CellData();
                data.Init(
                    mesh.PositionOffset, mesh.PositionScale,
                    mesh.Positions.GetInternalArray(), mesh.VerticesCount,
                    mesh.Triangles.GetInternalArray(), mesh.TrianglesCount);
            }

            if (cell.Lod == 0)
            {
                using (m_lock.AcquireExclusiveUsing())
                {
                    if (data != null)
                    {
                        var key = cell.PackId64();
                        m_cellsByCoordinate[key] = data;
                    }
                    else
                    {
                        SetEmpty(ref cell, true);
                    }
                }
            }

            return(data);
        }
        public void ProcessChangedCellComponents()
        {
            ProfilerShort.Begin("ProcessChangedCellComponents");

            m_currentHelper = this;

            Vector3I   min, max, pos;
            List <int> triangles = null;

            foreach (var cell in m_changedCells)
            {
                MyCellCoord cellCoord  = new MyCellCoord(0, cell);
                ulong       packedCell = cellCoord.PackId64();

                m_components.OpenCell(packedCell);

                min = CellToLowestCube(cell);
                max = min + m_cellSize - Vector3I.One;

                // Save a hashset of all the triangles in the current cell
                pos = min;
                for (var it = new Vector3I.RangeIterator(ref min, ref max); it.IsValid(); it.GetNext(out pos))
                {
                    if (!m_triangleRegistry.TryGetValue(pos, out triangles))
                    {
                        continue;
                    }

                    foreach (var triIndex in triangles)
                    {
                        m_tmpCellTriangles.Add(triIndex);
                    }
                }

                long timeBegin = m_mesh.GetCurrentTimestamp() + 1;
                long timeEnd   = timeBegin;
                m_currentComponentRel = 0;

                foreach (var triIndex in m_tmpCellTriangles)
                {
                    // Skip already visited triangles
                    var triangle = m_mesh.GetTriangle(triIndex);
                    if (m_currentComponentRel != 0 && m_mesh.VisitedBetween(triangle, timeBegin, timeEnd))
                    {
                        continue;
                    }

                    m_components.OpenComponent();

                    // Make sure we have place in m_currentCellConnections
                    if (m_currentComponentRel >= m_currentCellConnections.Count)
                    {
                        m_currentCellConnections.Add(new List <int>());
                    }

                    // Find connected component from an unvisited triangle and mark its connections
                    m_components.AddComponentTriangle(triangle, triangle.Center);
                    triangle.ComponentIndex = m_components.OpenComponentIndex;
                    m_mesh.PrepareTraversal(triangle, null, m_processTrianglePredicate);

                    var primitiveEnum = m_mesh.GetEnumerator();
                    while (primitiveEnum.MoveNext())
                    {
                        ;
                    }
                    primitiveEnum.Dispose();

                    m_components.CloseComponent();

                    timeEnd = m_mesh.GetCurrentTimestamp();
                    if (m_currentComponentRel == 0)
                    {
                        timeBegin = timeEnd;
                    }
                    m_currentComponentRel++;
                }

                m_tmpCellTriangles.Clear();

                MyNavmeshComponents.ClosedCellInfo cellInfo = new MyNavmeshComponents.ClosedCellInfo();
                m_components.CloseAndCacheCell(ref cellInfo);

                // Add new component primitives
                if (cellInfo.NewCell)
                {
                    for (int i = 0; i < cellInfo.ComponentNum; ++i)
                    {
                        m_mesh.HighLevelGroup.AddPrimitive(cellInfo.StartingIndex + i, m_components.GetComponentCenter(i));
                    }
                }

                // Connect new components with the others in the neighboring cells
                for (int i = 0; i < cellInfo.ComponentNum; ++i)
                {
                    foreach (var otherComponent in m_currentCellConnections[i])
                    {
                        m_mesh.HighLevelGroup.ConnectPrimitives(cellInfo.StartingIndex + i, otherComponent);
                    }
                    m_currentCellConnections[i].Clear();
                }

                // Set all the components as expanded
                for (int i = 0; i < cellInfo.ComponentNum; ++i)
                {
                    int componentIndex = cellInfo.StartingIndex + i;
                    var component      = m_mesh.HighLevelGroup.GetPrimitive(componentIndex);
                    if (component != null)
                    {
                        component.IsExpanded = true;
                    }
                }
            }

            m_changedCells.Clear();

            m_currentHelper = null;

            ProfilerShort.End();
        }
        private bool AddCell(Vector3I cellPos)
        {
            MyCellCoord coord = new MyCellCoord(NAVMESH_LOD, cellPos);

            var geometry = m_voxelMap.Storage.Geometry;

            MyVoxelGeometry.CellData data = geometry.GetCell(ref coord);
            if (data == null)
            {
                m_processedCells.Add(ref cellPos);
                m_higherLevelHelper.AddExplored(ref cellPos);
                return(false);
            }

            ulong packedCoord = coord.PackId64();

            List <DebugDrawEdge> debugEdgesList = new List <DebugDrawEdge>();

            m_debugCellEdges[packedCoord] = debugEdgesList;

            MyVoxelPathfinding.CellId cellId = new MyVoxelPathfinding.CellId()
            {
                VoxelMap = m_voxelMap, Pos = cellPos
            };

            MyTrace.Send(TraceWindow.Ai, "Adding cell " + cellPos);

            m_connectionHelper.ClearCell();
            m_vertexMapping.Init(data.VoxelVerticesCount);

            // Prepare list of possibly intersecting cube grids for voxel-grid navmesh intersection testing
            Vector3D     bbMin  = m_voxelMap.PositionLeftBottomCorner + (m_cellSize * (new Vector3D(-0.125) + cellPos));
            Vector3D     bbMax  = m_voxelMap.PositionLeftBottomCorner + (m_cellSize * (Vector3D.One + cellPos));
            BoundingBoxD cellBB = new BoundingBoxD(bbMin, bbMax);

            m_tmpGridList.Clear();
            m_navmeshCoordinator.PrepareVoxelTriangleTests(cellBB, m_tmpGridList);

            Vector3D voxelMapCenter     = m_voxelMap.PositionComp.GetPosition();
            Vector3  centerDisplacement = voxelMapCenter - m_voxelMap.PositionLeftBottomCorner;

            // This is needed for correct edge classification - to tell, whether the edges are inner or outer edges of the cell
            ProfilerShort.Begin("Triangle preprocessing");
            for (int i = 0; i < data.VoxelTrianglesCount; i++)
            {
                short a = data.VoxelTriangles[i].VertexIndex0;
                short b = data.VoxelTriangles[i].VertexIndex1;
                short c = data.VoxelTriangles[i].VertexIndex2;

                Vector3 aPos, bPos, cPos;
                Vector3 vert;
                data.GetUnpackedPosition(a, out vert);
                aPos = vert - centerDisplacement;
                data.GetUnpackedPosition(b, out vert);
                bPos = vert - centerDisplacement;
                data.GetUnpackedPosition(c, out vert);
                cPos = vert - centerDisplacement;

                bool invalidTriangle = false;
                if ((bPos - aPos).LengthSquared() <= MyVoxelConnectionHelper.OUTER_EDGE_EPSILON_SQ)
                {
                    m_vertexMapping.Union(a, b);
                    invalidTriangle = true;
                }
                if ((cPos - aPos).LengthSquared() <= MyVoxelConnectionHelper.OUTER_EDGE_EPSILON_SQ)
                {
                    m_vertexMapping.Union(a, c);
                    invalidTriangle = true;
                }
                if ((cPos - bPos).LengthSquared() <= MyVoxelConnectionHelper.OUTER_EDGE_EPSILON_SQ)
                {
                    m_vertexMapping.Union(b, c);
                    invalidTriangle = true;
                }

                if (invalidTriangle)
                {
                    continue;
                }

                m_connectionHelper.PreprocessInnerEdge(a, b);
                m_connectionHelper.PreprocessInnerEdge(b, c);
                m_connectionHelper.PreprocessInnerEdge(c, a);
            }
            ProfilerShort.End();

            ProfilerShort.Begin("Free face sorting");
            // Ensure that the faces have increasing index numbers
            Mesh.SortFreeFaces();
            ProfilerShort.End();

            m_higherLevelHelper.OpenNewCell(coord);

            ProfilerShort.Begin("Adding triangles");
            for (int i = 0; i < data.VoxelTrianglesCount; i++)
            {
                short a    = data.VoxelTriangles[i].VertexIndex0;
                short b    = data.VoxelTriangles[i].VertexIndex1;
                short c    = data.VoxelTriangles[i].VertexIndex2;
                short setA = (short)m_vertexMapping.Find(a);
                short setB = (short)m_vertexMapping.Find(b);
                short setC = (short)m_vertexMapping.Find(c);

                if (setA == setB || setB == setC || setA == setC)
                {
                    continue;
                }

                Vector3 aPos, bPos, cPos;
                Vector3 vert;
                data.GetUnpackedPosition(setA, out vert);
                aPos = vert - centerDisplacement;
                data.GetUnpackedPosition(setB, out vert);
                bPos = vert - centerDisplacement;
                data.GetUnpackedPosition(setC, out vert);
                cPos = vert - centerDisplacement;

                if (MyFakes.NAVMESH_PRESUMES_DOWNWARD_GRAVITY)
                {
                    Vector3 normal = (cPos - aPos).Cross(bPos - aPos);
                    normal.Normalize();
                    if (normal.Dot(ref Vector3.Up) <= Math.Cos(MathHelper.ToRadians(54.0f)))
                    {
                        continue;
                    }
                }

                Vector3D aTformed = aPos + voxelMapCenter;
                Vector3D bTformed = bPos + voxelMapCenter;
                Vector3D cTformed = cPos + voxelMapCenter;

                bool intersecting = false;
                m_tmpLinkCandidates.Clear();
                m_navmeshCoordinator.TestVoxelNavmeshTriangle(ref aTformed, ref bTformed, ref cTformed, m_tmpGridList, m_tmpLinkCandidates, out intersecting);
                if (intersecting)
                {
                    m_tmpLinkCandidates.Clear();
                    continue;
                }

                if (!m_connectionHelper.IsInnerEdge(a, b))
                {
                    debugEdgesList.Add(new DebugDrawEdge(aTformed, bTformed));
                }
                if (!m_connectionHelper.IsInnerEdge(b, c))
                {
                    debugEdgesList.Add(new DebugDrawEdge(bTformed, cTformed));
                }
                if (!m_connectionHelper.IsInnerEdge(c, a))
                {
                    debugEdgesList.Add(new DebugDrawEdge(cTformed, aTformed));
                }

                int edgeAB   = m_connectionHelper.TryGetAndRemoveEdgeIndex(b, a, ref bPos, ref aPos);
                int edgeBC   = m_connectionHelper.TryGetAndRemoveEdgeIndex(c, b, ref cPos, ref bPos);
                int edgeCA   = m_connectionHelper.TryGetAndRemoveEdgeIndex(a, c, ref aPos, ref cPos);
                int formerAB = edgeAB;
                int formerBC = edgeBC;
                int formerCA = edgeCA;

                ProfilerShort.Begin("AddTriangle");
                var tri = AddTriangle(ref aPos, ref bPos, ref cPos, ref edgeAB, ref edgeBC, ref edgeCA);
                ProfilerShort.End();

                CheckMeshConsistency();

                m_higherLevelHelper.AddTriangle(tri.Index);

                if (formerAB == -1)
                {
                    m_connectionHelper.AddEdgeIndex(a, b, ref aPos, ref bPos, edgeAB);
                }
                if (formerBC == -1)
                {
                    m_connectionHelper.AddEdgeIndex(b, c, ref bPos, ref cPos, edgeBC);
                }
                if (formerCA == -1)
                {
                    m_connectionHelper.AddEdgeIndex(c, a, ref cPos, ref aPos, edgeCA);
                }

                // TODO: Instead of this, just add the tri into a list of tris that want to connect with the link candidates
                //m_navmeshCoordinator.TryAddVoxelNavmeshLinks(tri, cellId, m_tmpLinkCandidates);
                foreach (var candidate in m_tmpLinkCandidates)
                {
                    List <MyNavigationPrimitive> primitives = null;
                    if (!m_tmpCubeLinkCandidates.TryGetValue(candidate, out primitives))
                    {
                        primitives = m_primitiveListPool.Allocate();
                        m_tmpCubeLinkCandidates.Add(candidate, primitives);
                    }

                    primitives.Add(tri);
                }
                m_tmpLinkCandidates.Clear();
            }
            ProfilerShort.End();

            m_tmpGridList.Clear();
            m_connectionHelper.ClearCell();
            m_vertexMapping.Clear();

            Debug.Assert(!m_processedCells.Contains(ref cellPos));
            m_processedCells.Add(ref cellPos);
            m_higherLevelHelper.AddExplored(ref cellPos);

            // Find connected components in the current cell's subgraph of the navigation mesh
            m_higherLevelHelper.ProcessCellComponents();
            m_higherLevelHelper.CloseCell();

            // Create navmesh links using the navmesh coordinator, taking into consideration the high level components
            m_navmeshCoordinator.TryAddVoxelNavmeshLinks2(cellId, m_tmpCubeLinkCandidates);
            m_navmeshCoordinator.UpdateVoxelNavmeshCellHighLevelLinks(cellId);

            foreach (var candidate in m_tmpCubeLinkCandidates)
            {
                candidate.Value.Clear();
                m_primitiveListPool.Deallocate(candidate.Value);
            }
            m_tmpCubeLinkCandidates.Clear();

            return(true);
        }
Beispiel #30
0
        public void ProcessChangedCellComponents()
        {
            ProfilerShort.Begin("ProcessChangedCellComponents");

            m_currentHelper = this;

            Vector3I   min, max, pos;
            List <int> triangles = null;

            foreach (var cell in m_changedCells)
            {
                min = CellToLowestCube(cell);
                max = min + m_cellSize - Vector3I.One;

                // Save a hashset of all the triangles in the current cell
                pos = min;
                for (var it = new Vector3I_RangeIterator(ref min, ref max); it.IsValid(); it.GetNext(out pos))
                {
                    if (!m_triangleRegistry.TryGetValue(pos, out triangles))
                    {
                        continue;
                    }

                    foreach (var triIndex in triangles)
                    {
                        m_tmpCellTriangles.Add(triIndex);
                    }
                }

                if (m_tmpCellTriangles.Count == 0)
                {
                    continue;
                }

                MyCellCoord cellCoord  = new MyCellCoord(0, cell);
                ulong       packedCell = cellCoord.PackId64();
                m_components.OpenCell(packedCell);

                long timeBegin = m_mesh.GetCurrentTimestamp() + 1;
                long timeEnd   = timeBegin;
                m_currentComponentRel = 0;

                m_tmpComponentTriangles.Clear();
                foreach (var triIndex in m_tmpCellTriangles)
                {
                    // Skip already visited triangles
                    var triangle = m_mesh.GetTriangle(triIndex);
                    if (m_currentComponentRel != 0 && m_mesh.VisitedBetween(triangle, timeBegin, timeEnd))
                    {
                        continue;
                    }

                    m_components.OpenComponent();

                    // Make sure we have place in m_currentCellConnections
                    if (m_currentComponentRel >= m_currentCellConnections.Count)
                    {
                        m_currentCellConnections.Add(new List <int>());
                    }

                    // Find connected component from an unvisited triangle and mark its connections
                    m_components.AddComponentTriangle(triangle, triangle.Center);
                    triangle.ComponentIndex = m_currentComponentRel;
                    m_tmpComponentTriangles.Add(triangle);
                    m_mesh.PrepareTraversal(triangle, null, m_processTrianglePredicate);
                    m_mesh.PerformTraversal();
                    m_tmpComponentTriangles.Add(null);

                    m_components.CloseComponent();

                    timeEnd = m_mesh.GetCurrentTimestamp();
                    if (m_currentComponentRel == 0)
                    {
                        timeBegin = timeEnd;
                    }
                    m_currentComponentRel++;
                }

                m_tmpCellTriangles.Clear();

                MyNavmeshComponents.ClosedCellInfo cellInfo = new MyNavmeshComponents.ClosedCellInfo();
                m_components.CloseAndCacheCell(ref cellInfo);

                // Renumber triangles from the old indices to the newly assigned index from m_components
                int componentIndex = cellInfo.StartingIndex;
                foreach (var triangle in m_tmpComponentTriangles)
                {
                    if (triangle == null)
                    {
                        componentIndex++;
                        continue;
                    }
                    triangle.ComponentIndex = componentIndex;
                }
                m_tmpComponentTriangles.Clear();

                // Remove old component primitives
                if (!cellInfo.NewCell && cellInfo.ComponentNum != cellInfo.OldComponentNum)
                {
                    for (int i = 0; i < cellInfo.OldComponentNum; ++i)
                    {
                        m_mesh.HighLevelGroup.RemovePrimitive(cellInfo.OldStartingIndex + i);
                    }
                }

                // Add new component primitives
                if (cellInfo.NewCell || cellInfo.ComponentNum != cellInfo.OldComponentNum)
                {
                    for (int i = 0; i < cellInfo.ComponentNum; ++i)
                    {
                        m_mesh.HighLevelGroup.AddPrimitive(cellInfo.StartingIndex + i, m_components.GetComponentCenter(i));
                    }
                }

                // Update existing component primitives
                if (!cellInfo.NewCell && cellInfo.ComponentNum == cellInfo.OldComponentNum)
                {
                    for (int i = 0; i < cellInfo.ComponentNum; ++i)
                    {
                        var primitive = m_mesh.HighLevelGroup.GetPrimitive(cellInfo.StartingIndex + i);
                        primitive.UpdatePosition(m_components.GetComponentCenter(i));
                    }
                }

                // Connect new components with the others in the neighboring cells
                for (int i = 0; i < cellInfo.ComponentNum; ++i)
                {
                    int compIndex = cellInfo.StartingIndex + i;

                    var primitive = m_mesh.HighLevelGroup.GetPrimitive(compIndex);
                    primitive.GetNeighbours(m_tmpNeighbors);

                    // Connect to disconnected components
                    foreach (var connection in m_currentCellConnections[i])
                    {
                        if (!m_tmpNeighbors.Remove(connection))
                        {
                            m_mesh.HighLevelGroup.ConnectPrimitives(compIndex, connection);
                        }
                    }

                    // Disconnect neighbors that should be no longer connected
                    foreach (var neighbor in m_tmpNeighbors)
                    {
                        // Only disconnect from the other cell if it is expanded and there was no connection found
                        var neighborPrimitive = m_mesh.HighLevelGroup.TryGetPrimitive(neighbor);
                        if (neighborPrimitive != null && neighborPrimitive.IsExpanded)
                        {
                            m_mesh.HighLevelGroup.DisconnectPrimitives(compIndex, neighbor);
                        }
                    }

                    m_tmpNeighbors.Clear();
                    m_currentCellConnections[i].Clear();
                }

                // Set all the components as expanded
                for (int i = 0; i < cellInfo.ComponentNum; ++i)
                {
                    componentIndex = cellInfo.StartingIndex + i;
                    var component = m_mesh.HighLevelGroup.GetPrimitive(componentIndex);
                    if (component != null)
                    {
                        component.IsExpanded = true;
                    }
                }
            }

            m_changedCells.Clear();

            m_currentHelper = null;

            ProfilerShort.End();
        }