using System; using System.Collections.Generic; using System.Text; using CommonAI.RTS; using CommonLang.Vector; using CommonLang; using CommonLang.Concurrent; using CommonAI.ZoneServer.JSGModule; namespace CommonAI.RTS.Manhattan { /// /// 实现了曼哈顿算法的A*寻路算法 /// public partial class AstarManhattan : RTSAstar { #region _Fields_ private MTerrain terrain_map; private IManhattanMap manhattan_map; private MSpaceAStar space_div; private readonly float space_sqrt; private readonly float cellw; private readonly float cellh; private readonly float cell_sqrt; private readonly float totalw; private readonly float totalh; private int area_count = 0; private readonly int div_size; private int space_optmize_limit_distance = 4; private int space_div_enable_distance = 4; private DirtyNodes dirty_nodes = new DirtyNodes(); #endregion //--------------------------------------------------------------------------------------------------------------------- /// /// 创建一个以曼哈顿算法的A*寻路算法 /// /// 地图适配器 /// 是否要检测斜边可以移动 /// 空间切割尺寸 public AstarManhattan(int uniqueID, IManhattanMap map_data, bool inclined = true, int space_size = 0) { //21.3, 7.3, 5.7, 3 this.manhattan_map = map_data; this.cellw = map_data.CellW; this.cellh = map_data.CellH; this.totalw = cellw * map_data.XCount; this.totalh = cellh * map_data.YCount; if (totalw < ushort.MinValue || totalw > ushort.MaxValue || totalh < ushort.MinValue || totalh > ushort.MaxValue) { throw new Exception(string.Format("Terrain Size must in ({0}~{1}", ushort.MinValue, ushort.MaxValue)); } this.div_size = space_size; this.cell_sqrt = (float)Math.Sqrt((cellw * cellw) + (cellh * cellh)); this.space_sqrt = (float)Math.Sqrt((div_size * div_size) + (div_size * div_size)); this.terrain_map = new MTerrain(this, map_data, inclined); base.Init(terrain_map); this.ResetArea(); if (space_size > 0) { //this.space_div = MSpaceNodeCache.GetCacheAStart(uniqueID, this, space_size); this.space_div = new MSpaceAStar(uniqueID, this, space_size); } } protected override void Disposing() { base.Disposing(); if (space_div != null) { space_div.Dispose(); space_div = null; } this.dirty_nodes.Clear(); this.manhattan_map = null; this.terrain_map = null; } //--------------------------------------------------------------------------------------------------------------------- #region _Properties_ public IManhattanMap MMap { get { return manhattan_map; } } /// /// 超过多少格子(空间分割格子)不进行路径优化 /// public int SpaceOptimizePathLimitDistance { get { return space_optmize_limit_distance; } set { if (value > 1) { space_optmize_limit_distance = value; } } } /// /// 超过多少格子(空间分割格子)启用空间分割 /// public int SpaceDivEnableDistance { get { return space_div_enable_distance; } set { if (value > 1) { space_div_enable_distance = value; } } } /// /// 空间分割尺度 /// public int SpaceDivSize { get { return div_size; } } /// /// 空间分割寻路 /// public MSpaceAStar SpaceAstar { get { return space_div; } } /// /// 是否支持8方向 /// public bool IsInclined { get { return terrain_map.is_inclined; } } /// /// 地图总共多少可行走区域 /// public int AreaCount { get { return area_count; } } #endregion //--------------------------------------------------------------------------------------------------------------------- #region _ForEachTerrain_ public static void NormalizeTerrainRegionByBlock(IManhattanMap tmap, ref int min_x, ref int min_y, ref int max_x, ref int max_y) { min_x = Math.Max(min_x, 0); min_y = Math.Max(min_y, 0); max_x = Math.Min(max_x, tmap.XCount - 1); max_y = Math.Min(max_y, tmap.YCount - 1); } /// /// /// /// /// public delegate void ForEachTerrainAction(int bx, int by); public static void ForEachTerrainPoint(IManhattanMap tmap, float sx, float sy, ForEachTerrainAction action) { int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); if (cx1 >= 0 && cx1 < tmap.XCount && cy1 >= 0 && cy1 < tmap.YCount) { action(cx1, cy1); } } public static void ForEachTerrainRect(IManhattanMap tmap, float sx, float sy, float w, float h, ForEachTerrainAction action) { int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); int cx2 = (int)((sx + w) / tmap.CellW); int cy2 = (int)((sy + h) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { action(cx, cy); } } } public static void ForEachTerrainRound(IManhattanMap tmap, float sx, float sy, float r, ForEachTerrainAction action) { int cx1 = (int)((sx - r) / tmap.CellW); int cy1 = (int)((sy - r) / tmap.CellH); int cx2 = (int)((sx + r) / tmap.CellW); int cy2 = (int)((sy + r) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { if (CMath.intersectRectRound( cx * tmap.CellW, cy * tmap.CellH, cx * tmap.CellW + tmap.CellW, cy * tmap.CellH + tmap.CellH, sx, sy, r)) { action(cx, cy); } } } } public static void ForEachTerrainEllipse(IManhattanMap tmap, float sx, float sy, float w, float h, ForEachTerrainAction action) { float scrw = w / 2; float scrh = h / 2; float scx = sx + w / 2; float scy = sy + h / 2; int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); int cx2 = (int)((sx + w) / tmap.CellW); int cy2 = (int)((sy + h) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); float cx0; float cy0; for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { cx0 = cx * tmap.CellW; cy0 = cy * tmap.CellH; if (CMath.intersectRectEllipse(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, scx, scy, scrw, scrh)) { action(cx, cy); } } } } public static void ForEachTerrainLine(IManhattanMap tmap, float x0, float y0, float x1, float y1, ForEachTerrainAction action) { int bx0 = (int)(x0 / tmap.CellW); int by0 = (int)(y0 / tmap.CellH); int bx1 = (int)(x1 / tmap.CellW); int by1 = (int)(y1 / tmap.CellH); if (bx0 > bx1) CUtils.Swap(ref bx0, ref bx1); if (by0 > by1) CUtils.Swap(ref by0, ref by1); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= tmap.XCount) bx1 = tmap.XCount - 1; if (by1 >= tmap.YCount) by1 = tmap.YCount - 1; float cx0; float cy0; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { cx0 = bx * tmap.CellW; cy0 = by * tmap.CellH; if (CMath.intersectRectLine(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, x0, y0, x1, y1)) { action(bx, by); } } } } public static void ForEachTerrainStripWidth(IManhattanMap tmap, float x0, float y0, float x1, float y1, float line_r, ForEachTerrainAction action) { CommonLang.Geometry.Vector2[] points = CMath.toStripWidthPolygon(x0, y0, x1, y1, line_r); CommonLang.Geometry.Vector2 min, max; CMath.toBoundingBox(points, out min, out max); int bx0 = (int)(min.X / tmap.CellW); int by0 = (int)(min.Y / tmap.CellH); int bx1 = (int)(max.X / tmap.CellW); int by1 = (int)(max.Y / tmap.CellH); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= tmap.XCount) bx1 = tmap.XCount - 1; if (by1 >= tmap.YCount) by1 = tmap.YCount - 1; float cx0; float cy0; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { cx0 = bx * tmap.CellW; cy0 = by * tmap.CellH; if (CMath.intersectRectPolygon(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, points)) { action(bx, by); } } } } /// /// /// /// /// /// True can break public delegate bool ForEachTerrainPredicate(int bx, int by); public static bool ForEachTerrainPoint(IManhattanMap tmap, float sx, float sy, ForEachTerrainPredicate action) { int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); if (cx1 >= 0 && cx1 < tmap.XCount && cy1 >= 0 && cy1 < tmap.YCount) { if (action(cx1, cy1)) { return true; } } return false; } public static bool ForEachTerrainRect(IManhattanMap tmap, float sx, float sy, float w, float h, ForEachTerrainPredicate action) { int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); int cx2 = (int)((sx + w) / tmap.CellW); int cy2 = (int)((sy + h) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { if (action(cx, cy)) { return true; } } } return false; } public static bool ForEachTerrainRound(IManhattanMap tmap, float sx, float sy, float r, ForEachTerrainPredicate action) { int cx1 = (int)((sx - r) / tmap.CellW); int cy1 = (int)((sy - r) / tmap.CellH); int cx2 = (int)((sx + r) / tmap.CellW); int cy2 = (int)((sy + r) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { if (CMath.intersectRectRound( cx * tmap.CellW, cy * tmap.CellH, cx * tmap.CellW + tmap.CellW, cy * tmap.CellH + tmap.CellH, sx, sy, r)) { if (action(cx, cy)) { return true; } } } } return false; } public static bool ForEachTerrainEllipse(IManhattanMap tmap, float sx, float sy, float w, float h, ForEachTerrainPredicate action) { float scrw = w / 2; float scrh = h / 2; float scx = sx + w / 2; float scy = sy + h / 2; int cx1 = (int)((sx) / tmap.CellW); int cy1 = (int)((sy) / tmap.CellH); int cx2 = (int)((sx + w) / tmap.CellW); int cy2 = (int)((sy + h) / tmap.CellH); cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, tmap.XCount - 1); cy2 = Math.Min(cy2, tmap.YCount - 1); float cx0; float cy0; for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { cx0 = cx * tmap.CellW; cy0 = cy * tmap.CellH; if (CMath.intersectRectEllipse(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, scx, scy, scrw, scrh)) { if (action(cx, cy)) { return true; } } } } return false; } public static bool ForEachTerrainLine(IManhattanMap tmap, float x0, float y0, float x1, float y1, ForEachTerrainPredicate action) { int bx0 = (int)(x0 / tmap.CellW); int by0 = (int)(y0 / tmap.CellH); int bx1 = (int)(x1 / tmap.CellW); int by1 = (int)(y1 / tmap.CellH); if (bx0 > bx1) CUtils.Swap(ref bx0, ref bx1); if (by0 > by1) CUtils.Swap(ref by0, ref by1); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= tmap.XCount) bx1 = tmap.XCount - 1; if (by1 >= tmap.YCount) by1 = tmap.YCount - 1; float cx0; float cy0; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { cx0 = bx * tmap.CellW; cy0 = by * tmap.CellH; if (CMath.intersectRectLine(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, x0, y0, x1, y1)) { if (action(bx, by)) { return true; } } } } return false; } public static bool ForEachTerrainStripWidth(IManhattanMap tmap, float x0, float y0, float x1, float y1, float line_r, ForEachTerrainPredicate action) { CommonLang.Geometry.Vector2[] points = CMath.toStripWidthPolygon(x0, y0, x1, y1, line_r); CommonLang.Geometry.Vector2 min, max; CMath.toBoundingBox(points, out min, out max); int bx0 = (int)(min.X / tmap.CellW); int by0 = (int)(min.Y / tmap.CellH); int bx1 = (int)(max.X / tmap.CellW); int by1 = (int)(max.Y / tmap.CellH); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= tmap.XCount) bx1 = tmap.XCount - 1; if (by1 >= tmap.YCount) by1 = tmap.YCount - 1; float cx0; float cy0; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { cx0 = bx * tmap.CellW; cy0 = by * tmap.CellH; if (CMath.intersectRectPolygon(cx0, cy0, cx0 + tmap.CellW, cy0 + tmap.CellH, points)) { if (action(bx, by)) { return true; } } } } return false; } #endregion //--------------------------------------------------------------------------------------------------------------------- #region _FillTerrain_ public bool TryFillTerrain(int bx, int by, int value) { if (manhattan_map.SetValue(bx, by, value)) { var mnode = terrain_map.getMapNode(bx, by); if (mnode != null && mnode.resetValue(this.terrain_map)) { dirty_nodes.AddDirty(mnode); return true; } } return false; } //--------------------------------------------------------------------------------------------------------------------- public void FillTerrain(float sx, float sy, int value) { ForEachTerrainPoint(manhattan_map, sx, sy, (bx, by) => { TryFillTerrain(bx, by, value); }); this.BeginFindPath(); } public void FillTerrain(float sx, float sy, float w, float h, int value) { ForEachTerrainRect(manhattan_map, sx, sy, w, h, (bx, by) => { TryFillTerrain(bx, by, value); }); this.BeginFindPath(); } public void FillTerrain(float sx, float sy, float w, float h, int[,] matrix) { ForEachTerrainRect(manhattan_map, sx, sy, w, h, (bx, by) => { TryFillTerrain(bx, by, matrix[bx, by]); }); this.BeginFindPath(); } //--------------------------------------------------------------------------------------------------------------------- public void FillTerrainByBlock(int bx, int by, int value) { if (bx >= 0 && bx < manhattan_map.XCount && by >= 0 && by < manhattan_map.YCount) { TryFillTerrain(bx, by, value); } this.BeginFindPath(); } public void FillTerrainByBlock(int bx, int by, int bw, int bh, int value) { int cx1 = Math.Max(bx, 0); int cy1 = Math.Max(by, 0); int cx2 = Math.Min(bx + bw, manhattan_map.XCount); int cy2 = Math.Min(by + bh, manhattan_map.YCount); for (int cx = cx1; cx < cx2; ++cx) { for (int cy = cy1; cy < cy2; ++cy) { TryFillTerrain(cx, cy, value); } } this.BeginFindPath(); } public void FillTerrainByBlock(int bx, int by, int bw, int bh, int[,] matrix) { int cx1 = Math.Max(bx, 0); int cy1 = Math.Max(by, 0); int cx2 = Math.Min(bx + bw, manhattan_map.XCount); int cy2 = Math.Min(by + bh, manhattan_map.YCount); for (int cx = cx1; cx < cx2; ++cx) { for (int cy = cy1; cy < cy2; ++cy) { TryFillTerrain(cx, cy, matrix[cx, cy]); } } this.BeginFindPath(); } //--------------------------------------------------------------------------------------------------------------------- public void BeginFindPath() { if (dirty_nodes.Count > 0) { using (var list = ListObjectPool.AllocAutoRelease(dirty_nodes.Values)) { var near_table = GetNearTables(IsInclined); foreach (var dn in list) { foreach (var offset in near_table) { var sn = GetMapNode(dn.BX + offset[0], dn.BY + offset[1]); if (sn != null) { dirty_nodes.AddDirty(sn); } } } } using (var list = ListObjectPool.AllocAutoRelease(dirty_nodes.Values)) { foreach (var mn in list) { mn.resetNextNodes(this.terrain_map); } } dirty_nodes.Clear(); this.ResetArea(); } if (space_div != null) { this.space_div.beginFindPath(); } } /// /// 计算连续区域 /// private void ResetArea() { byte area_index = 0; HashMap dirty_map = new HashMap(terrain_map.TotalNodeCount); for (int by = 0; by < terrain_map.ycount; by++) { for (int bx = 0; bx < terrain_map.xcount; bx++) { var mnode = terrain_map.getMapNode(bx, by); if (mnode != null) { mnode.area = 0; if (!mnode.Blocked) { dirty_map.Add(mnode, true); } } } } Stack stack = new Stack(dirty_map.Count); while (dirty_map.Count > 0) { area_index++; var exist = dirty_map.GetEnumerator(); stack.Clear(); if (exist.MoveNext()) { var current = exist.Current.Key; dirty_map.Remove(current); stack.Push(current); } while (stack.Count > 0) { MMapNode cur = stack.Pop(); cur.area = area_index; for (int i = cur.nextnodes.Length - 1; i >= 0; --i) { MMapNode next = cur.nextnodes[i]; if (dirty_map.Remove(next)) { stack.Push(next); } } } } area_count = area_index; } #endregion //--------------------------------------------------------------------------------------------------------------------- #region _FindPath_ public override WayPoint GenWayPoint(MapNode node) { return new MWayPoint(node as MMapNode); } public enum FindPathResult : byte { /// /// 可通过 /// Cross = 0, /// /// 没有路径 /// NoWay = 1, /// /// 原地 /// Destination = 2, /// /// 寻路范围超出地图范围 /// OutOfMap = 3, } public virtual FindPathResult findPath(float sx, float sy, float dx, float dy, out MWayPoint ret, bool optimize = true) { ret = null; int sbx = (int)(sx / cellw); int sby = (int)(sy / cellh); int dbx = (int)(dx / cellw); int dby = (int)(dy / cellh); if (sbx >= 0 && sbx < terrain_map.xcount && sby >= 0 && sby < terrain_map.ycount && dbx >= 0 && dbx < terrain_map.xcount && dby >= 0 && dby < terrain_map.ycount) { if (sbx == dbx && sby == dby) { var dn = terrain_map.getMapNode(sbx, sby); if (dn != null) { ret = new MWayPoint(dn); ret.SetPos(dx, dy); return FindPathResult.Destination; } else { return FindPathResult.NoWay; } } var snode = terrain_map.getMapNode(sbx, sby); var dnode = terrain_map.getMapNode(dbx, dby); if (snode == null || dnode == null) { return FindPathResult.NoWay; } if (snode.Blocked || dnode.Blocked) { return FindPathResult.NoWay; } if (snode.AreaValue != dnode.AreaValue) { return FindPathResult.NoWay; } BeginFindPath(); #region space_div if (space_div != null) { var sn = space_div.getSpaceNodeByBlock(sbx, sby); var dn = space_div.getSpaceNodeByBlock(dbx, dby); if (sn != null && dn != null && sn != dn) { if ((Math.Abs(sn.CX - dn.CX) >= space_div_enable_distance) || (Math.Abs(sn.CY - dn.CY) >= space_div_enable_distance)) { if (sn.AnchorNode != null && dn.AnchorNode != null) { var space_wp = space_div.findPath(sn, dn) as MSpaceAStar.MSpaceWayPoint; if (space_wp == null || space_wp.next == null) { return FindPathResult.NoWay; } space_wp = space_wp.Next; space_wp.tail.prev.LinkNext(null); var node_head = space_wp.SpaceNode.AnchorNode; var node_tail = space_wp.Tail.SpaceNode.AnchorNode; var path_head = findPathInternal(sx, sy, node_head.PosX, node_head.PosY, snode, node_head, optimize) as MWayPoint; if (path_head == null) { return FindPathResult.NoWay; } var path_tail = findPathInternal(node_tail.PosX, node_tail.PosY, dx, dy, node_tail, dnode, optimize) as MWayPoint; if (path_tail == null) { return FindPathResult.NoWay; } var path_body = space_wp.ToLinkWayPoint(); if (path_body != null) { path_head.tail.LinkNext(path_body); path_body.tail.LinkNext(path_tail); if (optimize) { if (Math.Abs(sn.CX - dn.CX) <= space_optmize_limit_distance && Math.Abs(sn.CY - dn.CY) <= space_optmize_limit_distance) { optimizePathInner(path_head); } } } else { path_head.tail.LinkNext(path_tail); if (optimize) { optimizePathInner(path_head); } } ret = path_head; ret = ret.Next; return FindPathResult.Cross; } } } } #endregion ret = findPathInternal(sx, sy, dx, dy, snode, dnode, optimize); if (ret == null || ret.Next == null) { return FindPathResult.NoWay; } else { ret = ret.Next; return FindPathResult.Cross; } } else { return FindPathResult.OutOfMap; } } protected virtual MWayPoint findPathInternal(float sx, float sy, float dx, float dy, MMapNode snode, MMapNode dnode, bool optimize) { MWayPoint ret = base.findPath(snode.tempNode, dnode.tempNode) as MWayPoint; if (ret.next == null) { return ret.next as MWayPoint; } else { MWayPoint root = ret; MWayPoint tail = root.Tail; if (!terrain_map.is_inclined) { root.SetPos(sx, sy); tail.SetPos(dx, dy); } else { var nroot = GenWayPoint(snode); nroot.LinkNext(root); root = nroot as MWayPoint; root.SetPos(sx, sy); var ntail = GenWayPoint(dnode); tail.LinkNext(ntail); tail = ntail as MWayPoint; tail.SetPos(dx, dy); } if (optimize) { optimizePathInner(root); } ret = root; } return ret; } protected MWayPoint findPathInternal(MMapNode snode, MMapNode dnode) { MWayPoint ret = base.findPath(snode.tempNode, dnode.tempNode) as MWayPoint; if (ret.next == null) { return ret.next as MWayPoint; } else { optimizePathInner(ret); } return ret; } protected virtual void optimizePathInner(MWayPoint root) { bool finded = false; do { finded = false; MWayPoint current = root; while (current != null && current.next != null) { MWayPoint next = current.Next; while (true) { if (next == null) { current = next; break; } if ((current.PosX == next.PosX) && (current.PosY == next.PosY)) { current.LinkNext(next.Next); next = next.Next; finded = true; continue; } if (next.Next == null) { current = next; break; } if (current.IsCenter && next.IsCenter && next.Next.IsCenter) { //三点一线// if (current.BX == next.BX && current.BX == next.Next.BX) { current.LinkNext(next.Next); next = next.Next; finded = true; break; } if (current.BY == next.BY && current.BY == next.Next.BY) { current.LinkNext(next.Next); next = next.Next; finded = true; break; } if (IsInclined) { int cnnx = current.BX - next.Next.BX; int cnny = current.BY - next.Next.BY; if (Math.Abs(cnnx) == Math.Abs(cnny)) { int cnx = current.BX - next.BX; int cny = current.BY - next.BY; int nnx = next.BX - next.Next.BX; int nny = next.BY - next.Next.BY; if ((cnnx == cnx + nnx) && (cnny == cny + nny)) { current.LinkNext(next.Next); next = next.Next; finded = true; break; } } } } if (TouchMapLine(current.PosX, current.PosY, next.Next.PosX, next.Next.PosY)) { current = next; break; } else { current.LinkNext(next.Next); next = next.Next; finded = true; continue; } } } } while (finded); } #endregion //--------------------------------------------------------------------------------------------------------------------- #region _MapNodes_ public MMapNode GetMapNode(int bx, int by) { if (bx >= 0 && bx < terrain_map.xcount && by >= 0 && by < terrain_map.ycount) { return this.terrain_map.getMapNode(bx, by); } return null; } public MMapNode GetMapNodeByPos(float x, float y) { int bx = (int)(x / cellw); int by = (int)(y / cellh); return GetMapNode(bx, by); } public MSpaceAStar.MSpaceNode GetSpaceMapNode(int cx, int cy) { if (space_div != null) { return space_div.SpaceMap.getSpaceNode(cx, cy); } return null; } public void PosToBlock(float x, float y, out int bx, out int by) { bx = (int)(x / cellw); by = (int)(y / cellh); } public bool InRangeByBlock(int bx, int by) { if (bx >= 0 && bx < manhattan_map.XCount && by >= 0 && by < manhattan_map.YCount) { return true; } return false; } public bool TouchMapBlock(int bx, int by) { var node = GetMapNode(bx, by); if (node == null) return true; return node.Blocked; } public bool TouchMapRect(float x0, float y0, float x1, float y1) { if (x0 > x1) CUtils.Swap(ref x0, ref x1); if (y0 > y1) CUtils.Swap(ref y0, ref y1); int bx0 = (int)(x0 / cellw); int by0 = (int)(y0 / cellh); int bx1 = (int)(x1 / cellw) + 1; int by1 = (int)(y1 / cellh) + 1; if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= terrain_map.xcount) bx1 = terrain_map.xcount - 1; if (by1 >= terrain_map.ycount) by1 = terrain_map.ycount - 1; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { MMapNode node = terrain_map.getMapNode(bx, by); if (node == null || node.Blocked) { return true; } } } return false; } /// /// /// /// /// /// /// /// public bool TouchMapLine(float x0, float y0, float x1, float y1) { int bx0 = (int)(x0 / cellw); int by0 = (int)(y0 / cellh); int bx1 = (int)(x1 / cellw); int by1 = (int)(y1 / cellh); if (bx0 > bx1) CUtils.Swap(ref bx0, ref bx1); if (by0 > by1) CUtils.Swap(ref by0, ref by1); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= terrain_map.xcount) bx1 = terrain_map.xcount - 1; if (by1 >= terrain_map.ycount) by1 = terrain_map.ycount - 1; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { MMapNode node = terrain_map.getMapNode(bx, by); if (node == null) { return true; } else if (node.TouchLine(x0, y0, x1, y1)) { return true; } } } return false; } /// /// 获得线段和地图碰撞最近的焦点 /// /// 起始点X /// 起始点Y /// 结束点X /// 结束点Y /// 接触点 /// 接触点 /// 接触距离 /// public bool GetLineTouchPoint(float x0, float y0, float x1, float y1, out float touch_x, out float touch_y, out float touch_distance) { int bx0 = (int)(x0 / cellw); int by0 = (int)(y0 / cellh); int bx1 = (int)(x1 / cellw); int by1 = (int)(y1 / cellh); if (bx0 > bx1) CUtils.Swap(ref bx0, ref bx1); if (by0 > by1) CUtils.Swap(ref by0, ref by1); if (bx0 < 0) bx0 = 0; if (by0 < 0) by0 = 0; if (bx1 >= terrain_map.xcount) bx1 = terrain_map.xcount - 1; if (by1 >= terrain_map.ycount) by1 = terrain_map.ycount - 1; bool ret = false; float t_d = 0; float t_x = 0; float t_y = 0; touch_x = 0; touch_y = 0; touch_distance = float.MaxValue; for (int by = by0; by <= by1; by++) { for (int bx = bx0; bx <= bx1; bx++) { MMapNode node = terrain_map.getMapNode(bx, by); if (node != null && node.GetLineTouchPoint(x0, y0, x1, y1, out t_x, out t_y, out t_d)) { if (t_d < touch_distance) { touch_distance = t_d; touch_x = t_x; touch_y = t_y; } ret = true; } } } return ret; } public enum TryMoveToMapBorderResult : byte { ARRIVE = 0, TOUCH = 1, BLOCK = 2, } /// /// 尝试移动坐标,如果碰撞则贴在碰撞边缘 /// /// /// /// /// /// public TryMoveToMapBorderResult TryMoveToMapBorder(ref float x, ref float y, float dx, float dy) { TryMoveToMapBorderResult result = TryMoveToMapBorderResult.ARRIVE; float oldx = x; float oldy = y; if (TouchMapBlock((int)((x + dx) / cellw), (int)((y) / cellh))) { result = TryMoveToMapBorderResult.TOUCH; } else { x += dx; } if (TouchMapBlock((int)((x) / cellw), (int)((y + dy) / cellh))) { result = TryMoveToMapBorderResult.TOUCH; } else { y += dy; } if (x == oldx && y == oldy) { result = TryMoveToMapBorderResult.BLOCK; } return result; } /// /// 找到附近可过去的随机一点 /// /// /// /// /// /// 优先四周无其他阻挡 /// public MMapNode FindNearRandomMoveableNode(System.Random random, float x, float y, float distance, bool recommend = false) { int cx1 = (int)((x - distance) / this.cellw) - 1; int cy1 = (int)((y - distance) / this.cellh) - 1; int cx2 = (int)((x + distance) / this.cellw) + 1; int cy2 = (int)((y + distance) / this.cellh) + 1; cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, this.manhattan_map.XCount - 1); cy2 = Math.Min(cy2, this.manhattan_map.YCount - 1); int x_len = cx2 - cx1 + 1; int y_len = cy2 - cy1 + 1; if (x_len > 0 && y_len > 0) { int bx = random.Next(x_len); int by = random.Next(y_len); for (int iy = 0; iy < y_len; iy++) { for (int ix = 0; ix < x_len; ix++) { var ret = terrain_map.getMapNode( cx1 + CMath.cycNum(bx, ix, x_len), cy1 + CMath.cycNum(by, iy, y_len)); if (ret != null && !ret.Blocked) { if (recommend) { return ToRecommend(ret, cx1, cy1, cx2, cy2); } else { return ret; } } } } } return null; } /// /// 找到附近可过去的随机一点 /// /// /// /// /// /// 优先四周无其他阻挡 /// public MMapNode FindNearRandomMoveableNode(System.Random random, float x, float y, float minDis, float maxDis, bool recommend = false) { int cx1 = (int)((x - maxDis) / this.cellw) - 1; int cy1 = (int)((y - maxDis) / this.cellh) - 1; int cx2 = (int)((x + maxDis) / this.cellw) + 1; int cy2 = (int)((y + maxDis) / this.cellh) + 1; cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, this.manhattan_map.XCount - 1); cy2 = Math.Min(cy2, this.manhattan_map.YCount - 1); int x_len = cx2 - cx1 + 1; int y_len = cy2 - cy1 + 1; int mx1 = (int)((x - minDis) / this.cellw) - 1; int my1 = (int)((y - minDis) / this.cellh) - 1; int mx2 = (int)((x + minDis) / this.cellw) + 1; int my2 = (int)((y + minDis) / this.cellh) + 1; mx1 = Math.Max(mx1, 0); my1 = Math.Max(my1, 0); mx2 = Math.Min(mx2, this.manhattan_map.XCount - 1); my2 = Math.Min(my2, this.manhattan_map.YCount - 1); int mx_len = mx2 - mx1 + 1; int my_len = my2 - my1 + 1; if (x_len > 0 && y_len > 0) { int bx = random.Next(x_len); int by = random.Next(y_len); int tempX = 0, tempY = 0; for (int iy = 0; iy < y_len; iy++) { for (int ix = 0; ix < x_len; ix++) { tempX = cx1 + CMath.cycNum(bx, ix, x_len); tempY = cy1 + CMath.cycNum(by, iy, y_len); if (mx1 < tempX && tempX < mx2 && my1 < tempY && tempY < my2) continue; var ret = terrain_map.getMapNode(tempX, tempY); if (ret != null && !ret.Blocked) { if (recommend) { ret = ToRecommend(ret, cx1, cy1, cx2, cy2); } //if(CMath.includeRectPoint(cx1, cy1, cx2, cy2, ret.PosX, ret.PosY) // && !CMath.includeRectPoint(mx1, my1, mx2, my2, ret.PosX, ret.PosY)) //{ //} //else //{ // System.Console.WriteLine("验证失败"); //} return ret; } } } } return null; } /// /// 指定范围可过去的一点. /// /// /// /// /// /// public MMapNode FindNearMoveableNode(float x, float y, float distance, bool recommend = false) { int cx1 = (int)((x - distance) / this.cellw) - 1; int cy1 = (int)((y - distance) / this.cellh) - 1; int cx2 = (int)((x + distance) / this.cellw) + 1; int cy2 = (int)((y + distance) / this.cellh) + 1; cx1 = Math.Max(cx1, 0); cy1 = Math.Max(cy1, 0); cx2 = Math.Min(cx2, this.manhattan_map.XCount - 1); cy2 = Math.Min(cy2, this.manhattan_map.YCount - 1); int x_len = cx2 - cx1 + 1; int y_len = cy2 - cy1 + 1; if (x_len > 0 && y_len > 0) { int bx = (x_len); int by = (y_len); for (int iy = 0; iy < y_len; iy++) { for (int ix = 0; ix < x_len; ix++) { var ret = terrain_map.getMapNode( cx1 + CMath.cycNum(bx, ix, x_len), cy1 + CMath.cycNum(by, iy, y_len)); if (ret != null && !ret.Blocked) { if (recommend) { return ToRecommend(ret, cx1, cy1, cx2, cy2); } else { return ret; } } } } } return null; } /// /// 找到离中心最近的可移动点 /// /// /// /// /// /// 优先四周无其他阻挡 /// public MMapNode FindNearMoveableNodeByBlock(int sx, int sy, int sw, int sh, bool recommend = true) { int count = Math.Max(sw / 2 + 1, sh / 2 + 1); int dx = sx + sw - 1; int dy = sy + sh - 1; int cx = sx + sw / 2; int cy = sy + sh / 2; for (int r = 0; r <= count; r++) { int cx1 = cx - r; int cy1 = cy - r; int cx2 = cx + r; int cy2 = cy + r; for (int ix = cx1; ix <= cx2; ix++) { if ((ix >= sx) && (ix <= dx)) { for (int iy = cy1; iy < cy2; iy++) { if ((iy >= sy) && (iy <= dy)) { var cn = GetMapNode(ix, iy); if (cn != null && !cn.Blocked) { if (recommend) { return ToRecommend(cn, sx, sy, dx, dy); } else { return cn; } } } } } } } return null; } private MMapNode ToRecommend(MMapNode src, int min_x, int min_y, int max_x, int max_y) { if (src.IsAllCross) return src; foreach (int[] offset in near_table_inclined) { int ix = src.BX + offset[0]; int iy = src.BY + offset[1]; if (ix >= min_x && iy >= min_y && ix <= max_x && iy <= max_y) { var cn = GetMapNode(ix, iy); if (cn != null && cn.IsAllCross) { return cn; } } } return src; } /// /// 检测范围内有无期望的地块 /// /// /// /// /// /// /// private bool CheckNearExpectByBlock(int sx, int sy, int sw, int sh, bool expect_block) { int cx1 = Math.Max(sx, 0); int cy1 = Math.Max(sy, 0); int cx2 = Math.Min(sx + sw, this.manhattan_map.XCount - 1); int cy2 = Math.Min(sy + sh, this.manhattan_map.YCount - 1); for (int cx = cx1; cx <= cx2; ++cx) { for (int cy = cy1; cy <= cy2; ++cy) { var cn = terrain_map.getMapNode(cx, cy); if (cn == null) { if (expect_block == false) return false; } else if (cn.Blocked != expect_block) { return false; } } } return true; } /// /// 检测范围内希望的地块数量 /// /// /// /// /// /// /// private int GetNearExpectCountByBlock(int sx, int sy, int sw, int sh, bool expect_block) { int ret = 0; int cx1 = Math.Max(sx, 0); int cy1 = Math.Max(sy, 0); int cx2 = Math.Min(sx + sw, this.manhattan_map.XCount); int cy2 = Math.Min(sy + sh, this.manhattan_map.YCount); for (int cx = cx1; cx < cx2; ++cx) { for (int cy = cy1; cy < cy2; ++cy) { var cn = terrain_map.getMapNode(cx, cy); if (cn == null) { if (expect_block) ret++; } else if (cn.Blocked == expect_block) { ret++; } } } return ret; } #endregion //--------------------------------------------------------------------------------------------------------------------- #region _常量_ private static int[][] near_table_inclined = new int[][] { new int[]{ 0,-1}, // top new int[]{-1, 0}, // left new int[]{ 1, 0}, // right new int[]{ 0, 1}, // bottom new int[]{-1,-1}, // top left new int[]{ 1,-1}, // top right new int[]{-1, 1}, // bottom left new int[]{ 1, 1}, // bottom right }; private static int[][] near_table_cross = new int[][] { new int[]{ 0,-1}, // top new int[]{-1, 0}, // left new int[]{ 1, 0}, // right new int[]{ 0, 1}, // bottom }; public static int[][] GetNearTables(bool inclined) { int[][] near_table = inclined ? near_table_inclined : near_table_cross; return near_table; } class DirtyNodes { private HashMap map = new HashMap(); public void AddDirty(T obj) { map.Put(obj, obj); } public void Clear() { map.Clear(); } public int Count { get { return map.Count; } } public ICollection Values { get { return map.Values; } } } #endregion //------------------------------------------------------------------------------------------------------------------ } }