Example #1
0
 public bool Equals(MapCoordinates other)
 {
     return(Row.Equals(other.Row) && Column.Equals(other.Column));
 }
Example #2
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>   Computes the FOV. </summary>
        ///
        /// <remarks> Darrell, 8/28/2016. </remarks>
        ///
        /// <param name="octant">       The octant to be scanned. </param>
        /// <param name="origin">       The spot from which the FOV is computed. </param>
        /// <param name="rangeLimit">   The range limit. </param>
        /// <param name="x">            The x coordinate. </param>
        /// <param name="top">          The top slope. </param>
        /// <param name="bottom">       The bottom slope. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        private void Compute(int octant, MapCoordinates origin, int rangeLimit, int x, Slope top, Slope bottom)
        {
            // throughout this function there are references to various parts of tiles. a tile's coordinates refer to its
            // center, and the following diagram shows the parts of the tile and the vectors from the origin that pass through
            // those parts. given a part of a tile with vector u, a vector v passes above it if v > u and below it if v < u
            //    g         center:        y / x
            // a------b   a top left:      (y*2+1) / (x*2-1)   i inner top left:      (y*4+1) / (x*4-1)
            // |  /\  |   b top right:     (y*2+1) / (x*2+1)   j inner top right:     (y*4+1) / (x*4+1)
            // |i/__\j|   c bottom left:   (y*2-1) / (x*2-1)   k inner bottom left:   (y*4-1) / (x*4-1)
            //e|/|  |\|f  d bottom right:  (y*2-1) / (x*2+1)   m inner bottom right:  (y*4-1) / (x*4+1)
            // |\|__|/|   e middle left:   (y*2) / (x*2-1)
            // |k\  /m|   f middle right:  (y*2) / (x*2+1)     a-d are the corners of the tile
            // |  \/  |   g top center:    (y*2+1) / (x*2)     e-h are the corners of the inner (wall) diamond
            // c------d   h bottom center: (y*2-1) / (x*2)     i-m are the corners of the inner square (1/2 tile width)
            //    h
            for (; x <= rangeLimit; x++)
            {
                // compute the Y coordinates of the top and bottom of the sector. we maintain that top > bottom
                int topY;

                // if top == ?/1 then it must be 1/1 because 0/1 < top <= 1/1. this is special-cased because top
                //  starts at 1/1 and remains 1/1 as long as it doesn't hit anything, so it's a common case
                if (top.X == 1)
                {
                    topY = x;
                }
                else // top < 1
                {
                    // Get the tile that the top vector enters from the left. Since our coordinates refer to the center of the
                    // tile, this is (x-0.5)*top+0.5, which can be computed as (x-0.5)*top+0.5 = (2(x+0.5)*top+1)/2 =
                    // ((2x+1)*top+1)/2. Since top == a/b, this is ((2x+1)*a+b)/2b. If it enters a tile at one of the left
                    // corners, it will round up, so it'll enter from the bottom-left and never the top-left
                    //
                    // The Y coordinate of the tile entered from the left
                    // Now it's possible that the vector passes from the left side of the tile up into the tile above before
                    // exiting from the right side of this column so we may need to increment topY
                    topY = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2);

                    // If the tile blocks light (i.e. is a wall)...
                    if (BlocksLight(x, topY, octant, origin))
                    {
                        // If the tile entered from the left blocks light, whether it passes into the tile above depends on the shape
                        // of the wall tile as well as the angle of the vector. If the tile has does not have a beveled top-left
                        // corner, then it is blocked. The corner is beveled if the tiles above and to the left are not walls. We can
                        // ignore the tile to the left because if it was a wall tile, the top vector must have entered this tile from
                        // the bottom-left corner, in which case it can't possibly enter the tile above.
                        //
                        // Otherwise, with a beveled top-left corner, the slope of the vector must be greater than or equal to the
                        // slope of the vector to the top center of the tile (x*2, topY*2+1) in order for it to miss the wall and
                        // pass into the tile above
                        if (top.GreaterOrEqual(topY * 2 + 1, x * 2) && !BlocksLight(x, topY + 1, octant, origin))
                        {
                            topY++;
                        }
                    }
                    else
                    {
                        // Since this tile doesn't block light, there's nothing to stop it from passing into the tile above, and it
                        // does so if the vector is greater than the vector for the bottom-right corner of the tile above. However,
                        // there is one additional consideration - later code in this method assumes that if a tile blocks light then
                        // it must be visible, so if the tile above blocks light we have to make sure the light actually impacts the
                        // wall shape. There are three cases:
                        //
                        //      1) the tile above is clear, in which case the vector must be above the bottom-right corner of the tile above
                        //      2) the tile above blocks light and does not have a beveled bottom-right corner, in which case the
                        //         vector must be above the bottom-right corner
                        //      3) the tile above blocks light and does have a beveled bottom-right corner, in which case
                        //         the vector must be above the bottom center of the tile above (i.e. the corner of the beveled edge).
                        //
                        // Now it's possible to merge 1 and 2 into a single check, and we get the following: if the tile above and to
                        // the right is a wall, then the vector must be above the bottom-right corner. Otherwise, the vector must be
                        // above the bottom center. this works because if the tile above and to the right is a wall, then there are
                        // two cases:
                        //
                        //      1) the tile above is also a wall, in which case we must check against the bottom-right corner,
                        //      2) the tile above is not a wall, in which case the vector passes into it if it's above the bottom-right corner.
                        //
                        // So either way we use the bottom-right corner in that case. Now, if the tile above and to the right
                        // is not a wall, then we again have two cases:
                        //
                        //      1) the tile above is a wall with a beveled edge, in which case we must check against the bottom center
                        //      2) the tile above is not a wall, in which case it will only be visible if light passes through the inner
                        //         square, and the inner square is guaranteed to be no larger than a wall diamond, so if it wouldn't pass
                        //         through a wall diamond then it can't be visible, so there's no point in incrementing topY even if light
                        //         passes through the corner of the tile above. so we might as well use the bottom center for both cases.

                        // center
                        var ax = x * 2;

                        // use bottom-right if the tile above and right is a wall
                        if (BlocksLight(x + 1, topY + 1, octant, origin))
                        {
                            ax++;
                        }
                        if (top.Greater(topY * 2 + 1, ax))
                        {
                            topY++;
                        }
                    }
                }

                // if bottom == 0/?, then it's hitting the tile at Y=0 dead center. this is special-cased because
                // bottom.Y starts at zero and remains zero as long as it doesn't hit anything, so it's common
                int bottomY;
                if (bottom.Y == 0)
                {
                    bottomY = 0;
                }
                else
                {
                    // The tile that the bottom vector enters from the left
                    // code below assumes that if a tile is a wall then it's visible, so if the tile contains a wall we have to
                    // ensure that the bottom vector actually hits the wall shape. It misses the wall shape if the top-left corner
                    // is beveled and bottom >= (bottomY*2+1)/(x*2). Finally, the top-left corner is beveled if the tiles to the
                    // left and above are clear. we can assume the tile to the left is clear because otherwise the bottom vector
                    // would be greater, so we only have to check above
                    bottomY = ((x * 2 - 1) * bottom.Y + bottom.X) / (bottom.X * 2);
                    if (bottom.GreaterOrEqual(bottomY * 2 + 1, x * 2) && BlocksLight(x, bottomY, octant, origin) &&
                        !BlocksLight(x, bottomY + 1, octant, origin))
                    {
                        bottomY++;
                    }
                }

                // Go through the tiles in the column now that we know which ones could possibly be visible
                var wasOpaque    = -1;                                         // 0:false, 1:true, -1:not applicable
                var rangeLimitSq = rangeLimit * rangeLimit;
                for (var y = topY; y >= bottomY; y--)                          // use a signed comparison because y can wrap around when decremented
                {
                    if (rangeLimit < 0 || GetDistanceSq(x, y) <= rangeLimitSq) // skip the tile if it's out of visual range
                    {
                        var isOpaque = BlocksLight(x, y, octant, origin);

                        // every tile where topY > y > bottomY is guaranteed to be visible. also, the code that initializes topY and
                        // bottomY guarantees that if the tile is opaque then it's visible. so we only have to do extra work for the
                        // case where the tile is clear and y == topY or y == bottomY. if y == topY then we have to make sure that
                        // the top vector is above the bottom-right corner of the inner square. if y == bottomY then we have to make
                        // sure that the bottom vector is below the top-left corner of the inner square
                        var isVisible =
                            isOpaque || ((y != topY || top.Greater(y * 4 - 1, x * 4 + 1)) && (y != bottomY || bottom.Less(y * 4 + 1, x * 4 - 1)));

                        // NOTE: if you want the algorithm to be either fully or mostly symmetrical, replace the line above with the
                        // following line (and uncomment the Slope.LessOrEqual method). the line ensures that a clear tile is visible
                        // only if there's an unobstructed line to its center. if you want it to be fully symmetrical, also remove
                        // the "isOpaque ||" part and see NOTE comments further down
                        // bool isVisible = isOpaque || ((y != topY || top.GreaterOrEqual(y, x)) && (y != bottomY || bottom.LessOrEqual(y, x)));
                        if (isVisible)
                        {
                            SetVisible(x, y, octant, origin);
                        }

                        // if we found a transition from clear to opaque or vice versa, adjust the top and bottom vectors
                        // but don't bother adjusting them if this is the last column anyway
                        if (x != rangeLimit)
                        {
                            if (isOpaque)
                            {
                                // If we found a transition from clear to opaque, this sector is done in this column,
                                // so adjust the bottom vector upward and continue processing it in the next column.
                                // If the opaque tile has a beveled top-left corner, move the bottom vector up to the top center.
                                // Otherwise, move it up to the top left. The corner is beveled if the tiles above and to the left are
                                // clear. we can assume the tile to the left is clear because otherwise the vector would be higher, so
                                // we only have to check the tile above.
                                if (wasOpaque == 0)
                                {
                                    // top center by default
                                    int nx = x * 2, ny = y * 2 + 1;

                                    // NOTE: If you're using full symmetry and want more expansive walls (recommended), comment out the next if statement
                                    if (BlocksLight(x, y + 1, octant, origin))
                                    {
                                        // top left if the corner is not beveled
                                        nx--;
                                    }

                                    // We have to maintain the invariant that top > bottom, so the new sector
                                    // created by adjusting the bottom is only valid if that's the case
                                    // if we're at the bottom of the column, then just adjust the current sector rather than recursing
                                    // since there's no chance that this sector can be split in two by a later transition back to clear
                                    if (top.Greater(ny, nx))
                                    {
                                        // don't recurse unless necessary
                                        if (y == bottomY)
                                        {
                                            bottom = new Slope(ny, nx);
                                            break;
                                        }
                                        Compute(octant, origin, rangeLimit, x + 1, top, new Slope(ny, nx));
                                    }
                                    // the new bottom is greater than or equal to the top, so the new sector is empty and we'll ignore
                                    // it. if we're at the bottom of the column, we'd normally adjust the current sector rather than
                                    else
                                    {
                                        // recursing, so that invalidates the current sector and we're done
                                        if (y == bottomY)
                                        {
                                            return;
                                        }
                                    }
                                }
                                wasOpaque = 1;
                            }
                            else
                            {
                                // If we found a transition from opaque to clear, adjust the top vector downwards
                                if (wasOpaque > 0)
                                {
                                    // If the opaque tile has a beveled bottom-right corner, move the top vector down to the bottom center -
                                    // otherwise, move it down to the bottom right. The corner is beveled if the tiles below and to the right
                                    // are clear. We know the tile below is clear because that's the current tile, so just check to the right

                                    // The bottom of the opaque tile (oy*2-1) equals the top of this tile (y*2+1)
                                    int nx = x * 2, ny = y * 2 + 1;

                                    // NOTE: if you're using full symmetry and want more expansive walls (recommended), comment out the next line

                                    // Check the right of the opaque tile (y+1), not this one
                                    if (BlocksLight(x + 1, y + 1, octant, origin))
                                    {
                                        nx++;
                                    }
                                    // We have to maintain the invariant that top > bottom. if not, the sector is empty and we're done
                                    if (bottom.GreaterOrEqual(ny, nx))
                                    {
                                        return;
                                    }
                                    top = new Slope(ny, nx);
                                }
                                wasOpaque = 0;
                            }
                        }
                    }
                }

                // if the column didn't end in a clear tile, then there's no reason to continue processing the current sector
                // because that means either 1) wasOpaque == -1, implying that the sector is empty or at its range limit, or 2)
                // wasOpaque == 1, implying that we found a transition from clear to opaque and we recursed and we never found
                // a transition back to clear, so there's nothing else for us to do that the recursive method hasn't already. (if
                // we didn't recurse (because y == bottomY), it would have executed a break, leaving wasOpaque equal to 0.)
                if (wasOpaque != 0)
                {
                    break;
                }
            }
        }
Example #3
0
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 /// <summary>	Constructor from a 2D character layout. </summary>
 ///
 /// <remarks>	Darrellp, 9/27/2011. </remarks>
 ///
 /// <param name="layout">	The layout. </param>
 /// <param name="location">	The location. </param>
 /// <param name="exits">	The exits. </param>
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 internal GenericRoom(char[][] layout, MapCoordinates location, List <GenericRoom> exits)
 {
     Setup(layout, location, exits);
 }
Example #4
0
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 /// <summary>	Return true if a location is part of this room's floor. </summary>
 ///
 /// <remarks>	Darrellp, 9/29/2011. </remarks>
 ///
 /// <param name="location">	The location in global coordinates to be tested. </param>
 ///
 /// <returns>	true if location is part of our room, false if not. </returns>
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 public bool ContainsPosition(MapCoordinates location)
 {
     return(location.Column >= Left && location.Column <= Right &&
            location.Row >= Top && location.Row <= Bottom &&
            this[location] == '.');
 }
Example #5
0
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 /// <summary>	Sets a room at a grid location. </summary>
 ///
 /// <remarks>	Darrellp, 9/23/2011. </remarks>
 ///
 /// <param name="gridLocation">	The grid location. </param>
 /// <param name="room">			The room to be placed there.. </param>
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 private void SetRoomAt(MapCoordinates gridLocation, RectangularRoom room)
 {
     _rooms[gridLocation.Column][gridLocation.Row] = room;
 }
Example #6
0
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 /// <summary>	Retrieve the room at a grid location. </summary>
 ///
 /// <remarks>	Darrellp, 9/23/2011. </remarks>
 ///
 /// <param name="gridLocation">	The grid location. </param>
 ///
 /// <returns>	The room occupying that grid location. </returns>
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 private RectangularRoom RoomAt(MapCoordinates gridLocation)
 {
     return(!WithinGrid(gridLocation) ? null : _rooms[gridLocation.Column][gridLocation.Row]);
 }
Example #7
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>	Locates the rooms on the map. </summary>
        ///
        /// <remarks>	Darrellp, 9/19/2011. </remarks>
        ///
        /// <param name="map">	The map to be excavated. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        private void LocateRooms(IRoomsMap map)
        {
            // Max number of cells we can fit with at least base cell size
            int gridWidth  = map.Width / _baseCellWidth;
            int gridHeight = map.Height / _baseCellHeight;

            // Size of cells we can manage total
            int baseRoomWidth  = map.Width / gridWidth;
            int baseRoomHeight = map.Height / gridHeight;

            // Remainders for Bresenham algorithm
            int widthRemainder  = map.Width % baseRoomWidth;
            int heightRemainder = map.Height % baseRoomHeight;

            // Tally for Bresenham
            int widthTally  = gridWidth / 2;
            int heightTally = gridHeight / 2;

            // First column is on the left
            int mapColumn = 0;

            // Array of rooms in the grid
            _rooms = new RectangularRoom[gridWidth][];

            // For each grid column
            for (int gridColumn = 0; gridColumn < gridWidth; gridColumn++)
            {
                // Reset the map row to 0
                int mapRow = 0;

                // Determine the current map column
                int currentWidth = baseRoomWidth;
                widthTally += widthRemainder;

                // Do we need to bump width ala Bresenham?
                if (widthTally >= gridWidth)
                {
                    // Bump width
                    currentWidth++;
                    widthTally -= gridWidth;
                }

                // Create the row of rooms for this column
                _rooms[gridColumn] = new RectangularRoom[gridHeight];

                // For each row of the grid
                for (int gridRow = 0; gridRow < gridHeight; gridRow++)
                {
                    // Determine the current map row
                    int currentHeight = baseRoomHeight;
                    heightTally += heightRemainder;

                    // Do we need to bump height ala Bresenham?
                    if (heightTally >= gridHeight)
                    {
                        // Bump height
                        currentHeight++;
                        heightTally -= gridHeight;
                    }

                    // Create a room in the grid cell
                    MapCoordinates  gridLocation = new MapCoordinates(gridColumn, gridRow);
                    MapCoordinates  cellLocation = new MapCoordinates(mapColumn, mapRow);
                    RectangularRoom room         = CreateRoomInCell(gridLocation, cellLocation, currentWidth, currentHeight);

                    // Place it in the grid
                    _rooms[gridColumn][gridRow] = room;

                    // Advance the map row
                    mapRow += currentHeight;
                }

                // Advance the map column
                mapColumn += currentWidth;
            }
        }
Example #8
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>	Merge two rooms. </summary>
        ///
        /// <remarks>	Named as though dir was vertical.  Darrellp, 9/22/2011. </remarks>
        ///
        /// <param name="topRoomsGridLocation">	The location of the top room in the grid. </param>
        /// <param name="dir">					The direction of the merge. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        private void MergeTwoRooms(MapCoordinates topRoomsGridLocation, Dir dir)
        {
            // Get grid coordinates of the other room
            MapCoordinates bottomRoomsGridLocation = topRoomsGridLocation.NextLarger(dir);
            Dir            otherDir = MapCoordinates.OtherDirection(dir);

            // Retrieve both rooms
            RectangularRoom topRoom    = RoomAt(topRoomsGridLocation);
            RectangularRoom bottomRoom = RoomAt(bottomRoomsGridLocation);

            // Are the rooms unmergable?
            if (!CheckMergeForOverlap(topRoom, bottomRoom, otherDir))
            {
                // Return and don't merge them - use normal corridors
                return;
            }

            // Remove their generic counterparts
            _mapRoomToGenericRooms.Remove(topRoom);
            _mapRoomToGenericRooms.Remove(bottomRoom);

            // Get their current coordinates, etc.
            int topRoomsBottom    = topRoom.LargeCoord(dir);
            int topRoomsTop       = topRoom.SmallCoord(dir);
            int bottomRoomsTop    = bottomRoom.SmallCoord(dir);
            int bottomRoomsHeight = bottomRoom.Size(dir);
            int topRoomsWidth     = topRoom.Size(otherDir);
            int bottomRoomsWidth  = bottomRoom.Size(otherDir);

            // Pick a random spot between the rooms to merge them
            // This will be the new inside coord of the small coordinate room
            int mergeRow = _rnd.Next(topRoomsBottom, bottomRoomsTop);

            // Determine all the new coordinates
            int            topRoomsNewHeight    = mergeRow - topRoomsTop + 1;
            int            bottomRoomsNewHeight = bottomRoomsTop - mergeRow + bottomRoomsHeight - 1;
            MapCoordinates bottomRoomsLocation  = bottomRoom.Location;

            bottomRoomsLocation[dir] = mergeRow + 1;

            // Create our new expanded rooms
            RectangularRoom roomTopNew = RectangularRoom.CreateUndirectional(
                topRoom.Location,
                topRoomsNewHeight,
                topRoomsWidth,
                topRoomsGridLocation[dir],
                topRoomsGridLocation[otherDir],
                dir);
            RectangularRoom roomBottomNew = RectangularRoom.CreateUndirectional(
                bottomRoomsLocation,
                bottomRoomsNewHeight,
                bottomRoomsWidth,
                bottomRoomsGridLocation[dir],
                bottomRoomsGridLocation[otherDir],
                dir);

            // Install the new rooms
            SetRoomAt(topRoomsGridLocation, roomTopNew);
            SetRoomAt(bottomRoomsGridLocation, roomBottomNew);

            // Create the new generic rooms
            // We don't create the single merged generic room until we excavate because we don't know
            // the layout until then.
            _mapRoomToGenericRooms[roomTopNew]    = roomTopNew.ToGeneric();
            _mapRoomToGenericRooms[roomBottomNew] = roomBottomNew.ToGeneric();

            // Mark this in our merges structure
            _merges.Connect(topRoomsGridLocation, bottomRoomsGridLocation);
        }
Example #9
0
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        /// <summary>	Excavate a merge between two rooms. </summary>
        ///
        /// <remarks>
        /// Names are named as though dir was vertical and dirOther horizontal. Darrellp, 9/22/2011.
        /// </remarks>
        ///
        /// <param name="map">			The map. </param>
        /// <param name="topRoom">		The top room. </param>
        /// <param name="bottomRoom">	The bottom room. </param>
        /// <param name="dir">			The dir. </param>
        ////////////////////////////////////////////////////////////////////////////////////////////////////
        private void ExcavateMerge(IRoomsMap map, RectangularRoom topRoom, RectangularRoom bottomRoom, Dir dir)
        {
            // Get the opposite direction
            Dir dirOther = MapCoordinates.OtherDirection(dir);

            // Are the rooms unmergable?
            if (!CheckOverlap(topRoom, bottomRoom, dirOther))
            {
                // Should have caught this in MergeTwoRooms - throw exception
                throw new RogueException("Non-overlapping rooms made it to ExcavateMerge");
            }

            // Get the appropriate coordinates
            int topRoomsLeft     = topRoom.Location[dirOther];
            int topRoomsRight    = topRoomsLeft + topRoom.Size(dirOther) - 1;
            int bottomRoomsLeft  = bottomRoom.Location[dirOther];
            int bottomRoomsRight = bottomRoomsLeft + bottomRoom.Size(dirOther) - 1;

            // Get the high and low points of the overlap
            int overlapLeft  = Math.Max(topRoomsLeft, bottomRoomsLeft) + 1;
            int overlapRight = Math.Min(topRoomsRight, bottomRoomsRight) - 1;

            // Create our new merged generic room
            GenericRoom groomTop    = _mapRoomToGenericRooms[topRoom];
            GenericRoom groomBottom = _mapRoomToGenericRooms[bottomRoom];

            groomTop.CombineWith(groomBottom);

            // For each column in the grid
            foreach (RectangularRoom[] roomColumn in _rooms)
            {
                // For each row in the grid
                for (int iRow = 0; iRow < _rooms[0].Length; iRow++)
                {
                    // Get the rect room at that spot
                    RectangularRoom room = roomColumn[iRow];

                    // Is it mapped to our defunct bottom room?
                    if (_mapRoomToGenericRooms[room] == groomBottom)
                    {
                        // Map it to our shiny new top room
                        _mapRoomToGenericRooms[room] = groomTop;
                    }
                }
            }

            // Get the location we're going to start the clearing at
            int            topRoomsBottom  = topRoom.Location[dir] + topRoom.Size(dir) - 1;
            MapCoordinates currentLocation = MapCoordinates.CreateUndirectional(topRoomsBottom, overlapLeft, dir);
            char           floorChar       = TerrainFactory.TerrainToChar(TerrainType.Floor);

            // For each spot along the overlap
            for (int iCol = overlapLeft; iCol <= overlapRight; iCol++)
            {
                // Clear out the two walls of the abutting rooms
                currentLocation[dirOther]    = iCol;
                map[currentLocation].Terrain = TerrainType.Floor;
                groomTop[currentLocation]    = floorChar;
                currentLocation[dir]         = topRoomsBottom + 1;
                map[currentLocation].Terrain = TerrainType.Floor;
                groomTop[currentLocation]    = floorChar;
                currentLocation[dir]         = topRoomsBottom;
            }
            Debug.Assert(groomBottom == groomTop || !_mapRoomToGenericRooms.ContainsValue(groomBottom));
        }