public void registerLevel(LevelPoint level)
 {
     if (!levels.ContainsKey(level.levelIndex))
     {
         levels.Add(level.levelIndex, level);
     }
 }
 public Platform(Transform first, Transform second)
 {
     First       = first;
     Second      = second;
     FirstPoint  = new LevelPoint(first.name);
     SecondPoint = new LevelPoint(second.name);
 }
    private void GeneratePaths(char[,] worldData)
    {
        int count = Random.Range(MinPaths, MaxPaths);

        LevelPoint[] lastPath = RunPath(LevelWidth / 2, LevelHeigth / 2);
        if (LevelType == LevelType.Ambush)
        {
            StartPoint = lastPath[0];
        }
        applyPath(worldData, lastPath);

        for (int i = 1; i < count; i++)
        {
            LevelPoint start;
            if (BuildMode == LevelBuildMode.FollowAndContinue)
            {
                start = lastPath[Random.Range(0, lastPath.Length)];
            }
            else if (BuildMode == LevelBuildMode.RandomContinue)
            {
                start = FloorPoints[Random.Range(0, FloorPoints.Count)];
            }
            else
            {
                start.X = LevelWidth / 2;
                start.Y = LevelHeigth / 2;
            }
            LevelPoint[] path = RunPath(start.X, start.Y);
            applyPath(worldData, path);
            lastPath = path;
        }
    }
    private void OnCollisionEnter2D(Collision2D collision)
    {
        Transform playerTransform = PlayerController.instance.transform;

        Collider2D[] hitColliders = Physics2D.OverlapCircleAll(new Vector2(playerTransform.position.x, playerTransform.position.y + .35f), 1.3f, 1 << LayerMask.NameToLayer("Ground"));

        if (hitColliders.Length == 0)
        {
            return;
        }
        GameObject defaultGameObject = hitColliders[0].attachedRigidbody.gameObject;

        int i = 0;

        while (i < hitColliders.Length)
        {
            if (!GameObject.ReferenceEquals(defaultGameObject, hitColliders[i].attachedRigidbody.gameObject))
            {
                return;
            }
            i++;
        }
        LevelPoint nextRoom    = this;
        LevelPoint currentRoom = defaultGameObject.GetComponent <LevelPoint>();

        Debug.Log("all of circle in one room");

        if (!isLocked && nextRoom.levelIndex != currentRoom.levelIndex)
        {
            LevelManager.singleton.disableCollisionBetweenLevels(collision.otherCollider.name, currentRoom, nextRoom);
        }
        return;
    }
 public override void Compute(LevelPoint origin, int rangeLimit)
 {
     _setVisible(origin.X, origin.Y);
     for (uint octant = 0; octant < 8; octant++)
     {
         Compute(octant, origin, rangeLimit, 1, new Slope(1, 1), new Slope(0, 1));
     }
 }
Exemple #6
0
    public void Run(Vector2Int origin, int sightLimit)
    {
        var levelPoint = new LevelPoint {
            X = (uint)origin.x, Y = (uint)origin.y
        };

        map.ClearVisibility();
        fovComputer.Compute(levelPoint, sightLimit);
    }
    public override bool Equals(object obj)
    {
        if (!GetType().Equals(obj.GetType()))
        {
            return(false);
        }

        LevelPoint p = (LevelPoint)obj;

        return(p.X == X && p.Y == Y);
    }
Exemple #8
0
 public void SaveUnlockedLevel(LevelPoint level)
 {
     for (int i = 0; i < levelConScript.levels.Length; i++)
     {
         if (levelConScript.levels[i].ToString() == level.ToString())
         {
             PlayerPrefs.SetString("locked/unlocked" + levelConScript.levels[i].ToString(), "unlocked");
             PlayerPrefs.Save();
         }
     }
 }
Exemple #9
0
 public void MakeLevelNotInteractable(LevelPoint level)
 {
     if (level.GetComponent <Image> () != null)
     {
         level.GetComponent <Image> ().sprite = lockSprite;
     }
     else
     {
         level.GetComponent <SpriteRenderer>().sprite = lockSprite;
     }
 }
Exemple #10
0
 public void MakeLevelButtonInteractable(LevelPoint level)
 {
     if (level.GetComponent <Image> () != null)
     {
         level.GetComponent <Image> ().sprite = unlockSprite;
     }
     else
     {
         level.GetComponent <SpriteRenderer>().sprite = unlockSprite;
     }
     //level.GetComponent<Button>().interactable = true;
 }
    private void generateEndPoint()
    {
        while (true)
        {
            EndPoint = FourCorners[Random.Range(0, 4)];

            if (!EndPoint.Equals(StartPoint))
            {
                break;
            }
        }
    }
Exemple #12
0
    /// <summary>
    ///     Находит список тригеров соприкасающихся с точкой и возвращает их модели
    /// </summary>
    private IEnumerable <TriggerModel> GetTriggersModelFromPoint(LevelPoint point)
    {
        var triggers = new List <TriggerModel>();

        foreach (var triggerModel in TriggerModels)
        {
            if (triggerModel.Platform.Coincidences(point))
            {
                triggers.Add(triggerModel);
            }
        }

        return(triggers.Count > 0 ? triggers : null);
    }
 private void OnTriggerStay2D(Collider2D collision)
 {
     if (collision.name == "Player")
     {
         if (Input.GetKeyDown(KeyCode.Space))
         {
             LevelPoint level = gameObject.GetComponentInParent <LevelPoint>();
             if (level != null)
             {
                 GameManager.singelton.currentLevel = level.levelIndex;
                 LevelManager.singleton.loadLevelScene(level.levelIndex);
             }
         }
     }
 }
    public void disableCollisionBetweenLevels(string colliderName, LevelPoint currentRoom, LevelPoint otherRoom)
    {
        BoxCollider2D thisRoomCollider = null, otherRoomCollider = null;

        if (colliderName == "Bottom")
        {
            otherRoomCollider = otherRoom.bottom;
            thisRoomCollider  = currentRoom.top;
        }
        else if (colliderName == "Top")
        {
            otherRoomCollider = otherRoom.top;
            thisRoomCollider  = currentRoom.bottom;
        }
        else if (colliderName == "BottomRight")
        {
            otherRoomCollider = otherRoom.bottomRight;
            thisRoomCollider  = currentRoom.topLeft;
        }
        else if (colliderName == "BottomLeft")
        {
            otherRoomCollider = otherRoom.bottomLeft;
            thisRoomCollider  = currentRoom.topRight;
        }
        else if (colliderName == "TopLeft")
        {
            otherRoomCollider = otherRoom.topLeft;
            thisRoomCollider  = currentRoom.bottomRight;
        }
        else if (colliderName == "TopRight")
        {
            otherRoomCollider = otherRoom.topRight;
            thisRoomCollider  = currentRoom.bottomLeft;
        }
        else
        {
            Debug.Log("error Happen");
        }
        thisRoomCollider.enabled  = false;
        otherRoomCollider.enabled = false;
        Debug.Log("will disable " + otherRoomCollider.name + " and " + thisRoomCollider.name);
        return;
    }
    private void generateObjectivePoint()
    {
        int count = 0;

        while (true)
        {
            if (count > FloorPoints.Count * FloorPoints.Count)
            {
                MinObjectiveDistance--;
                count = 0;
            }

            ObjectivePoint = FloorPoints[Random.Range(0, FloorPoints.Count)];
            count++;
            if (Mathf.Abs(ObjectivePoint.X - StartPoint.X) + Mathf.Abs(ObjectivePoint.Y - StartPoint.Y) > MinObjectiveDistance)
            {
                break;
            }
        }
    }
    public void getClosesetLevel(int currentLevelIndex, Transform collisionTransform)
    {
        float      lowestDistance = 10000f;
        LevelPoint closesetLevel  = null;

        for (var x = 0; x < levels.Count; x++)
        {
            if (currentLevelIndex == levels[x].levelIndex)
            {
                continue;
            }
            float currDistance = Vector2.Distance(levels[x].transform.position, collisionTransform.position);
            if (currDistance < lowestDistance)
            {
                lowestDistance = currDistance;
                closesetLevel  = levels[x];
            }
        }

        //Debug.Log(closesetLevel.levelIndex + " Distance: " + lowestDistance);
    }
    private void changeDirection()
    {
        m_direction.X = 0;
        m_direction.Y = 0;

        int dir = Rand.Next(10000) % 4;

        switch (dir)
        {
        case 0:
            m_direction.X = -1;
            break;

        case 1:
            m_direction.Y = -1;
            break;

        case 2:
            m_direction.X = 1;
            break;

        case 3:
            m_direction.Y = 1;
            break;

        case 4:
            m_direction.X = 1;
            m_direction.Y = 1;
            break;
        }

        if (m_direction.X == m_lastDirection.Y && m_direction.Y == m_lastDirection.Y)
        {
            changeDirection();
        }
        else
        {
            m_lastDirection = m_direction;
        }
    }
    public LevelPoint[] RunPath()
    {
        if (m_points != null)
        {
            return(m_points);
        }

        changeDirection();
        int len = Rand.Next(m_minSize, m_maxSize);

        LevelPoint[] path = new LevelPoint[len];

        path[0].X = m_startX;
        path[0].Y = m_startY;

        for (int i = 1; i < len; i++)
        {
try_again:
            //should change direction
            if (Rand.Next(1000) % 3 == 0)
            {
                changeDirection();
            }


            path[i].X = path[i - 1].X + m_direction.X;
            path[i].Y = path[i - 1].Y + m_direction.Y;

            if (path[i].X >= m_levelWith || path[i].Y >= m_levelHeigth || path[i].X < 0 || path[i].Y < 0)
            {
                goto try_again;
            }
        }
        m_points = path;
        return(path);
    }
    // NOTE: the code duplication between BlocksLight and SetVisible is for performance. don't refactor the octant
    // translation out unless you don't mind an 18% drop in speed
    bool BlocksLight(uint x, uint y, uint octant, LevelPoint origin)
    {
        uint nx = origin.X, ny = origin.Y;

        switch (octant)
        {
        case 0: nx += x; ny -= y; break;

        case 1: nx += y; ny -= x; break;

        case 2: nx -= y; ny -= x; break;

        case 3: nx -= x; ny -= y; break;

        case 4: nx -= x; ny += y; break;

        case 5: nx -= y; ny += x; break;

        case 6: nx += y; ny += x; break;

        case 7: nx += x; ny += y; break;
        }
        return(_blocksLight((int)nx, (int)ny));
    }
    void SetVisible(uint x, uint y, uint octant, LevelPoint origin)
    {
        uint nx = origin.X, ny = origin.Y;

        switch (octant)
        {
        case 0: nx += x; ny -= y; break;

        case 1: nx += y; ny -= x; break;

        case 2: nx -= y; ny -= x; break;

        case 3: nx -= x; ny -= y; break;

        case 4: nx -= x; ny += y; break;

        case 5: nx -= y; ny += x; break;

        case 6: nx += y; ny += x; break;

        case 7: nx += x; ny += y; break;
        }
        _setVisible((int)nx, (int)ny);
    }
    private void offerAsCorner(LevelPoint point)
    {
        if ((FourCorners[UP_LEFT].X - FourCorners[UP_LEFT].Y) / 2f > (point.X - point.Y) / 2f)
        {
            FourCorners[UP_LEFT] = point;
        }

        if ((FourCorners[UP_RIGTH].X + FourCorners[UP_RIGTH].Y) / 2f < (point.X + point.Y) / 2f)
        {
            FourCorners[UP_RIGTH] = point;
        }

        if ((FourCorners[DOWN_LEFT].X + FourCorners[DOWN_LEFT].Y) / 2f > (point.X + point.Y) / 2f)
        {
            FourCorners[DOWN_LEFT] = point;
        }

        if ((FourCorners[DOWN_RIGTH].X - FourCorners[DOWN_RIGTH].Y) / 2f < (point.X - point.Y) / 2f)
        {
            FourCorners[DOWN_RIGTH] = point;
        }

        /*if(FourCorners[UP_LEFT].Y <= point.Y)
         * {
         *  if(FourCorners[UP_LEFT].X >= point.X)
         *  {
         *      FourCorners[UP_LEFT] = point;
         *  }
         * }
         * else if (FourCorners[UP_LEFT].X > point.X)
         * {
         *  if((FourCorners[UP_LEFT].Y - point.Y) < (LevelHeigth / 8))
         *  {
         *      FourCorners[UP_LEFT] = point;
         *  }
         * }
         *
         * if (FourCorners[UP_RIGTH].Y <= point.Y)
         * {
         *  if (FourCorners[UP_RIGTH].X <= point.X)
         *  {
         *      FourCorners[UP_RIGTH] = point;
         *  }
         * }
         * else if (FourCorners[UP_RIGTH].X < point.X)
         * {
         *  if ((FourCorners[UP_RIGTH].Y - point.Y) < (LevelHeigth / 8))
         *  {
         *      FourCorners[UP_RIGTH] = point;
         *  }
         * }
         *
         * if (FourCorners[DOWN_LEFT].Y >= point.Y)
         * {
         *  if (FourCorners[DOWN_LEFT].X >= point.X)
         *  {
         *      FourCorners[DOWN_LEFT] = point;
         *  }
         * }
         * else if (FourCorners[DOWN_LEFT].X > point.X)
         * {
         *  if ((FourCorners[DOWN_LEFT].Y - point.Y) < (LevelHeigth / 8))
         *  {
         *      FourCorners[DOWN_LEFT] = point;
         *  }
         * }
         *
         * if (FourCorners[DOWN_RIGTH].Y >= point.Y)
         * {
         *  if (FourCorners[DOWN_RIGTH].X <= point.X)
         *  {
         *      FourCorners[DOWN_RIGTH] = point;
         *  }
         * }
         * else if (FourCorners[DOWN_RIGTH].X < point.X)
         * {
         *  if ((FourCorners[DOWN_RIGTH].Y - point.Y) < (LevelHeigth / 8))
         *  {
         *      FourCorners[DOWN_RIGTH] = point;
         *  }
         * }*/
    }
    public void Generate()
    {
        char[,] worldData = new char[LevelWidth, LevelHeigth];
        fill(worldData, '#');

        purgeOld();

        GeneratePaths(worldData);

        m_world.InitMap(new Map(worldData));
        m_world.Draw();

        if (LevelType != LevelType.Ambush)
        {
            StartPoint = FourCorners[Random.Range(0, 4)];
            generateObjectivePoint();
            generateEndPoint();
        }

        EnemySpawnPoints = new LevelPoint[Random.Range(MinEnemySpawnCount, MaxEnemySpawnCount)];
        generateSpawnEnemySpawnPoints();

        if (Verbose)
        {
            foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[UP_LEFT].X, FourCorners[UP_LEFT].Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.red;
            }

            foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[UP_RIGTH].X, FourCorners[UP_RIGTH].Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.magenta;
            }

            foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[DOWN_LEFT].X, FourCorners[DOWN_LEFT].Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.cyan;
            }

            foreach (World.Tile tile in m_world.GetGrid().GetCell(FourCorners[DOWN_RIGTH].X, FourCorners[DOWN_RIGTH].Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.green;
            }


            foreach (World.Tile tile in m_world.GetGrid().GetCell(StartPoint.X, StartPoint.Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.yellow;
            }

            foreach (LevelPoint point in EnemySpawnPoints)
            {
                foreach (World.Tile tile in m_world.GetGrid().GetCell(point.X, point.Y).GetTiles())
                {
                    tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.black;
                }
            }
        }

        if (LevelType != LevelType.Ambush)
        {
            foreach (World.Tile tile in m_world.GetGrid().GetCell(ObjectivePoint.X, ObjectivePoint.Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = new Color(0.2f, 0.4f, 0.1f);
            }
        }

        if (LevelType != LevelType.Ambush)
        {
            foreach (World.Tile tile in m_world.GetGrid().GetCell(EndPoint.X, EndPoint.Y).GetTiles())
            {
                tile.GetGO().GetComponent <MeshRenderer>().material.color = Color.red;
            }
        }
    }
 /// <summary>
 ///     Есть ли совпадения с точками платформы
 /// </summary>
 /// <param name="point">Точка, с которой ищем совпадения</param>
 public bool Coincidences(LevelPoint point)
 {
     return(point == FirstPoint || point == SecondPoint);
 }
    void Compute(uint octant, LevelPoint origin, int rangeLimit, uint 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 <= (uint)rangeLimit; x++) // (x <= (uint)rangeLimit) == (rangeLimit < 0 || x <= rangeLimit)
        {
            // compute the Y coordinates of the top and bottom of the sector. we maintain that top > bottom
            uint topY;
            if (top.X == 1) // 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
                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
                topY = ((x * 2 - 1) * top.Y + top.X) / (top.X * 2); // 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
                if (BlocksLight(x, topY, octant, origin)) // if the tile blocks light (i.e. is a wall)...
                {
                    // 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 // the tile doesn't block light
                {
                    // 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. now 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, and 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,
                    // or 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, or 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.
                    uint ax = x * 2; // center
                    if (BlocksLight(x + 1, topY + 1, octant, origin))
                    {
                        ax++;                                // use bottom-right if the tile above and right is a wall
                    }
                    if (top.Greater(topY * 2 + 1, ax))
                    {
                        topY++;
                    }
                }
            }

            uint bottomY;
            if (bottom.Y == 0) // 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
                bottomY = 0;
            }
            else // bottom > 0
            {
                bottomY = ((x * 2 - 1) * bottom.Y + bottom.X) / (bottom.X * 2); // 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
                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
            int wasOpaque = -1;                                                  // 0:false, 1:true, -1:not applicable
            for (uint y = topY; (int)y >= (int)bottomY; y--)                     // use a signed comparison because y can wrap around when decremented
            {
                if (rangeLimit < 0 || GetDistance((int)x, (int)y) <= rangeLimit) // skip the tile if it's out of visual range
                {
                    bool 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
                    bool 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
                    if (x != rangeLimit) // but don't bother adjusting them if this is the last column anyway
                    {
                        if (isOpaque)
                        {
                            if (wasOpaque == 0) // 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
                                uint nx = x * 2, ny = y * 2 + 1; // top center by default
                                // NOTE: if you're using full symmetry and want more expansive walls (recommended), comment out the next line
                                if (BlocksLight(x, y + 1, octant, origin))
                                {
                                    nx--;                // top left if the corner is not beveled
                                }
                                if (top.Greater(ny, 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 (y == bottomY)
                                    {
                                        bottom = new Slope(ny, nx); break;
                                    }                                     // don't recurse unless necessary
                                    else
                                    {
                                        Compute(octant, origin, rangeLimit, x + 1, top, new Slope(ny, nx));
                                    }
                                }
                                else // 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
                                    if (y == bottomY)
                                    {
                                        return; // recursing, so that invalidates the current sector and we're done
                                    }
                                }
                            }
                            wasOpaque = 1;
                        }
                        else
                        {
                            if (wasOpaque > 0) // if we found a transition from opaque to clear, adjust the top vector downwards
                            {
                                // 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
                                uint nx = x * 2, ny = y * 2 + 1; // the bottom of the opaque tile (oy*2-1) equals the top of this tile (y*2+1)
                                // NOTE: if you're using full symmetry and want more expansive walls (recommended), comment out the next line
                                if (BlocksLight(x + 1, y + 1, octant, origin))
                                {
                                    nx++;                       // check the right of the opaque tile (y+1), not this one
                                }
                                // 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;
            }
        }
    }