using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using nft.framework.drawing;
using nft.core.game;
using nft.impl.game;

using ICDir = nft.core.geometry.InterCardinalDirection;
using Geocon = nft.core.geometry.GeometricConstants;
using System.Diagnostics;
namespace nft.core.geometry {
    public abstract class CoordinationUtil {
        public static CoordinationUtil Create(Size3D szWorld, ViewFactor vf) {
            Scaler s = vf.Scaler;
            switch (vf.ViewDirection) {
                case ICDir.NORTHEAST:
                    return new _NE(szWorld, s);
                case ICDir.NORTHWEST:
                    return new _NW(szWorld, s);
                case ICDir.SOUTHEAST:
                    return new _SE(szWorld, s);
                case ICDir.SOUTHWEST:
                    return new _SW(szWorld, s);
                default:
                    throw new InvalidOperationException();
            }
        }

        protected Size3D szMaxIdx;
        protected Scaler scaler;
        protected Size cellUnit;

        protected CoordinationUtil(Size3D sz, Scaler s) {
            //this.szWorld = sz;
            this.szMaxIdx = new Size3D(sz.sx-1,sz.sy-1,sz.sz-1);
            this.scaler = s;
            int w = scaler.Scale(Geocon.UnitWidthPixel);
            int h = scaler.Scale(Geocon.UnitHeightPixel);
            cellUnit = new Size(w, h);
        }

        public override int GetHashCode() {
            return GetType().GetHashCode() ^ szMaxIdx.GetHashCode() ^ scaler.GetHashCode();
        }

        public override bool Equals(object obj) {
            if (obj.GetType().Equals(this.GetType())) {
                CoordinationUtil other = obj as CoordinationUtil;
                return (szMaxIdx.Equals(other.szMaxIdx) && scaler.Value == other.scaler.Value);
            }
            return false;
        }

        /// <summary>
        /// Converts the isometric view grid coordinates(p) to world map location(X,Y,locZ).
        /// </summary>
        public abstract Location IsometricGridToLocation(int vx, int vy, int loc_z);

        /// <summary>
        /// Converts the quarter view coordinates(p) to world map location(X,Y,locZ).
        /// </summary>
        public Location QuarterXYToLocation(int vx, int vy, int locZ) {
            return QuarterXYToLocation(new Point(vx, vy), locZ);
        }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public abstract Location QuarterXYToLocation(Point p, int locZ);

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public abstract LocationF QuarterXYToLocationF(Point p, int pixHeight);

        /// <summary>
        /// Converts the world map location(X,Y,Z) to quarter view coordinates(vx,vy).
        /// </summary>
        public Point LocationToQuarterXY(Location loc) {
            return LocationToQuarterXY(RotatedForViewAxis(loc));
        }

        /// <summary>
        /// Converts the rotated world map coordinates(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public Point LocationToQuarterXY(Point3DV pt) {
            int vx = pt.VX - pt.VY + szMaxIdx.sy;
            int vy = szMaxIdx.sx - pt.VX + szMaxIdx.sy - pt.VY;
            vx *= cellUnit.Width;
            vy *= cellUnit.Width;
            vy >>= 1;
            vy += (szMaxIdx.sz - pt.VZ) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            return new Point(vx, vy);
        }

        /// <summary>
        /// Converts the world map location(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public abstract PointF LocationToQuarterXY(LocationF loc);

        /// <summary>
        /// Rotate 3D Rectangle along the quarter view pseudo axis.
        /// the vertex which point out the Location will re-selected to 
        /// the one most nearest to the axis origin (0,0,0);
        /// Y Axis:Increase from bottom right to top left
        /// X Axis:Increase from bottom left to top right
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        public abstract Rect3DV RotatedForViewAxis(Rect3D rect);

        /// <summary>
        /// Rotate 3D Location along the quarter view pseudo axis.
        /// coordination will recalced to be an offset from the front vertex of the world rectangle.
        /// Y Axis:Increase from bottom right to top left
        /// X Axis:Increase from bottom left to top right
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        public abstract Point3DV RotatedForViewAxis(Location loc);

        /// <summary>
        /// Convert view axis point to world Locaion.
        /// </summary>
        /// <param name="loc"></param>
        /// <returns></returns>
        public abstract Location RestoreRotated(Point3DV pt);

        /// <summary>
        /// Rotate 3D size along the quarter view pseudo axis.
        /// Different from the rotation of Point3D, world view-origin does no effect.
        /// </summary>
        /// <param name="?"></param>
        /// <returns></returns>
        public abstract Size3DV RotatedForViewAxis(Size3D size);

        /// <summary>
        /// Offset location by values along the rotated view axis.
        /// </summary>
        /// <param name="loc"></param>
        /// <param name="vx"></param>
        /// <param name="vy"></param>
        public abstract void OffsetLocationByRotatedAxis(ref Location loc, int vx, int vy);

        /// <summary>
        /// Offset location by values along the rotated view axis.
        /// </summary>
        /// <param name="loc"></param>
        /// <param name="vx"></param>
        /// <param name="vy"></param>
        /// <returns></returns>
        public abstract Location OffsetLocationByRotatedAxis(Location loc, int vx, int vy);

        protected bool IsWestCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            int x = l.X;
            int y = l.Y;
            basegap = 0;
            if (y < 0 || x < 0 || y >= map.Size.sy || x >= map.Size.sx)
                return false;
            TerrainPiecePair p1 = map[x, y];
            ITerrainPiece tp1 = p1[Direction4.WEST];
            if (x < 1) {
                basegap = tp1.BaseHeight;
                return true;
            }
            TerrainPiecePair p2 = map[x - 1, y];
            ITerrainPiece tp2 = p2[Direction4.EAST];
            if (tp1.GetHeightAt(ICDir.NORTHWEST) > tp2.GetHeightAt(ICDir.NORTHEAST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            if (tp1.GetHeightAt(ICDir.SOUTHWEST) > tp2.GetHeightAt(ICDir.SOUTHEAST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            return false;
        }

        protected bool IsEastCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            int x = l.X;
            int y = l.Y;
            basegap = 0;
            if (y < 0 || x < 0 || y >= map.Size.sy || x >= map.Size.sx)
                return false;
            TerrainPiecePair p1 = map[x, y];
            ITerrainPiece tp1 = p1[Direction4.EAST];
            if (map.Size.sx - x <= 1) {
                basegap = tp1.BaseHeight;
                return true;
            }
            TerrainPiecePair p2 = map[x + 1, y];
            ITerrainPiece tp2 = p2[Direction4.WEST];
            if (tp1.GetHeightAt(ICDir.NORTHEAST) > tp2.GetHeightAt(ICDir.NORTHWEST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            if (tp1.GetHeightAt(ICDir.SOUTHEAST) > tp2.GetHeightAt(ICDir.SOUTHWEST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            return false;
        }

        static protected bool IsNorthCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            int x = l.X;
            int y = l.Y;
            basegap = 0;
            if (y < 0 || x < 0 || y >= map.Size.sy || x >= map.Size.sx)
                return false;
            TerrainPiecePair p1 = map[x, y];
            ITerrainPiece tp1 = p1[Direction4.NORTH];
            if (map.Size.sy - y <= 1) {
                basegap = tp1.BaseHeight;
                return true;
            } 
            TerrainPiecePair p2 = map[x, y + 1];
            ITerrainPiece tp2 = p2[Direction4.SOUTH];
            if (tp1.GetHeightAt(ICDir.NORTHEAST) > tp2.GetHeightAt(ICDir.SOUTHEAST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            if (tp1.GetHeightAt(ICDir.NORTHWEST) > tp2.GetHeightAt(ICDir.SOUTHWEST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            return false;
        }

        protected bool IsSouthCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            int x = l.X;
            int y = l.Y;
            basegap = 0;
            if (y < 0 || x < 0 || y >= map.Size.sy || x >= map.Size.sx)
                return false;
            TerrainPiecePair p1 = map[x, y];
            ITerrainPiece tp1 = p1[Direction4.SOUTH];
            if (y < 1) {
                basegap = tp1.BaseHeight;
                return true;
            }
            TerrainPiecePair p2 = map[x, y - 1];
            ITerrainPiece tp2 = p2[Direction4.NORTH];
            if (tp1.GetHeightAt(ICDir.SOUTHEAST) > tp2.GetHeightAt(ICDir.NORTHEAST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            if (tp1.GetHeightAt(ICDir.SOUTHWEST) > tp2.GetHeightAt(ICDir.NORTHWEST)) {
                basegap = tp1.BaseHeight - tp2.BaseHeight;
                return true;
            }
            return false;
        }

        /// <summary>
        /// returns true if the cliff on the left side of the cell is visible.
        /// </summary>
        /// <param name="terrainMapImpl"></param>
        /// <param name="l"></param>
        /// <param name="basegap">base height gap in unit</param>
        /// <returns></returns>
        public abstract bool IsLeftCliffVisible(TerrainMapImpl map, Location l, out int basegap);

        /// <summary>
        /// returns true if the cliff on the right side of the cell is visible.
        /// </summary>
        /// <param name="terrainMapImpl"></param>
        /// <param name="l"></param>
        /// <param name="basegap">base height gap in unit</param>
        /// <returns></returns>
        public abstract bool IsRightCliffVisible(TerrainMapImpl map, Location l, out int basegap);

        /// <summary>
        /// returns true if the cliff on the diagonal line of the cell is visible.
        /// </summary>
        /// <param name="map"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public abstract bool IsDiagonalCliffVisible(TerrainMapImpl map, Location l, out int basegap);

    }

    class _NE : CoordinationUtil {

        internal _NE(Size3D sz, Scaler s) : base(sz, s) { }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location IsometricGridToLocation(int vx, int vy, int loc_z) {
            return new Location(vx, vy, loc_z);
        }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location QuarterXYToLocation(Point p, int locZ) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz - locZ) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            int locX = ((szMaxIdx.sx * unit + p.X) >> 1) - p.Y + vz;
            int locY = szMaxIdx.sy * unit - p.X + locX;
            locX /= unit;
            locY /= unit;
            return new Location(locX, locY, locZ);
        }

        public override LocationF QuarterXYToLocationF(Point p, int ph) {
            int unit = cellUnit.Width;
            double vz = (szMaxIdx.sz * cellUnit.Height / Geocon.TerrainStepHeightInUnit) - ph;
            double locX = ((szMaxIdx.sx * unit + p.X) / 2.0f) - p.Y + vz;
            double locY = szMaxIdx.sy * unit - p.X + locX;
            locX /= unit;
            locY /= unit;
            return new LocationF(locX, locY, ph * Geocon.TerrainStepHeightInUnit / (double)cellUnit.Height);
        }

        /// <summary>
        /// Converts the world map coordinates(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public override PointF LocationToQuarterXY(LocationF loc) {
            float vx = loc.X - loc.Y + szMaxIdx.sy;
            float vy = (szMaxIdx.sx - loc.X + szMaxIdx.sy - loc.Y) / 2;
            int unit = cellUnit.Width;
            vx *= unit;
            vy *= unit;
            vy += (szMaxIdx.sz - loc.Z) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            return new PointF(vx, vy);
        }

        public override Rect3DV RotatedForViewAxis(Rect3D rect) {
            return new Rect3DV(rect);
        }

        public override Point3DV RotatedForViewAxis(Location loc) {
            return new Point3DV(loc);
        }

        public override Location RestoreRotated(Point3DV pt) {
            return new Location(pt.VX, pt.VY, pt.VZ);
        }

        public override Size3DV RotatedForViewAxis(Size3D size) {
            return new Size3DV(size);
        }

        public override void OffsetLocationByRotatedAxis(ref Location loc, int vx, int vy) {
            loc.X += (short)vx;
            loc.Y += (short)vy;
        }

        public override Location OffsetLocationByRotatedAxis(Location loc, int vx, int vy) {
            return new Location(loc.X + vx, loc.Y + vy, loc.Z);
        }

        public override bool IsLeftCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsWestCliffVisible(map, l, out basegap);
        }

        public override bool IsRightCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsSouthCliffVisible(map, l, out basegap);
        }

        public override bool IsDiagonalCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            basegap = 0;
            TerrainPiecePair pair = map[l.X, l.Y];
            ITerrainPiece p1 = pair[Direction4.NORTH];
            ITerrainPiece p2 = pair[Direction4.SOUTH];
            int d2 = p2.GetHeightAt(ICDir.NORTHWEST);
            if (d2 < 0) return false;
            int d1 = p1.GetHeightAt(ICDir.NORTHWEST);
            int d3 = p1.GetHeightAt(ICDir.SOUTHEAST);
            int d4 = p2.GetHeightAt(ICDir.SOUTHEAST);
            if (d1 > d2 || d3 > d4) {
                basegap = p1.BaseHeight - p2.BaseHeight;
                return true;
            }
            return false;
        }
    }

    class _NW : CoordinationUtil {

        internal _NW(Size3D sz, Scaler s) : base(sz, s) { }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location IsometricGridToLocation(int vx, int vy, int loc_z) {
            //return new Location(vy, szMaxIdx.sy - vx, loc_z);
            return new Location(szMaxIdx.sx - vy, vx, loc_z);
        }

        
        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location QuarterXYToLocation(Point p, int locZ) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz - locZ) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            int locX = p.Y + ((p.X - szMaxIdx.sy * unit) >> 1) + vz;
            int locY = p.X - locX;
            locX /= unit;
            locY /= unit;
            return new Location(locX, locY, locZ);
        }

        public override LocationF QuarterXYToLocationF(Point p, int ph) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz * cellUnit.Height / Geocon.TerrainStepHeightInUnit )- ph;
            double locX = p.Y + ((p.X - szMaxIdx.sy * unit) / 2) + vz;
            double locY = p.X - locX;
            locX /= unit;
            locY /= unit;
            return new LocationF(locX, locY, ph * Geocon.TerrainStepHeightInUnit / (double)cellUnit.Height);
        }

        /// <summary>
        /// Converts the world map coordinates(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public override PointF LocationToQuarterXY(LocationF loc) {
            float vx = loc.X + loc.Y;
            float vy = (loc.X + szMaxIdx.sy - loc.Y) / 2;
            int unit = cellUnit.Width;
            vx *= unit;
            vy *= unit;
            vy += (szMaxIdx.sz - loc.Z) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            return new PointF(vx, vy);
        }

        public override Rect3DV RotatedForViewAxis(Rect3D rect) {
            Location loc = rect.Location;
            loc.X += (short)rect.WidthX;
            Point3DV pt = RotatedForViewAxis(loc);
            Size3DV sz = RotatedForViewAxis(rect.Size);
            return new Rect3DV(pt, sz);
        }

        public override Point3DV RotatedForViewAxis(Location loc) {
            return new Point3DV(loc.Y, szMaxIdx.sx - loc.X, loc.Z);
        }

        public override Location RestoreRotated(Point3DV pt) {
            return new Location(szMaxIdx.sx - pt.VY, pt.VX, pt.VZ);
        }

        public override Size3DV RotatedForViewAxis(Size3D size) {
            return new Size3DV(size.sy, size.sx, size.sz);
        }

        public override void OffsetLocationByRotatedAxis(ref Location loc, int vx, int vy) {
            loc.X -= (short)vy;
            loc.Y += (short)vx;
        }

        public override Location OffsetLocationByRotatedAxis(Location loc, int vx, int vy) {
            return new Location(loc.X - vy, loc.Y + vx, loc.Z);
        }

        public override bool IsLeftCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsSouthCliffVisible(map, l, out basegap);
        }

        public override bool IsRightCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsEastCliffVisible(map, l, out basegap);
        }

        public override bool IsDiagonalCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            basegap = 0;
            TerrainPiecePair pair = map[l.X, l.Y];
            ITerrainPiece p1 = pair[Direction4.NORTH];
            ITerrainPiece p2 = pair[Direction4.SOUTH];
            int d2 = p2.GetHeightAt(ICDir.NORTHEAST);
            if (d2 < 0) return false;
            int d1 = p1.GetHeightAt(ICDir.NORTHEAST);
            int d3 = p1.GetHeightAt(ICDir.SOUTHWEST);
            int d4 = p2.GetHeightAt(ICDir.SOUTHWEST);
            if (d1 > d2 || d3 > d4) {
                basegap = p1.BaseHeight - p2.BaseHeight;
                return true;
            }
            return false;
        }
    }

    class _SW : CoordinationUtil {

        internal _SW(Size3D sz, Scaler s) : base(sz, s) { }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location IsometricGridToLocation(int vx, int vy, int loc_z) {
            return new Location(szMaxIdx.sx - vx, szMaxIdx.sy - vy, loc_z);
        }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location QuarterXYToLocation(Point p, int locZ) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz - locZ) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            int wx = szMaxIdx.sx * unit;
            int locY = p.Y + ((p.X - wx) >> 1) - vz;
            int locX = wx + locY - p.X;
            locX /= unit;
            locY /= unit;
            return new Location(locX, locY, locZ);
        }

        public override LocationF QuarterXYToLocationF(Point p, int ph) {
            int unit = cellUnit.Width;
            int vz = szMaxIdx.sz * cellUnit.Height / Geocon.TerrainStepHeightInUnit - ph;
            int wx = szMaxIdx.sx * unit;
            double locY = p.Y + ((p.X - wx) >> 1) - vz;
            double locX = wx + locY - p.X;
            locX /= unit;
            locY /= unit;
            return new LocationF(locX, locY, ph * Geocon.TerrainStepHeightInUnit / (double)cellUnit.Height);
        }
        
        /// <summary>
        /// Converts the world map coordinates(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public override PointF LocationToQuarterXY(LocationF loc) {
            float vx = loc.Y - loc.X + szMaxIdx.sx;
            float vy = (loc.X + loc.Y) / 2;
            int unit = cellUnit.Width;
            vx *= unit;
            vy *= unit;
            vy += (szMaxIdx.sz - loc.Z) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            return new PointF(vx, vy);
        }

        public override Rect3DV RotatedForViewAxis(Rect3D rect) {
            Location loc = rect.Location;
            loc.X += (short)rect.WidthX;
            loc.Y += (short)rect.WidthY;
            Point3DV pt = RotatedForViewAxis(loc);
            Size3DV sz = RotatedForViewAxis(rect.Size);
            return new Rect3DV(pt, sz);
        }

        public override Point3DV RotatedForViewAxis(Location loc) {
            return new Point3DV(szMaxIdx.sx - loc.X, szMaxIdx.sy - loc.Y, loc.Z);
        }

        public override Location RestoreRotated(Point3DV pt) {
            return new Location(szMaxIdx.sx - pt.VX, szMaxIdx.sy - pt.VY, pt.VZ);
        }

        public override Size3DV RotatedForViewAxis(Size3D size) {
            return new Size3DV(size);
        }

        public override void OffsetLocationByRotatedAxis(ref Location loc, int vx, int vy) {
            loc.X -= (short)vx;
            loc.Y -= (short)vy;
        }

        public override Location OffsetLocationByRotatedAxis(Location loc, int vx, int vy) {
            return new Location(loc.X - vx, loc.Y - vy, loc.Z);
        }

        public override bool IsLeftCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsEastCliffVisible(map, l, out basegap);
        }

        public override bool IsRightCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsNorthCliffVisible(map, l, out basegap);
        }

        public override bool IsDiagonalCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            basegap = 0;
            TerrainPiecePair pair = map[l.X, l.Y];
            ITerrainPiece p1 = pair[Direction4.SOUTH];
            ITerrainPiece p2 = pair[Direction4.NORTH];
            int d2 = p2.GetHeightAt(ICDir.NORTHWEST);
            if (d2 < 0) return false;
            int d1 = p1.GetHeightAt(ICDir.NORTHWEST);
            int d3 = p1.GetHeightAt(ICDir.SOUTHEAST);
            int d4 = p2.GetHeightAt(ICDir.SOUTHEAST);
            if (d1 > d2 || d3 > d4) {
                basegap = p1.BaseHeight - p2.BaseHeight;
                return true;
            }
            return false;
        }
    }

    class _SE : CoordinationUtil {

        internal _SE(Size3D sz, Scaler s) : base(sz, s) { }
        
        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location IsometricGridToLocation(int vx, int vy, int loc_z) {
            return new Location(vy, szMaxIdx.sy - vx, loc_z);
            //return new Location(szMaxIdx.sx - vy, vx, loc_z);
        }

        /// <summary>
        /// Converts the quarter view coordinates(vx,vy) to world map location(X,Y,locZ).
        /// </summary>
        public override Location QuarterXYToLocation(Point p, int locZ) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz - locZ) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            int wx = szMaxIdx.sx * unit;
            int wy = szMaxIdx.sy * unit;
            int locX = wx - p.Y + ((wy - p.X) >> 1) + vz;
            int locY = wx - locX + wy - p.X;
            locX /= unit;
            locY /= unit;
            return new Location(locX, locY, locZ);
        }

        public override LocationF QuarterXYToLocationF(Point p, int ph) {
            int unit = cellUnit.Width;
            int vz = (szMaxIdx.sz * cellUnit.Height / Geocon.TerrainStepHeightInUnit )- ph;
            int wx = szMaxIdx.sx * unit;
            int wy = szMaxIdx.sy * unit;
            double locX = wx - p.Y + ((wy - p.X) / 2) + vz;
            double locY = wx - locX + wy - p.X;
            locX /= unit;
            locY /= unit;
            return new LocationF(locX, locY, ph * Geocon.TerrainStepHeightInUnit / (double)cellUnit.Height);
        }

        /// <summary>
        /// Converts the world map coordinates(X,Y,Z) to quarter view coordinates(X,Y).
        /// </summary>
        public override PointF LocationToQuarterXY(LocationF loc) {
            float vx = szMaxIdx.sx - loc.X + szMaxIdx.sy - loc.Y;
            float vy = (loc.Y + szMaxIdx.sx - loc.X) / 2;
            int unit = cellUnit.Width;
            vx *= unit;
            vy *= unit;
            vy += (szMaxIdx.sz - loc.Z) * cellUnit.Height / Geocon.TerrainStepHeightInUnit;
            return new PointF(vx, vy);
        }

        public override Rect3DV RotatedForViewAxis(Rect3D rect) {
            Location loc = rect.Location;
            loc.Y += (short)rect.WidthY;
            Point3DV pt = RotatedForViewAxis(loc);
            Size3DV sz = RotatedForViewAxis(rect.Size);
            return new Rect3DV(pt, sz);
        }

        public override Point3DV RotatedForViewAxis(Location loc) {
            return new Point3DV(szMaxIdx.sy - loc.Y, loc.X, loc.Z);
        }

        public override Location RestoreRotated(Point3DV pt) {
            return new Location(pt.VY, szMaxIdx.sy - pt.VX, pt.VZ);
        }

        public override Size3DV RotatedForViewAxis(Size3D size) {
            return new Size3DV(size.sy, size.sx, size.sz);
        }

        public override void OffsetLocationByRotatedAxis(ref Location loc, int vx, int vy) {
            loc.X += (short)vy;
            loc.Y -= (short)vx;
        }

        public override Location OffsetLocationByRotatedAxis(Location loc, int vx, int vy) {
            return new Location(loc.X + vy, loc.Y - vx, loc.Z);
        }

        public override bool IsLeftCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsNorthCliffVisible(map, l, out basegap);
        }

        public override bool IsRightCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            return IsWestCliffVisible(map, l, out basegap);
        }

        public override bool IsDiagonalCliffVisible(TerrainMapImpl map, Location l, out int basegap) {
            basegap = 0;
            TerrainPiecePair pair = map[l.X, l.Y];
            ITerrainPiece p1 = pair[Direction4.SOUTH];
            ITerrainPiece p2 = pair[Direction4.NORTH];
            int d2 = p2.GetHeightAt(ICDir.NORTHEAST);
            if (d2 < 0) return false;
            int d1 = p1.GetHeightAt(ICDir.NORTHEAST);
            int d3 = p1.GetHeightAt(ICDir.SOUTHWEST);
            int d4 = p2.GetHeightAt(ICDir.SOUTHWEST);
            if (d1 > d2 || d3 > d4) {
                basegap = p1.BaseHeight - p2.BaseHeight;
                return true;
            }
            return false;

        }
    }
}
