private static void GenerateCollisionShapes(TileEdgeMap edgeMap, Vector2 origin, Vector2 tileSize, bool roundedCorners, IList <ShapeInfo> shapeList) { // Traverse the edge map and gradually create chain / loop // shapes until all edges have been used. RawList <Point2> currentChain = new RawList <Point2>(); RawList <Vector2> vertexBuffer = new RawList <Vector2>(); while (true) { // Begin a new continuous chain of nodes currentChain.Clear(); // Find a starting node for our current chain. // If there is none, we found and handled all edges. Point2 start = edgeMap.FindNonEmpty(); if (start == new Point2(-1, -1)) { break; } // Traverse the current chain node-by-node from the start we found Point2 current = start; while (true) { // Add the current node to our continuous chain currentChain.Add(current); // Find the next node that connects to the current one. // If there is none, our current chain is done. Point2 next = edgeMap.GetClockwiseNextFrom(current); if (next == new Point2(-1, -1)) { break; } // Remove the edge we used to get to the next node edgeMap.RemoveEdge(current, next); // Use the next node as origin for traversing further current = next; } // Generate a shape from the current chain bool isLoop = (start == currentChain[currentChain.Count - 1]); if (isLoop) { currentChain.RemoveAt(currentChain.Count - 1); } vertexBuffer.Clear(); // Rounded corners if (roundedCorners && currentChain.Count >= 3) { vertexBuffer.Reserve(currentChain.Count * 2); vertexBuffer.Count = 0; for (int i = 0; i < currentChain.Count; i++) { int prevIndex = (i - 1 + currentChain.Count) % currentChain.Count; int nextIndex = (i + 1) % currentChain.Count; Vector2 currentVert = origin + tileSize * (Vector2)currentChain[i]; Vector2 prevVert = origin + tileSize * (Vector2)currentChain[prevIndex]; Vector2 nextVert = origin + tileSize * (Vector2)currentChain[nextIndex]; if (nextVert - currentVert != currentVert - prevVert) { if (!isLoop && (i == 0 || i == currentChain.Count - 1)) { vertexBuffer.Add(currentVert); } else { vertexBuffer.Add(currentVert + (prevVert - currentVert).Normalized * tileSize * 0.2f); vertexBuffer.Add(currentVert + (nextVert - currentVert).Normalized * tileSize * 0.2f); } } } } // Sharp corners else { vertexBuffer.Reserve(currentChain.Count); vertexBuffer.Count = 0; for (int i = 0; i < currentChain.Count; i++) { int prevIndex = (i - 1 + currentChain.Count) % currentChain.Count; int nextIndex = (i + 1) % currentChain.Count; Vector2 currentVert = origin + tileSize * (Vector2)currentChain[i]; Vector2 prevVert = origin + tileSize * (Vector2)currentChain[prevIndex]; Vector2 nextVert = origin + tileSize * (Vector2)currentChain[nextIndex]; if (nextVert - currentVert != currentVert - prevVert) { vertexBuffer.Add(currentVert); } } } Vector2[] vertices = new Vector2[vertexBuffer.Count]; vertexBuffer.CopyTo(vertices, 0); shapeList.Add(isLoop ? (ShapeInfo) new LoopShapeInfo(vertices) : (ShapeInfo) new ChainShapeInfo(vertices)); } }
private static void AddBlockCollisionEdges(Grid <TileCollisionShape> collisionData, TileEdgeMap targetEdgeMap, Point2 edgeMapPos, Point2 totalSize) { int leftBorderPos = 0 - edgeMapPos.X; int rightBorderPos = totalSize.X - edgeMapPos.X; int topBorderPos = 0 - edgeMapPos.Y; int bottomBorderPos = totalSize.Y - edgeMapPos.Y; // Add block geometry to the specified edge map for (int y = 0; y < SectorSize; y++) { for (int x = 0; x < SectorSize; x++) { // Skip non-solid blocks bool center = (collisionData[x, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; if (!center) { continue; } // A filled block will always overwrite its inner diagonal edges targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y)); // Determine block collision neighbourhood bool left = (x == 0) ? (x == leftBorderPos) : (collisionData[x - 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool right = (x == SectorSize - 1) ? (x == rightBorderPos) : (collisionData[x + 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool top = (y == 0) ? (y == topBorderPos) : (collisionData[x, y - 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool bottom = (y == SectorSize - 1) ? (y == bottomBorderPos) : (collisionData[x, y + 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; // Adjust outer edge states if (center != left) { targetEdgeMap.AddEdge(new Point2(x, y), new Point2(x, y + 1)); } else { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); } if (center != right) { targetEdgeMap.AddEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } else { targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } if (center != top) { targetEdgeMap.AddEdge(new Point2(x, y), new Point2(x + 1, y)); } else { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); } if (center != bottom) { targetEdgeMap.AddEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); } else { targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); } } } // Detect diagonal fences next to solid blocks and remove the // edges that might have become redundant. This can't be done // in the above loop without complicating control flow, so it's // done here. for (int y = 0; y < SectorSize; y++) { for (int x = 0; x < SectorSize; x++) { TileCollisionShape centerShape = collisionData[x, y]; bool diagonalDown = (centerShape & TileCollisionShape.DiagonalDown) == TileCollisionShape.DiagonalDown; bool diagonalUp = (centerShape & TileCollisionShape.DiagonalUp) == TileCollisionShape.DiagonalUp; // Skip tiles that aren't diagonal fences if (!diagonalDown && !diagonalUp) { continue; } // Determine block collision neighbourhood bool left = (x == 0) ? (x == leftBorderPos) : (collisionData[x - 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool right = (x == SectorSize - 1) ? (x == rightBorderPos) : (collisionData[x + 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool top = (y == 0) ? (y == topBorderPos) : (collisionData[x, y - 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool bottom = (y == SectorSize - 1) ? (y == bottomBorderPos) : (collisionData[x, y + 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; // Remove perpendicular edges that are redundant because of the diagonal fence // connecting two adjacent solid blocks. if (diagonalDown) { if (top && right) { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } if (bottom && left) { targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); } } else { if (top && left) { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); } if (bottom && right) { targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } } } } }
private static void GenerateCollisionShapes(TileEdgeMap edgeMap, Vector2 origin, Vector2 tileSize, bool roundedCorners, IList<ShapeInfo> shapeList) { // Traverse the edge map and gradually create chain / loop // shapes until all edges have been used. RawList<Point2> currentChain = new RawList<Point2>(); RawList<Vector2> vertexBuffer = new RawList<Vector2>(); while (true) { // Begin a new continuous chain of nodes currentChain.Clear(); // Find a starting node for our current chain. // If there is none, we found and handled all edges. Point2 start = edgeMap.FindNonEmpty(); if (start == new Point2(-1, -1)) break; // Traverse the current chain node-by-node from the start we found Point2 current = start; while (true) { // Add the current node to our continuous chain currentChain.Add(current); // Find the next node that connects to the current one. // If there is none, our current chain is done. Point2 next = edgeMap.GetClockwiseNextFrom(current); if (next == new Point2(-1, -1)) break; // Remove the edge we used to get to the next node edgeMap.RemoveEdge(current, next); // Use the next node as origin for traversing further current = next; } // Generate a shape from the current chain bool isLoop = (start == currentChain[currentChain.Count - 1]); if (isLoop) currentChain.RemoveAt(currentChain.Count - 1); vertexBuffer.Clear(); // Rounded corners if (roundedCorners && currentChain.Count >= 3) { vertexBuffer.Reserve(currentChain.Count * 2); vertexBuffer.Count = 0; for (int i = 0; i < currentChain.Count; i++) { int prevIndex = (i - 1 + currentChain.Count) % currentChain.Count; int nextIndex = (i + 1) % currentChain.Count; Vector2 currentVert = origin + tileSize * (Vector2)currentChain[i]; Vector2 prevVert = origin + tileSize * (Vector2)currentChain[prevIndex]; Vector2 nextVert = origin + tileSize * (Vector2)currentChain[nextIndex]; if (nextVert - currentVert != currentVert - prevVert) { if (!isLoop && (i == 0 || i == currentChain.Count - 1)) { vertexBuffer.Add(currentVert); } else { vertexBuffer.Add(currentVert + (prevVert - currentVert).Normalized * tileSize * 0.2f); vertexBuffer.Add(currentVert + (nextVert - currentVert).Normalized * tileSize * 0.2f); } } } } // Sharp corners else { vertexBuffer.Reserve(currentChain.Count); vertexBuffer.Count = 0; for (int i = 0; i < currentChain.Count; i++) { int prevIndex = (i - 1 + currentChain.Count) % currentChain.Count; int nextIndex = (i + 1) % currentChain.Count; Vector2 currentVert = origin + tileSize * (Vector2)currentChain[i]; Vector2 prevVert = origin + tileSize * (Vector2)currentChain[prevIndex]; Vector2 nextVert = origin + tileSize * (Vector2)currentChain[nextIndex]; if (nextVert - currentVert != currentVert - prevVert) vertexBuffer.Add(currentVert); } } shapeList.Add(isLoop ? (ShapeInfo)new LoopShapeInfo(vertexBuffer) : (ShapeInfo)new ChainShapeInfo(vertexBuffer)); } }
private static void GetTileAreaOutlines(IReadOnlyGrid<bool> tileArea, Vector2 tileSize, ref List<Vector2[]> outlines) { // Initialize the container we'll put our outlines into if (outlines == null) outlines = new List<Vector2[]>(); else outlines.Clear(); // Generate a data structure containing all visible edges TileEdgeMap edgeMap = new TileEdgeMap(tileArea.Width + 1, tileArea.Height + 1); for (int y = 0; y < edgeMap.Height; y++) { for (int x = 0; x < edgeMap.Width; x++) { // Determine highlight state of the four tiles around this node bool topLeft = x > 0 && y > 0 && tileArea[x - 1, y - 1]; bool topRight = x < tileArea.Width && y > 0 && tileArea[x , y - 1]; bool bottomLeft = x > 0 && y < tileArea.Height && tileArea[x - 1, y ]; bool bottomRight = x < tileArea.Width && y < tileArea.Height && tileArea[x , y ]; // Determine which edges are visible if (topLeft != topRight ) edgeMap.AddEdge(new Point2(x, y), new Point2(x , y - 1)); if (topRight != bottomRight) edgeMap.AddEdge(new Point2(x, y), new Point2(x + 1, y )); if (bottomRight != bottomLeft ) edgeMap.AddEdge(new Point2(x, y), new Point2(x , y + 1)); if (bottomLeft != topLeft ) edgeMap.AddEdge(new Point2(x, y), new Point2(x - 1, y )); } } // Traverse edges to form outlines until no more edges are left RawList<Vector2> outlineBuilder = new RawList<Vector2>(); while (true) { // Find the beginning of an outline Point2 current = edgeMap.FindNonEmpty(); if (current.X == -1 || current.Y == -1) break; // Traverse it until no more edges are left while (true) { Point2 next = edgeMap.GetClockwiseNextFrom(current); if (next.X == -1 || next.Y == -1) break; outlineBuilder.Add(next * tileSize); edgeMap.RemoveEdge(current, next); current = next; } // Close the loop by adding the first element again if (outlineBuilder.Count > 0) outlineBuilder.Add(outlineBuilder[0]); // If we have enough vertices, keep the outline for drawing Vector2[] outline = new Vector2[outlineBuilder.Count]; outlineBuilder.CopyTo(outline, 0); outlines.Add(outline); // Reset the outline builder to an empty state outlineBuilder.Clear(); } }
private static void AddBlockCollisionEdges(Grid<TileCollisionShape> collisionData, TileEdgeMap targetEdgeMap, Point2 edgeMapPos, Point2 totalSize) { int leftBorderPos = 0 - edgeMapPos.X; int rightBorderPos = totalSize.X - edgeMapPos.X; int topBorderPos = 0 - edgeMapPos.Y; int bottomBorderPos = totalSize.Y - edgeMapPos.Y; // Add block geometry to the specified edge map for (int y = 0; y < SectorSize; y++) { for (int x = 0; x < SectorSize; x++) { // Skip non-solid blocks bool center = (collisionData[x, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; if (!center) continue; // A filled block will always overwrite its inner diagonal edges targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y)); // Determine block collision neighbourhood bool left = (x == 0) ? (x == leftBorderPos ) : (collisionData[x - 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool right = (x == SectorSize - 1) ? (x == rightBorderPos ) : (collisionData[x + 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool top = (y == 0) ? (y == topBorderPos ) : (collisionData[x, y - 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool bottom = (y == SectorSize - 1) ? (y == bottomBorderPos) : (collisionData[x, y + 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; // Adjust outer edge states if (center != left ) targetEdgeMap.AddEdge (new Point2(x, y), new Point2(x, y + 1)); else targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); if (center != right) targetEdgeMap.AddEdge (new Point2(x + 1, y), new Point2(x + 1, y + 1)); else targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); if (center != top) targetEdgeMap.AddEdge (new Point2(x, y), new Point2(x + 1, y)); else targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); if (center != bottom) targetEdgeMap.AddEdge (new Point2(x, y + 1), new Point2(x + 1, y + 1)); else targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); } } // Detect diagonal fences next to solid blocks and remove the // edges that might have become redundant. This can't be done // in the above loop without complicating control flow, so it's // done here. for (int y = 0; y < SectorSize; y++) { for (int x = 0; x < SectorSize; x++) { TileCollisionShape centerShape = collisionData[x, y]; bool diagonalDown = (centerShape & TileCollisionShape.DiagonalDown) == TileCollisionShape.DiagonalDown; bool diagonalUp = (centerShape & TileCollisionShape.DiagonalUp) == TileCollisionShape.DiagonalUp; // Skip tiles that aren't diagonal fences if (!diagonalDown && !diagonalUp) continue; // Determine block collision neighbourhood bool left = (x == 0) ? (x == leftBorderPos ) : (collisionData[x - 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool right = (x == SectorSize - 1) ? (x == rightBorderPos ) : (collisionData[x + 1, y] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool top = (y == 0) ? (y == topBorderPos ) : (collisionData[x, y - 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; bool bottom = (y == SectorSize - 1) ? (y == bottomBorderPos) : (collisionData[x, y + 1] & TileCollisionShape.Solid) == TileCollisionShape.Solid; // Remove perpendicular edges that are redundant because of the diagonal fence // connecting two adjacent solid blocks. if (diagonalDown) { if (top && right) { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } if (bottom && left) { targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); } } else { if (top && left) { targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x + 1, y)); targetEdgeMap.RemoveEdge(new Point2(x, y), new Point2(x, y + 1)); } if (bottom && right) { targetEdgeMap.RemoveEdge(new Point2(x, y + 1), new Point2(x + 1, y + 1)); targetEdgeMap.RemoveEdge(new Point2(x + 1, y), new Point2(x + 1, y + 1)); } } } } }