Renamed project folders from Subsurface to Barotrauma
This commit is contained in:
@@ -0,0 +1,613 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Voronoi2;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Factories;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
static class CaveGenerator
|
||||
{
|
||||
public static List<VoronoiCell> CarveCave(List<VoronoiCell> cells, Vector2 startPoint, out List<VoronoiCell> newCells)
|
||||
{
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
float siteInterval = 400.0f;
|
||||
float siteVariance = siteInterval * 0.4f;
|
||||
|
||||
Vector4 edges = new Vector4(
|
||||
cells.Min(x => x.edges.Min(e => e.point1.X)),
|
||||
cells.Min(x => x.edges.Min(e => e.point1.Y)),
|
||||
cells.Max(x => x.edges.Max(e => e.point1.X)),
|
||||
cells.Max(x => x.edges.Max(e => e.point1.Y)));
|
||||
|
||||
edges.X -= siteInterval * 2;
|
||||
edges.Y -= siteInterval * 2;
|
||||
edges.Z += siteInterval * 2;
|
||||
edges.W += siteInterval * 2;
|
||||
|
||||
Rectangle borders = new Rectangle((int)edges.X, (int)edges.Y, (int)(edges.Z - edges.X), (int)(edges.W - edges.Y));
|
||||
|
||||
for (float x = edges.X + siteInterval; x < edges.Z - siteInterval; x += siteInterval)
|
||||
{
|
||||
for (float y = edges.Y + siteInterval; y < edges.W - siteInterval; y += siteInterval)
|
||||
{
|
||||
if (Rand.Int(5, false) == 0) continue; //skip some positions to make the cells more irregular
|
||||
|
||||
sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, false));
|
||||
}
|
||||
}
|
||||
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, edges.X, edges.Y, edges.Z, edges.W);
|
||||
|
||||
List<VoronoiCell>[,] cellGrid;
|
||||
newCells = GraphEdgesToCells(graphEdges, borders, 1000, out cellGrid);
|
||||
|
||||
foreach (VoronoiCell cell in newCells)
|
||||
{
|
||||
//if the cell is at the edge of the graph, remove it
|
||||
if (cell.edges.Any(e =>
|
||||
e.point1.X == edges.X || e.point1.X == edges.Z ||
|
||||
e.point1.Y == edges.Z || e.point1.Y == edges.W))
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
continue;
|
||||
}
|
||||
|
||||
//remove cells that aren't inside any of the original "base cells"
|
||||
if (cells.Any(c => c.IsPointInside(cell.Center))) continue;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
//mark all the cells adjacent to the removed cell as edges of the cave
|
||||
var adjacent = edge.AdjacentCell(cell);
|
||||
if (adjacent != null && adjacent.CellType != CellType.Removed) adjacent.CellType = CellType.Edge;
|
||||
}
|
||||
|
||||
cell.CellType = CellType.Removed;
|
||||
}
|
||||
|
||||
newCells.RemoveAll(newCell => newCell.CellType == CellType.Removed);
|
||||
|
||||
//start carving from the edge cell closest to the startPoint
|
||||
VoronoiCell startCell = null;
|
||||
float closestDist = 0.0f;
|
||||
foreach (VoronoiCell cell in newCells)
|
||||
{
|
||||
if (cell.CellType != CellType.Edge) continue;
|
||||
|
||||
float dist = Vector2.Distance(startPoint, cell.Center);
|
||||
if (dist < closestDist || startCell == null)
|
||||
{
|
||||
startCell = cell;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
startCell.CellType = CellType.Path;
|
||||
|
||||
List<VoronoiCell> path = new List<VoronoiCell>() {startCell};
|
||||
VoronoiCell pathCell = startCell;
|
||||
for (int i = 0; i < newCells.Count / 2; i++)
|
||||
{
|
||||
var allowedNextCells = new List<VoronoiCell>();
|
||||
foreach (GraphEdge edge in pathCell.edges)
|
||||
{
|
||||
var adjacent = edge.AdjacentCell(pathCell);
|
||||
if (adjacent == null ||
|
||||
adjacent.CellType == CellType.Removed ||
|
||||
adjacent.CellType == CellType.Edge) continue;
|
||||
|
||||
allowedNextCells.Add(adjacent);
|
||||
}
|
||||
|
||||
if (allowedNextCells.Count == 0)
|
||||
{
|
||||
if (i>5) break;
|
||||
|
||||
foreach (GraphEdge edge in pathCell.edges)
|
||||
{
|
||||
var adjacent = edge.AdjacentCell(pathCell);
|
||||
if (adjacent == null ||
|
||||
adjacent.CellType == CellType.Removed) continue;
|
||||
|
||||
allowedNextCells.Add(adjacent);
|
||||
}
|
||||
|
||||
if (allowedNextCells.Count == 0) break;
|
||||
}
|
||||
|
||||
//randomly pick one of the adjacent cells as the next cell
|
||||
pathCell = allowedNextCells[Rand.Int(allowedNextCells.Count, false)];
|
||||
|
||||
//randomly take steps further away from the startpoint to make the cave expand further
|
||||
if (Rand.Int(4, false) == 0)
|
||||
{
|
||||
float furthestDist = 0.0f;
|
||||
foreach (VoronoiCell nextCell in allowedNextCells)
|
||||
{
|
||||
float dist = Vector2.Distance(startCell.Center, nextCell.Center);
|
||||
if (dist > furthestDist || furthestDist == 0.0f)
|
||||
{
|
||||
furthestDist = dist;
|
||||
pathCell = nextCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pathCell.CellType = CellType.Path;
|
||||
path.Add(pathCell);
|
||||
}
|
||||
|
||||
//make sure the tunnel is always wider than minPathWidth
|
||||
float minPathWidth = 100.0f;
|
||||
for (int i = 0; i < path.Count; i++)
|
||||
{
|
||||
var cell = path[i];
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
if (edge.point1 == edge.point2) continue;
|
||||
if (Vector2.Distance(edge.point1, edge.point2) > minPathWidth) continue;
|
||||
|
||||
GraphEdge adjacentEdge = cell.edges.Find(e => e != edge && (e.point1 == edge.point1 || e.point2 == edge.point1));
|
||||
|
||||
var adjacentCell = adjacentEdge.AdjacentCell(cell);
|
||||
if (i>0 && (adjacentCell.CellType == CellType.Path || adjacentCell.CellType == CellType.Edge)) continue;
|
||||
|
||||
adjacentCell.CellType = CellType.Path;
|
||||
path.Add(adjacentCell);
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GraphEdgesToCells(List<GraphEdge> graphEdges, Rectangle borders, float gridCellSize, out List<VoronoiCell>[,] cellGrid)
|
||||
{
|
||||
List<VoronoiCell> cells = new List<VoronoiCell>();
|
||||
|
||||
cellGrid = new List<VoronoiCell>[(int)Math.Ceiling(borders.Width / gridCellSize), (int)Math.Ceiling(borders.Height / gridCellSize)];
|
||||
for (int x = 0; x < borders.Width / gridCellSize; x++)
|
||||
{
|
||||
for (int y = 0; y < borders.Height / gridCellSize; y++)
|
||||
{
|
||||
cellGrid[x, y] = new List<VoronoiCell>();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GraphEdge ge in graphEdges)
|
||||
{
|
||||
if (ge.point1 == ge.point2) continue;
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Site site = (i == 0) ? ge.site1 : ge.site2;
|
||||
|
||||
int x = (int)(Math.Floor((site.coord.x-borders.X) / gridCellSize));
|
||||
int y = (int)(Math.Floor((site.coord.y-borders.Y) / gridCellSize));
|
||||
|
||||
x = MathHelper.Clamp(x, 0, cellGrid.GetLength(0)-1);
|
||||
y = MathHelper.Clamp(y, 0, cellGrid.GetLength(1)-1);
|
||||
|
||||
VoronoiCell cell = cellGrid[x,y].Find(c => c.site == site);
|
||||
|
||||
if (cell == null)
|
||||
{
|
||||
cell = new VoronoiCell(site);
|
||||
cellGrid[x, y].Add(cell);
|
||||
cells.Add(cell);
|
||||
}
|
||||
|
||||
if (ge.cell1 == null)
|
||||
{
|
||||
ge.cell1 = cell;
|
||||
}
|
||||
else
|
||||
{
|
||||
ge.cell2 = cell;
|
||||
}
|
||||
cell.edges.Add(ge);
|
||||
}
|
||||
}
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
|
||||
private static Vector2 GetEdgeNormal(GraphEdge edge, VoronoiCell cell = null)
|
||||
{
|
||||
if (cell == null) cell = edge.AdjacentCell(null);
|
||||
if (cell == null) return Vector2.UnitX;
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.point1, edge.point2) == -1)
|
||||
{
|
||||
var temp = edge.point1;
|
||||
edge.point1 = edge.point2;
|
||||
edge.point2 = temp;
|
||||
}
|
||||
|
||||
Vector2 normal = Vector2.Zero;
|
||||
|
||||
normal = Vector2.Normalize(edge.point2 - edge.point1);
|
||||
Vector2 diffToCell = Vector2.Normalize(cell.Center - edge.point2);
|
||||
|
||||
normal = new Vector2(-normal.Y, normal.X);
|
||||
|
||||
if (Vector2.Dot(normal, diffToCell) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<Vector2> pathNodes, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false, Vector2? gridOffset = null)
|
||||
{
|
||||
var targetCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < pathNodes.Count; i++)
|
||||
{
|
||||
int cellIndex = FindCellIndex(pathNodes[i], cells, cellGrid, gridCellSize, 2, gridOffset);
|
||||
targetCells.Add(cells[cellIndex]);
|
||||
}
|
||||
|
||||
return GeneratePath(targetCells, cells, cellGrid, gridCellSize, limits, wanderAmount, mirror);
|
||||
}
|
||||
|
||||
|
||||
public static List<VoronoiCell> GeneratePath(
|
||||
List<VoronoiCell> targetCells, List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid,
|
||||
int gridCellSize, Rectangle limits, float wanderAmount = 0.3f, bool mirror = false)
|
||||
{
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
//how heavily the path "steers" towards the endpoint
|
||||
//lower values will cause the path to "wander" more, higher will make it head straight to the end
|
||||
wanderAmount = MathHelper.Clamp(wanderAmount, 0.0f, 1.0f);
|
||||
|
||||
List<GraphEdge> allowedEdges = new List<GraphEdge>();
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell currentCell = targetCells[0];
|
||||
currentCell.CellType = CellType.Path;
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
int currentTargetIndex = 1;
|
||||
|
||||
int iterationsLeft = cells.Count;
|
||||
|
||||
do
|
||||
{
|
||||
int edgeIndex = 0;
|
||||
|
||||
allowedEdges.Clear();
|
||||
foreach (GraphEdge edge in currentCell.edges)
|
||||
{
|
||||
if (!limits.Contains(edge.AdjacentCell(currentCell).Center)) continue;
|
||||
|
||||
allowedEdges.Add(edge);
|
||||
}
|
||||
|
||||
//steer towards target
|
||||
if (Rand.Range(0.0f, 1.0f, false) > wanderAmount || allowedEdges.Count == 0)
|
||||
{
|
||||
for (int i = 0; i < currentCell.edges.Count; i++)
|
||||
{
|
||||
if (!MathUtils.LinesIntersect(currentCell.Center, targetCells[currentTargetIndex].Center,
|
||||
currentCell.edges[i].point1, currentCell.edges[i].point2)) continue;
|
||||
edgeIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//choose random edge (ignoring ones where the adjacent cell is outside limits)
|
||||
else
|
||||
{
|
||||
|
||||
|
||||
//if (allowedEdges.Count==0)
|
||||
//{
|
||||
// edgeIndex = Rand.Int(currentCell.edges.Count, false);
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
edgeIndex = Rand.Int(allowedEdges.Count, false);
|
||||
if (mirror && edgeIndex > 0) edgeIndex = allowedEdges.Count - edgeIndex;
|
||||
edgeIndex = currentCell.edges.IndexOf(allowedEdges[edgeIndex]);
|
||||
//}
|
||||
}
|
||||
|
||||
currentCell = currentCell.edges[edgeIndex].AdjacentCell(currentCell);
|
||||
currentCell.CellType = CellType.Path;
|
||||
pathCells.Add(currentCell);
|
||||
|
||||
iterationsLeft--;
|
||||
|
||||
if (currentCell == targetCells[currentTargetIndex])
|
||||
{
|
||||
currentTargetIndex += 1;
|
||||
if (currentTargetIndex >= targetCells.Count) break;
|
||||
}
|
||||
|
||||
} while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0);
|
||||
|
||||
|
||||
Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
return pathCells;
|
||||
}
|
||||
|
||||
public static List<Body> GeneratePolygons(List<VoronoiCell> cells, out List<VertexPositionTexture> verticeList, bool setSolid=true)
|
||||
{
|
||||
verticeList = new List<VertexPositionTexture>();
|
||||
var bodies = new List<Body>();
|
||||
|
||||
List<Vector2> tempVertices = new List<Vector2>();
|
||||
List<Vector2> bodyPoints = new List<Vector2>();
|
||||
|
||||
for (int n = cells.Count - 1; n >= 0; n-- )
|
||||
{
|
||||
VoronoiCell cell = cells[n];
|
||||
|
||||
bodyPoints.Clear();
|
||||
tempVertices.Clear();
|
||||
foreach (GraphEdge ge in cell.edges)
|
||||
{
|
||||
if (Math.Abs(Vector2.Distance(ge.point1, ge.point2))<0.1f) continue;
|
||||
if (!tempVertices.Contains(ge.point1)) tempVertices.Add(ge.point1);
|
||||
if (!tempVertices.Contains(ge.point2)) tempVertices.Add(ge.point2);
|
||||
|
||||
VoronoiCell adjacentCell = ge.AdjacentCell(cell);
|
||||
//if (adjacentCell!=null && cells.Contains(adjacentCell)) continue;
|
||||
|
||||
if (setSolid) ge.isSolid = (adjacentCell == null || !cells.Contains(adjacentCell));
|
||||
|
||||
if (!bodyPoints.Contains(ge.point1)) bodyPoints.Add(ge.point1);
|
||||
if (!bodyPoints.Contains(ge.point2)) bodyPoints.Add(ge.point2);
|
||||
}
|
||||
|
||||
if (tempVertices.Count < 3 || bodyPoints.Count < 2)
|
||||
{
|
||||
cells.RemoveAt(n);
|
||||
continue;
|
||||
}
|
||||
|
||||
var triangles = MathUtils.TriangulateConvexHull(tempVertices, cell.Center);
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
foreach (Vector2 vertex in triangles[i])
|
||||
{
|
||||
//shift the coordinates around a bit to make the texture repetition less obvious
|
||||
Vector2 uvCoords = new Vector2(
|
||||
vertex.X / 2000.0f + (float)Math.Sin(vertex.X / 500.0f) * 0.15f,
|
||||
vertex.Y / 2000.0f + (float)Math.Sin(vertex.Y / 700.0f) * 0.15f);
|
||||
|
||||
verticeList.Add(new VertexPositionTexture(new Vector3(vertex, 1.0f), uvCoords));
|
||||
}
|
||||
}
|
||||
|
||||
if (bodyPoints.Count < 2) continue;
|
||||
|
||||
if (bodyPoints.Count < 3)
|
||||
{
|
||||
foreach (Vector2 vertex in tempVertices)
|
||||
{
|
||||
if (bodyPoints.Contains(vertex)) continue;
|
||||
bodyPoints.Add(vertex);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bodyPoints.Count; i++)
|
||||
{
|
||||
cell.bodyVertices.Add(bodyPoints[i]);
|
||||
bodyPoints[i] = ConvertUnits.ToSimUnits(bodyPoints[i]);
|
||||
}
|
||||
|
||||
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
|
||||
triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center));
|
||||
|
||||
Body cellBody = new Body(GameMain.World);
|
||||
|
||||
for (int i = 0; i < triangles.Count; i++)
|
||||
{
|
||||
//don't create a triangle if any of the vertices are too close to each other
|
||||
//(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties)
|
||||
if (Vector2.Distance(triangles[i][0], triangles[i][1]) < 0.05f ||
|
||||
Vector2.Distance(triangles[i][0], triangles[i][2]) < 0.05f ||
|
||||
Vector2.Distance(triangles[i][1], triangles[i][2]) < 0.05f) continue;
|
||||
|
||||
Vertices bodyVertices = new Vertices(triangles[i]);
|
||||
FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody);
|
||||
}
|
||||
|
||||
cellBody.UserData = cell;
|
||||
cellBody.SleepingAllowed = false;
|
||||
cellBody.BodyType = BodyType.Kinematic;
|
||||
cellBody.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
cell.body = cellBody;
|
||||
bodies.Add(cellBody);
|
||||
}
|
||||
|
||||
return bodies;
|
||||
}
|
||||
|
||||
public static VertexPositionTexture[] GenerateWallShapes(List<VoronoiCell> cells)
|
||||
{
|
||||
float inwardThickness = 500.0f, outWardThickness = 30.0f;
|
||||
|
||||
List<VertexPositionTexture> verticeList = new List<VertexPositionTexture>();
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
//if (cell.body == null) continue;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
if (edge.cell1 != null && edge.cell1.body == null && edge.cell1.CellType != CellType.Empty) edge.cell1 = null;
|
||||
if (edge.cell2 != null && edge.cell2.body == null && edge.cell2.CellType != CellType.Empty) edge.cell2 = null;
|
||||
|
||||
CompareCCW compare = new CompareCCW(cell.Center);
|
||||
if (compare.Compare(edge.point1, edge.point2) == -1)
|
||||
{
|
||||
var temp = edge.point1;
|
||||
edge.point1 = edge.point2;
|
||||
edge.point2 = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
//if (cell.body == null) continue;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
if (!edge.isSolid) continue;
|
||||
|
||||
GraphEdge leftEdge = cell.edges.Find(e => e != edge && (edge.point1 == e.point1 || edge.point1 == e.point2));
|
||||
GraphEdge rightEdge = cell.edges.Find(e => e != edge && (edge.point2 == e.point1 || edge.point2 == e.point2));
|
||||
|
||||
Vector2 leftNormal = Vector2.Zero, rightNormal = Vector2.Zero;
|
||||
|
||||
if (leftEdge == null)
|
||||
{
|
||||
leftNormal = GetEdgeNormal(edge, cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
leftNormal = (leftEdge.isSolid) ?
|
||||
Vector2.Normalize(GetEdgeNormal(leftEdge) + GetEdgeNormal(edge, cell)) :
|
||||
Vector2.Normalize(leftEdge.Center - edge.point1);
|
||||
}
|
||||
|
||||
|
||||
if (!MathUtils.IsValid(leftNormal))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Invalid left normal");
|
||||
#endif
|
||||
if (cell.body != null)
|
||||
{
|
||||
GameMain.World.RemoveBody(cell.body);
|
||||
cell.body = null;
|
||||
}
|
||||
leftNormal = Vector2.UnitX;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (rightEdge == null)
|
||||
{
|
||||
rightNormal = GetEdgeNormal(edge, cell);
|
||||
}
|
||||
else
|
||||
{
|
||||
rightNormal = (rightEdge.isSolid) ?
|
||||
Vector2.Normalize(GetEdgeNormal(rightEdge) + GetEdgeNormal(edge, cell)) :
|
||||
Vector2.Normalize(rightEdge.Center - edge.point2);
|
||||
}
|
||||
|
||||
if (!MathUtils.IsValid(rightNormal))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Invalid right normal");
|
||||
#endif
|
||||
if (cell.body != null)
|
||||
{
|
||||
GameMain.World.RemoveBody(cell.body);
|
||||
cell.body = null;
|
||||
}
|
||||
rightNormal = Vector2.UnitX;
|
||||
break;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Vector2[] verts = new Vector2[3];
|
||||
VertexPositionTexture[] vertPos = new VertexPositionTexture[3];
|
||||
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
verts[0] = edge.point1 - leftNormal * outWardThickness;
|
||||
verts[1] = edge.point2 - rightNormal * outWardThickness;
|
||||
verts[2] = edge.point1 + leftNormal * inwardThickness;
|
||||
|
||||
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), Vector2.Zero);
|
||||
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX);
|
||||
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(0, 0.5f));
|
||||
}
|
||||
else
|
||||
{
|
||||
verts[0] = edge.point1 + leftNormal * inwardThickness;
|
||||
verts[1] = edge.point2 - rightNormal * outWardThickness;
|
||||
verts[2] = edge.point2 + rightNormal * inwardThickness;
|
||||
|
||||
vertPos[0] = new VertexPositionTexture(new Vector3(verts[0], 0.0f), new Vector2(0.0f, 0.5f));
|
||||
vertPos[1] = new VertexPositionTexture(new Vector3(verts[1], 0.0f), Vector2.UnitX);
|
||||
vertPos[2] = new VertexPositionTexture(new Vector3(verts[2], 0.0f), new Vector2(1.0f, 0.5f));
|
||||
}
|
||||
|
||||
var comparer = new CompareCCW((verts[0] + verts[1] + verts[2]) / 3.0f);
|
||||
Array.Sort(verts, vertPos, comparer);
|
||||
|
||||
for (int j = 0; j < 3; j++)
|
||||
{
|
||||
verticeList.Add(vertPos[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return verticeList.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find the index of the cell which the point is inside
|
||||
/// (actually finds the cell whose center is closest, but it's always the correct cell assuming the point is inside the borders of the diagram)
|
||||
/// </summary>
|
||||
public static int FindCellIndex(Vector2 position,List<VoronoiCell> cells, List<VoronoiCell>[,] cellGrid, int gridCellSize, int searchDepth = 1, Vector2? offset = null)
|
||||
{
|
||||
float closestDist = 0.0f;
|
||||
VoronoiCell closestCell = null;
|
||||
|
||||
Vector2 gridOffset = offset == null ? Vector2.Zero : (Vector2)offset;
|
||||
position -= gridOffset;
|
||||
|
||||
int gridPosX = (int)Math.Floor(position.X / gridCellSize);
|
||||
int gridPosY = (int)Math.Floor(position.Y / gridCellSize);
|
||||
|
||||
for (int x = Math.Max(gridPosX - searchDepth, 0); x <= Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1); x++)
|
||||
{
|
||||
for (int y = Math.Max(gridPosY - searchDepth, 0); y <= Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1); y++)
|
||||
{
|
||||
for (int i = 0; i < cellGrid[x, y].Count; i++)
|
||||
{
|
||||
float dist = Vector2.Distance(cellGrid[x, y][i].Center, position);
|
||||
if (closestDist != 0.0f && dist > closestDist) continue;
|
||||
|
||||
closestDist = dist;
|
||||
closestCell = cellGrid[x, y][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cells.IndexOf(closestCell);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,953 @@
|
||||
using FarseerPhysics;
|
||||
using FarseerPhysics.Common;
|
||||
using FarseerPhysics.Dynamics;
|
||||
using FarseerPhysics.Factories;
|
||||
using Lidgren.Network;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Voronoi2;
|
||||
using Barotrauma.RuinGeneration;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
|
||||
class Level
|
||||
{
|
||||
public const float ShaftHeight = 1000.0f;
|
||||
|
||||
public static Level Loaded
|
||||
{
|
||||
get { return loaded; }
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum PositionType
|
||||
{
|
||||
MainPath=1, Cave=2, Ruin=4
|
||||
}
|
||||
|
||||
struct InterestingPosition
|
||||
{
|
||||
public readonly Vector2 Position;
|
||||
public readonly PositionType PositionType;
|
||||
|
||||
public InterestingPosition(Vector2 position, PositionType positionType)
|
||||
{
|
||||
Position = position;
|
||||
PositionType = positionType;
|
||||
}
|
||||
}
|
||||
|
||||
static Level loaded;
|
||||
|
||||
private LevelRenderer renderer;
|
||||
|
||||
//how close the sub has to be to start/endposition to exit
|
||||
public const float ExitDistance = 6000.0f;
|
||||
|
||||
private string seed;
|
||||
|
||||
public const int GridCellSize = 2000;
|
||||
private List<VoronoiCell>[,] cellGrid;
|
||||
|
||||
//private WrappingWall[,] wrappingWalls;
|
||||
|
||||
//private float shaftHeight;
|
||||
|
||||
//List<Body> bodies;
|
||||
private List<VoronoiCell> cells;
|
||||
|
||||
//private VertexBuffer vertexBuffer;
|
||||
|
||||
private Vector2 startPosition, endPosition;
|
||||
|
||||
private Rectangle borders;
|
||||
|
||||
private List<Body> bodies;
|
||||
|
||||
private List<InterestingPosition> positionsOfInterest;
|
||||
|
||||
private List<Ruin> ruins;
|
||||
|
||||
private Color backgroundColor;
|
||||
|
||||
private LevelGenerationParams generationParams;
|
||||
|
||||
public Vector2 StartPosition
|
||||
{
|
||||
get { return startPosition; }
|
||||
}
|
||||
|
||||
public Vector2 Size
|
||||
{
|
||||
get { return new Vector2(borders.Width, borders.Height); }
|
||||
}
|
||||
|
||||
public Vector2 EndPosition
|
||||
{
|
||||
get { return endPosition; }
|
||||
}
|
||||
|
||||
public List<Ruin> Ruins
|
||||
{
|
||||
get { return ruins; }
|
||||
}
|
||||
|
||||
//public WrappingWall[,] WrappingWalls
|
||||
//{
|
||||
// get { return wrappingWalls; }
|
||||
//}
|
||||
|
||||
public string Seed
|
||||
{
|
||||
get { return seed; }
|
||||
}
|
||||
|
||||
public float Difficulty
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Body ShaftBody
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public LevelGenerationParams GenerationParams
|
||||
{
|
||||
get { return generationParams; }
|
||||
}
|
||||
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get { return backgroundColor; }
|
||||
}
|
||||
|
||||
public Level(string seed, float difficulty, LevelGenerationParams generationParams)
|
||||
{
|
||||
this.seed = seed;
|
||||
|
||||
this.Difficulty = difficulty;
|
||||
|
||||
this.generationParams = generationParams;
|
||||
|
||||
borders = new Rectangle(0, 0, (int)generationParams.Width, (int)generationParams.Height);
|
||||
}
|
||||
|
||||
public static Level CreateRandom(LocationConnection locationConnection)
|
||||
{
|
||||
string seed = locationConnection.Locations[0].Name + locationConnection.Locations[1].Name;
|
||||
|
||||
return new Level(seed, locationConnection.Difficulty, LevelGenerationParams.GetRandom(seed));
|
||||
}
|
||||
|
||||
public static Level CreateRandom(string seed = "")
|
||||
{
|
||||
if (seed == "")
|
||||
{
|
||||
seed = Rand.Range(0, int.MaxValue, false).ToString();
|
||||
}
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
return new Level(seed, Rand.Range(30.0f, 80.0f, false), LevelGenerationParams.GetRandom(seed));
|
||||
}
|
||||
|
||||
public void Generate(bool mirror = false)
|
||||
{
|
||||
Stopwatch sw = new Stopwatch();
|
||||
sw.Start();
|
||||
|
||||
if (loaded != null) loaded.Unload();
|
||||
loaded = this;
|
||||
|
||||
positionsOfInterest = new List<InterestingPosition>();
|
||||
|
||||
renderer = new LevelRenderer(this);
|
||||
|
||||
Voronoi voronoi = new Voronoi(1.0);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
|
||||
bodies = new List<Body>();
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
backgroundColor = generationParams.BackgroundColor;
|
||||
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
|
||||
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f);
|
||||
|
||||
float minWidth = 6500.0f;
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
Rectangle dockedSubBorders = Submarine.MainSub.GetDockedBorders();
|
||||
minWidth = Math.Max(minWidth, Math.Max(dockedSubBorders.Width, dockedSubBorders.Height));
|
||||
}
|
||||
|
||||
startPosition = new Vector2(
|
||||
Rand.Range(minWidth, minWidth * 2, false),
|
||||
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));
|
||||
|
||||
endPosition = new Vector2(
|
||||
borders.Width - Rand.Range(minWidth, minWidth * 2, false),
|
||||
Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, false));
|
||||
|
||||
List<Vector2> pathNodes = new List<Vector2>();
|
||||
Rectangle pathBorders = borders;// new Rectangle((int)minWidth, (int)minWidth, borders.Width - (int)minWidth * 2, borders.Height - (int)minWidth);
|
||||
pathBorders.Inflate(-minWidth*2, -minWidth*2);
|
||||
|
||||
pathNodes.Add(new Vector2(startPosition.X, borders.Height));
|
||||
|
||||
Vector2 nodeInterval = generationParams.MainPathNodeIntervalRange;
|
||||
|
||||
for (float x = startPosition.X + Rand.Range(nodeInterval.X, nodeInterval.Y, false);
|
||||
x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, false);
|
||||
x += Rand.Range(nodeInterval.X, nodeInterval.Y, false))
|
||||
{
|
||||
pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, false)));
|
||||
}
|
||||
|
||||
pathNodes.Add(new Vector2(endPosition.X, borders.Height));
|
||||
|
||||
if (pathNodes.Count <= 2)
|
||||
{
|
||||
pathNodes.Add((startPosition + endPosition) / 2);
|
||||
}
|
||||
|
||||
List<List<Vector2>> smallTunnels = new List<List<Vector2>>();
|
||||
for (int i = 0; i < generationParams.SmallTunnelCount; i++)
|
||||
{
|
||||
var tunnelStartPos = pathNodes[Rand.Range(2, pathNodes.Count - 2, false)];
|
||||
tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right);
|
||||
|
||||
float tunnelLength = Rand.Range(
|
||||
generationParams.SmallTunnelLengthRange.X,
|
||||
generationParams.SmallTunnelLengthRange.Y,
|
||||
false);
|
||||
|
||||
var tunnelNodes = MathUtils.GenerateJaggedLine(
|
||||
tunnelStartPos,
|
||||
new Vector2(tunnelStartPos.X, pathBorders.Bottom)+Rand.Vector(tunnelLength,false),
|
||||
4, 1000.0f);
|
||||
|
||||
List<Vector2> tunnel = new List<Vector2>();
|
||||
foreach (Vector2[] tunnelNode in tunnelNodes)
|
||||
{
|
||||
if (!pathBorders.Contains(tunnelNode[0])) continue;
|
||||
tunnel.Add(tunnelNode[0]);
|
||||
}
|
||||
|
||||
if (tunnel.Any()) smallTunnels.Add(tunnel);
|
||||
}
|
||||
|
||||
Vector2 siteInterval = generationParams.VoronoiSiteInterval;
|
||||
Vector2 siteVariance = generationParams.VoronoiSiteVariance;
|
||||
for (float x = siteInterval.X / 2; x < borders.Width; x += siteInterval.X)
|
||||
{
|
||||
for (float y = siteInterval.Y / 2; y < borders.Height; y += siteInterval.Y)
|
||||
{
|
||||
Vector2 site = new Vector2(
|
||||
x + Rand.Range(-siteVariance.X, siteVariance.X, false),
|
||||
y + Rand.Range(-siteVariance.Y, siteVariance.Y, false));
|
||||
|
||||
if (smallTunnels.Any(t => t.Any(node => Vector2.Distance(node, site) < siteInterval.Length())))
|
||||
{
|
||||
//add some more sites around the small tunnels to generate more small voronoi cells
|
||||
if (x < borders.Width - siteInterval.X) sites.Add(new Vector2(x, y) + Vector2.UnitX * siteInterval * 0.5f);
|
||||
if (y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.UnitY * siteInterval * 0.5f);
|
||||
if (x < borders.Width - siteInterval.X && y < borders.Height - siteInterval.Y) sites.Add(new Vector2(x, y) + Vector2.One * siteInterval * 0.5f);
|
||||
}
|
||||
|
||||
if (mirror) site.X = borders.Width - site.X;
|
||||
|
||||
sites.Add(site);
|
||||
}
|
||||
}
|
||||
|
||||
Stopwatch sw2 = new Stopwatch();
|
||||
sw2.Start();
|
||||
|
||||
List<GraphEdge> graphEdges = voronoi.MakeVoronoiGraph(sites, borders.Width, borders.Height);
|
||||
|
||||
Debug.WriteLine("MakeVoronoiGraph: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
//construct voronoi cells based on the graph edges
|
||||
cells = CaveGenerator.GraphEdgesToCells(graphEdges, borders, GridCellSize, out cellGrid);
|
||||
|
||||
Debug.WriteLine("find cells: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
List<VoronoiCell> mainPath = CaveGenerator.GeneratePath(pathNodes, cells, cellGrid, GridCellSize,
|
||||
new Rectangle(pathBorders.X, pathBorders.Y, pathBorders.Width, borders.Height), 0.5f, mirror);
|
||||
|
||||
for (int i = 2; i < mainPath.Count; i += 3)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(mainPath[i].Center, PositionType.MainPath));
|
||||
}
|
||||
|
||||
List<VoronoiCell> pathCells = new List<VoronoiCell>(mainPath);
|
||||
|
||||
EnlargeMainPath(pathCells, minWidth);
|
||||
|
||||
foreach (InterestingPosition positionOfInterest in positionsOfInterest)
|
||||
{
|
||||
WayPoint wayPoint = new WayPoint(positionOfInterest.Position, SpawnType.Enemy, null);
|
||||
wayPoint.MoveWithLevel = true;
|
||||
}
|
||||
|
||||
startPosition.X = pathCells[0].Center.X;
|
||||
|
||||
foreach (List<Vector2> tunnel in smallTunnels)
|
||||
{
|
||||
if (tunnel.Count<2) continue;
|
||||
|
||||
//find the cell which the path starts from
|
||||
int startCellIndex = CaveGenerator.FindCellIndex(tunnel[0], cells, cellGrid, GridCellSize, 1);
|
||||
if (startCellIndex < 0) continue;
|
||||
|
||||
//if it wasn't one of the cells in the main path, don't create a tunnel
|
||||
if (cells[startCellIndex].CellType != CellType.Path) continue;
|
||||
|
||||
var newPathCells = CaveGenerator.GeneratePath(tunnel, cells, cellGrid, GridCellSize, pathBorders);
|
||||
|
||||
positionsOfInterest.Add(new InterestingPosition(tunnel.Last(), PositionType.Cave));
|
||||
|
||||
if (tunnel.Count > 4) positionsOfInterest.Add(new InterestingPosition(tunnel[tunnel.Count / 2], PositionType.Cave));
|
||||
|
||||
pathCells.AddRange(newPathCells);
|
||||
}
|
||||
|
||||
Debug.WriteLine("path: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
cells = CleanCells(pathCells);
|
||||
|
||||
pathCells.AddRange(CreateBottomHoles(generationParams.BottomHoleProbability, new Rectangle(
|
||||
(int)(borders.Width * 0.2f), 0,
|
||||
(int)(borders.Width * 0.6f), (int)(borders.Height * 0.8f))));
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
if (cell.Center.Y < borders.Height / 2) continue;
|
||||
cell.edges.ForEach(e => e.OutsideLevel = true);
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in pathCells)
|
||||
{
|
||||
cell.edges.ForEach(e => e.OutsideLevel = false);
|
||||
|
||||
cell.CellType = CellType.Path;
|
||||
cells.Remove(cell);
|
||||
}
|
||||
|
||||
//generate some narrow caves
|
||||
int caveAmount = 0;// Rand.Int(3, false);
|
||||
List<VoronoiCell> usedCaveCells = new List<VoronoiCell>();
|
||||
for (int i = 0; i < caveAmount; i++)
|
||||
{
|
||||
Vector2 startPoint = Vector2.Zero;
|
||||
VoronoiCell startCell = null;
|
||||
|
||||
var caveCells = new List<VoronoiCell>();
|
||||
|
||||
int maxTries = 5, tries = 0;
|
||||
while (tries<maxTries)
|
||||
{
|
||||
startCell = cells[Rand.Int(cells.Count, false)];
|
||||
|
||||
//find an edge between the cell and the already carved path
|
||||
GraphEdge startEdge =
|
||||
startCell.edges.Find(e => pathCells.Contains(e.AdjacentCell(startCell)));
|
||||
|
||||
if (startEdge != null)
|
||||
{
|
||||
startPoint = (startEdge.point1 + startEdge.point2) / 2.0f;
|
||||
startPoint += startPoint - startCell.Center;
|
||||
|
||||
//get the cells in which the cave will be carved
|
||||
caveCells = GetCells(startCell.Center, 2);
|
||||
//remove cells that have already been "carved" out
|
||||
caveCells.RemoveAll(c => c.CellType == CellType.Path);
|
||||
|
||||
//if any of the cells have already been used as a cave, continue and find some other cells
|
||||
if (usedCaveCells.Any(c => caveCells.Contains(c))) continue;
|
||||
break;
|
||||
}
|
||||
|
||||
tries++;
|
||||
}
|
||||
|
||||
//couldn't find a place for a cave -> abort
|
||||
if (tries >= maxTries) break;
|
||||
|
||||
if (!caveCells.Any()) continue;
|
||||
|
||||
usedCaveCells.AddRange(caveCells);
|
||||
|
||||
List<VoronoiCell> caveSolidCells;
|
||||
var cavePathCells = CaveGenerator.CarveCave(caveCells, startPoint, out caveSolidCells);
|
||||
|
||||
//remove the large cells used as a "base" for the cave (they've now been replaced with smaller ones)
|
||||
caveCells.ForEach(c => cells.Remove(c));
|
||||
|
||||
cells.AddRange(caveSolidCells);
|
||||
|
||||
foreach (VoronoiCell cell in cavePathCells)
|
||||
{
|
||||
cells.Remove(cell);
|
||||
}
|
||||
|
||||
pathCells.AddRange(cavePathCells);
|
||||
|
||||
for (int j = cavePathCells.Count / 2; j < cavePathCells.Count; j += 10)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(cavePathCells[j].Center, PositionType.Cave));
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = 0; x < cellGrid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < cellGrid.GetLength(1); y++)
|
||||
{
|
||||
cellGrid[x, y].Clear();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
||||
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
||||
|
||||
if (x < 0 || y < 0 || x >= cellGrid.GetLength(0) || y >= cellGrid.GetLength(1)) continue;
|
||||
|
||||
cellGrid[x, y].Add(cell);
|
||||
}
|
||||
|
||||
ruins = new List<Ruin>();
|
||||
for (int i = 0; i<generationParams.RuinCount; i++)
|
||||
{
|
||||
GenerateRuin(mainPath);
|
||||
}
|
||||
|
||||
startPosition.Y = borders.Height;
|
||||
endPosition.Y = borders.Height;
|
||||
|
||||
List<VoronoiCell> cellsWithBody = new List<VoronoiCell>(cells);
|
||||
|
||||
List<VertexPositionTexture> bodyVertices;
|
||||
bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out bodyVertices);
|
||||
|
||||
renderer.SetBodyVertices(bodyVertices.ToArray());
|
||||
renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells));
|
||||
|
||||
renderer.PlaceSprites(generationParams.BackgroundSpriteAmount);
|
||||
|
||||
ShaftBody = BodyFactory.CreateEdge(GameMain.World,
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.X, 0)),
|
||||
ConvertUnits.ToSimUnits(new Vector2(borders.Right, 0)));
|
||||
|
||||
ShaftBody.SetTransform(ConvertUnits.ToSimUnits(new Vector2(0.0f, borders.Height)), 0.0f);
|
||||
|
||||
ShaftBody.BodyType = BodyType.Static;
|
||||
ShaftBody.CollisionCategories = Physics.CollisionLevel;
|
||||
|
||||
bodies.Add(ShaftBody);
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
edge.cell1 = null;
|
||||
edge.cell2 = null;
|
||||
edge.site1 = null;
|
||||
edge.site2 = null;
|
||||
}
|
||||
}
|
||||
|
||||
//initialize MapEntities that aren't in any sub (e.g. items inside ruins)
|
||||
MapEntity.MapLoaded(null);
|
||||
|
||||
Debug.WriteLine("Generatelevel: " + sw2.ElapsedMilliseconds + " ms");
|
||||
sw2.Restart();
|
||||
|
||||
if (mirror)
|
||||
{
|
||||
Vector2 temp = startPosition;
|
||||
startPosition = endPosition;
|
||||
endPosition = temp;
|
||||
}
|
||||
|
||||
Debug.WriteLine("**********************************************************************************");
|
||||
Debug.WriteLine("Generated a map with " + sites.Count + " sites in " + sw.ElapsedMilliseconds + " ms");
|
||||
Debug.WriteLine("Seed: "+seed);
|
||||
Debug.WriteLine("**********************************************************************************");
|
||||
}
|
||||
|
||||
|
||||
private List<VoronoiCell> CreateBottomHoles(float holeProbability, Rectangle limits)
|
||||
{
|
||||
List<VoronoiCell> toBeRemoved = new List<VoronoiCell>();
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
if (Rand.Range(0.0f, 1.0f, false) > holeProbability) continue;
|
||||
|
||||
if (!limits.Contains(cell.Center)) continue;
|
||||
|
||||
float closestDist = 0.0f;
|
||||
WayPoint closestWayPoint = null;
|
||||
foreach (WayPoint wp in WayPoint.WayPointList)
|
||||
{
|
||||
if (wp.SpawnType != SpawnType.Path) continue;
|
||||
|
||||
float dist =Math.Abs(cell.Center.X - wp.WorldPosition.X);
|
||||
if (closestWayPoint == null || dist < closestDist)
|
||||
{
|
||||
closestDist = dist;
|
||||
closestWayPoint = wp;
|
||||
}
|
||||
}
|
||||
|
||||
if (closestWayPoint.WorldPosition.Y < cell.Center.Y) continue;
|
||||
|
||||
toBeRemoved.Add(cell);
|
||||
}
|
||||
|
||||
return toBeRemoved;
|
||||
}
|
||||
|
||||
private void EnlargeMainPath(List<VoronoiCell> pathCells, float minWidth)
|
||||
{
|
||||
List<WayPoint> wayPoints = new List<WayPoint>();
|
||||
|
||||
var newWaypoint = new WayPoint(new Rectangle((int)pathCells[0].Center.X, borders.Height, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
//WayPoint prevWaypoint = newWaypoint;
|
||||
|
||||
for (int i = 0; i < pathCells.Count; i++)
|
||||
{
|
||||
////clean "loops" from the path
|
||||
//for (int n = 0; n < i; n++)
|
||||
//{
|
||||
// if (pathCells[n] != pathCells[i]) continue;
|
||||
|
||||
// pathCells.RemoveRange(n + 1, i - n);
|
||||
// break;
|
||||
//}
|
||||
//if (i >= pathCells.Count) break;
|
||||
|
||||
pathCells[i].CellType = CellType.Path;
|
||||
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[i].Center.X, (int)pathCells[i].Center.Y, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
wayPoints[wayPoints.Count-2].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
||||
|
||||
for (int n = 0; n < wayPoints.Count; n++)
|
||||
{
|
||||
if (wayPoints[n].Position != newWaypoint.Position) continue;
|
||||
|
||||
wayPoints[n].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[n]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
//prevWaypoint = newWaypoint;
|
||||
}
|
||||
|
||||
newWaypoint = new WayPoint(new Rectangle((int)pathCells[pathCells.Count - 1].Center.X, borders.Height, 10, 10), null);
|
||||
newWaypoint.MoveWithLevel = true;
|
||||
wayPoints.Add(newWaypoint);
|
||||
|
||||
wayPoints[wayPoints.Count - 2].linkedTo.Add(newWaypoint);
|
||||
newWaypoint.linkedTo.Add(wayPoints[wayPoints.Count - 2]);
|
||||
|
||||
if (minWidth > 0.0f)
|
||||
{
|
||||
List<VoronoiCell> removedCells = GetTooCloseCells(pathCells, minWidth);
|
||||
foreach (VoronoiCell removedCell in removedCells)
|
||||
{
|
||||
if (removedCell.CellType == CellType.Path) continue;
|
||||
|
||||
pathCells.Add(removedCell);
|
||||
removedCell.CellType = CellType.Path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GetTooCloseCells(List<VoronoiCell> emptyCells, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
Vector2 position = emptyCells[0].Center;
|
||||
|
||||
if (minDistance == 0.0f) return tooCloseCells;
|
||||
|
||||
float step = 100.0f;
|
||||
|
||||
int targetCellIndex = 1;
|
||||
|
||||
minDistance *= 0.5f;
|
||||
do
|
||||
{
|
||||
tooCloseCells.AddRange(GetTooCloseCells(position, minDistance));
|
||||
|
||||
position += Vector2.Normalize(emptyCells[targetCellIndex].Center - position) * step;
|
||||
|
||||
if (Vector2.Distance(emptyCells[targetCellIndex].Center, position) < step * 2.0f) targetCellIndex++;
|
||||
|
||||
} while (Vector2.Distance(position, emptyCells[emptyCells.Count - 1].Center) > step * 2.0f);
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
private List<VoronoiCell> GetTooCloseCells(Vector2 position, float minDistance)
|
||||
{
|
||||
List<VoronoiCell> tooCloseCells = new List<VoronoiCell>();
|
||||
|
||||
var closeCells = GetCells(position, 3);
|
||||
|
||||
foreach (VoronoiCell cell in closeCells)
|
||||
{
|
||||
bool tooClose = false;
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
|
||||
if (Vector2.Distance(edge.point1, position) < minDistance ||
|
||||
Vector2.Distance(edge.point2, position) < minDistance)
|
||||
{
|
||||
tooClose = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tooClose && !tooCloseCells.Contains(cell)) tooCloseCells.Add(cell);
|
||||
}
|
||||
|
||||
return tooCloseCells;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// remove all cells except those that are adjacent to the empty cells
|
||||
/// </summary>
|
||||
private List<VoronoiCell> CleanCells(List<VoronoiCell> emptyCells)
|
||||
{
|
||||
List<VoronoiCell> newCells = new List<VoronoiCell>();
|
||||
|
||||
foreach (VoronoiCell cell in emptyCells)
|
||||
{
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
VoronoiCell adjacent = edge.AdjacentCell(cell);
|
||||
if (adjacent != null && !newCells.Contains(adjacent))
|
||||
{
|
||||
newCells.Add(adjacent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newCells;
|
||||
}
|
||||
|
||||
private void GenerateRuin(List<VoronoiCell> mainPath)
|
||||
{
|
||||
|
||||
Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, false), Rand.Range(5000.0f, 8000.0f, false));
|
||||
float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f;
|
||||
|
||||
Vector2 ruinPos = cells[Rand.Int(cells.Count, false)].Center;
|
||||
|
||||
int iter = 0;
|
||||
|
||||
ruinPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y/2, ruinPos.Y);
|
||||
|
||||
while (mainPath.Any(p => Vector2.Distance(ruinPos, p.Center) < ruinRadius * 2.0f))
|
||||
{
|
||||
Vector2 weighedPathPos = ruinPos;
|
||||
iter++;
|
||||
|
||||
foreach (VoronoiCell pathCell in mainPath)
|
||||
{
|
||||
float dist = Vector2.Distance(pathCell.Center, ruinPos);
|
||||
if (dist > 10000.0f) continue;
|
||||
|
||||
Vector2 moveAmount = Vector2.Normalize(ruinPos - pathCell.Center) * 100000.0f / dist;
|
||||
|
||||
//if (weighedPathPos.Y + moveAmount.Y > borders.Bottom - ruinSize.X)
|
||||
//{
|
||||
// moveAmount.X = (Math.Abs(moveAmount.Y) + Math.Abs(moveAmount.X))*Math.Sign(moveAmount.X);
|
||||
// moveAmount.Y = 0.0f;
|
||||
//}
|
||||
|
||||
weighedPathPos += moveAmount;
|
||||
weighedPathPos.Y = Math.Min(borders.Y + borders.Height - ruinSize.Y / 2, weighedPathPos.Y);
|
||||
}
|
||||
|
||||
ruinPos = weighedPathPos;
|
||||
|
||||
if (iter > 10000) break;
|
||||
}
|
||||
|
||||
VoronoiCell closestPathCell = null;
|
||||
float closestDist = 0.0f;
|
||||
foreach (VoronoiCell pathCell in mainPath)
|
||||
{
|
||||
float dist = Vector2.Distance(pathCell.Center, ruinPos);
|
||||
if (closestPathCell == null || dist < closestDist)
|
||||
{
|
||||
closestPathCell = pathCell;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
var ruin = new Ruin(closestPathCell, cells, new Rectangle((ruinPos - ruinSize * 0.5f).ToPoint(), ruinSize.ToPoint()));
|
||||
ruins.Add(ruin);
|
||||
|
||||
ruin.RuinShapes.Sort((shape1, shape2) => shape2.DistanceFromEntrance.CompareTo(shape1.DistanceFromEntrance));
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
positionsOfInterest.Add(new InterestingPosition(ruin.RuinShapes[i].Rect.Center.ToVector2(), PositionType.Ruin));
|
||||
}
|
||||
|
||||
foreach (RuinShape ruinShape in ruin.RuinShapes)
|
||||
{
|
||||
var tooClose = GetTooCloseCells(ruinShape.Rect.Center.ToVector2(), Math.Max(ruinShape.Rect.Width, ruinShape.Rect.Height));
|
||||
|
||||
foreach (VoronoiCell cell in tooClose)
|
||||
{
|
||||
if (cell.CellType == CellType.Empty) continue;
|
||||
foreach (GraphEdge e in cell.edges)
|
||||
{
|
||||
Rectangle rect = ruinShape.Rect;
|
||||
rect.Y += rect.Height;
|
||||
if (MathUtils.GetLineRectangleIntersection(e.point1, e.point2, rect) != null)
|
||||
{
|
||||
cell.CellType = CellType.Removed;
|
||||
|
||||
int x = (int)Math.Floor(cell.Center.X / GridCellSize);
|
||||
int y = (int)Math.Floor(cell.Center.Y / GridCellSize);
|
||||
|
||||
cellGrid[x, y].Remove(cell);
|
||||
cells.Remove(cell);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 GetRandomItemPos(PositionType spawnPosType, float randomSpread, float offsetFromWall = 10.0f)
|
||||
{
|
||||
if (!positionsOfInterest.Any()) return Size*0.5f;
|
||||
|
||||
Vector2 position = Vector2.Zero;
|
||||
|
||||
offsetFromWall = ConvertUnits.ToSimUnits(offsetFromWall);
|
||||
|
||||
int tries = 0;
|
||||
do
|
||||
{
|
||||
Vector2 startPos = Level.Loaded.GetRandomInterestingPosition(true, spawnPosType, true);
|
||||
|
||||
startPos += Rand.Vector(Rand.Range(0.0f, randomSpread, false), false);
|
||||
|
||||
Vector2 endPos = startPos - Vector2.UnitY * Size.Y;
|
||||
|
||||
if (Submarine.PickBody(
|
||||
ConvertUnits.ToSimUnits(startPos),
|
||||
ConvertUnits.ToSimUnits(endPos),
|
||||
null, Physics.CollisionLevel) != null)
|
||||
{
|
||||
position = ConvertUnits.ToDisplayUnits(Submarine.LastPickedPosition) + Vector2.Normalize(startPos - endPos)*offsetFromWall;
|
||||
break;
|
||||
}
|
||||
|
||||
tries++;
|
||||
|
||||
if (tries == 10)
|
||||
{
|
||||
position = EndPosition - Vector2.UnitY * 300.0f;
|
||||
}
|
||||
|
||||
} while (tries < 10);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public Vector2 GetRandomInterestingPosition(bool useSyncedRand, PositionType positionType, bool avoidSubs)
|
||||
{
|
||||
if (!positionsOfInterest.Any()) return Size * 0.5f;
|
||||
|
||||
var matchingPositions = positionsOfInterest.FindAll(p => positionType.HasFlag(p.PositionType));
|
||||
|
||||
if (avoidSubs)
|
||||
{
|
||||
foreach (Submarine sub in Submarine.Loaded)
|
||||
{
|
||||
float minDist = Math.Max(sub.Borders.Width, sub.Borders.Height);
|
||||
matchingPositions.RemoveAll(p => Vector2.Distance(p.Position, sub.WorldPosition) < minDist);
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchingPositions.Any())
|
||||
{
|
||||
return positionsOfInterest[Rand.Int(positionsOfInterest.Count, !useSyncedRand)].Position;
|
||||
}
|
||||
|
||||
return matchingPositions[Rand.Int(matchingPositions.Count, !useSyncedRand)].Position;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
/*
|
||||
if (Submarine.MainSub != null)
|
||||
{
|
||||
WrappingWall.UpdateWallShift(Submarine.MainSub.WorldPosition, wrappingWalls);
|
||||
}*/
|
||||
|
||||
if (Hull.renderer != null)
|
||||
{
|
||||
Hull.renderer.ScrollWater((float)deltaTime);
|
||||
}
|
||||
|
||||
renderer.Update(deltaTime);
|
||||
}
|
||||
|
||||
public void DrawFront(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (renderer == null) return;
|
||||
renderer.Draw(spriteBatch);
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
foreach (InterestingPosition pos in positionsOfInterest)
|
||||
{
|
||||
Color color = Color.Yellow;
|
||||
if (pos.PositionType == PositionType.Cave)
|
||||
{
|
||||
color = Color.DarkOrange;
|
||||
}
|
||||
else if (pos.PositionType == PositionType.Ruin)
|
||||
{
|
||||
color = Color.LightGray;
|
||||
}
|
||||
|
||||
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X-15.0f, -pos.Position.Y-15.0f), new Vector2(30.0f, 30.0f), color, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundSpriteManager = null)
|
||||
{
|
||||
float brightness = MathHelper.Clamp(50.0f + (cam.Position.Y - Size.Y) / 2000.0f, 10.0f, 40.0f);
|
||||
|
||||
float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3;
|
||||
GameMain.LightManager.AmbientLight = new Color(backgroundColor * (brightness / avgValue), 1.0f);
|
||||
|
||||
graphics.Clear(backgroundColor);
|
||||
|
||||
if (renderer == null) return;
|
||||
renderer.DrawBackground(spriteBatch, cam, backgroundSpriteManager);
|
||||
}
|
||||
|
||||
public List<VoronoiCell> GetCells(Vector2 pos, int searchDepth = 2)
|
||||
{
|
||||
int gridPosX = (int)Math.Floor(pos.X / GridCellSize);
|
||||
int gridPosY = (int)Math.Floor(pos.Y / GridCellSize);
|
||||
|
||||
int startX = Math.Max(gridPosX - searchDepth, 0);
|
||||
int endX = Math.Min(gridPosX + searchDepth, cellGrid.GetLength(0) - 1);
|
||||
|
||||
int startY = Math.Max(gridPosY - searchDepth, 0);
|
||||
int endY = Math.Min(gridPosY + searchDepth, cellGrid.GetLength(1) - 1);
|
||||
|
||||
List<VoronoiCell> cells = new List<VoronoiCell>();
|
||||
|
||||
for (int x = startX; x <= endX; x++)
|
||||
{
|
||||
for (int y = startY; y <= endY; y++)
|
||||
{
|
||||
foreach (VoronoiCell cell in cellGrid[x, y])
|
||||
{
|
||||
cells.Add(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
if (wrappingWalls == null) return cells;
|
||||
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
for (int n = 0; n < 2; n++)
|
||||
{
|
||||
if (wrappingWalls[side, n] == null) continue;
|
||||
|
||||
if (Vector2.Distance(wrappingWalls[side, n].MidPos, pos) > WrappingWall.WallWidth) continue;
|
||||
|
||||
foreach (VoronoiCell cell in wrappingWalls[side, n].Cells)
|
||||
{
|
||||
cells.Add(cell);
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
return cells;
|
||||
}
|
||||
|
||||
private void Unload()
|
||||
{
|
||||
if (renderer!=null)
|
||||
{
|
||||
renderer.Dispose();
|
||||
renderer = null;
|
||||
}
|
||||
|
||||
if (ruins != null)
|
||||
{
|
||||
ruins.Clear();
|
||||
ruins = null;
|
||||
}
|
||||
|
||||
/*
|
||||
if (wrappingWalls!=null)
|
||||
{
|
||||
for (int side = 0; side < 2; side++)
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
if (wrappingWalls[side, i] != null) wrappingWalls[side, i].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
wrappingWalls = null;
|
||||
}*/
|
||||
|
||||
|
||||
cells = null;
|
||||
|
||||
if (bodies != null)
|
||||
{
|
||||
bodies.Clear();
|
||||
bodies = null;
|
||||
}
|
||||
|
||||
loaded = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LevelGenerationParams : IPropertyObject
|
||||
{
|
||||
private static List<LevelGenerationParams> presets;
|
||||
|
||||
public string Name
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private float width, height;
|
||||
|
||||
private Vector2 voronoiSiteInterval;
|
||||
//how much the sites are "scattered" on x- and y-axis
|
||||
//if Vector2.Zero, the sites will just be placed in a regular grid pattern
|
||||
private Vector2 voronoiSiteVariance;
|
||||
|
||||
//how far apart the nodes of the main path can be
|
||||
//x = min interval, y = max interval
|
||||
private Vector2 mainPathNodeIntervalRange;
|
||||
|
||||
private int smallTunnelCount;
|
||||
//x = min length, y = max length
|
||||
private Vector2 smallTunnelLengthRange;
|
||||
|
||||
//how large portion of the bottom of the level should be "carved out"
|
||||
//if 0.0f, the bottom will be completely solid (making the abyss unreachable)
|
||||
//if 1.0f, the bottom will be completely open
|
||||
private float bottomHoleProbability;
|
||||
|
||||
private int ruinCount;
|
||||
|
||||
public Color BackgroundColor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(1000, false)]
|
||||
public int BackgroundSpriteAmount
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public Dictionary<string, ObjectProperty> ObjectProperties
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
[HasDefaultValue(100000.0f, false)]
|
||||
public float Width
|
||||
{
|
||||
get { return width; }
|
||||
set { width = Math.Max(value, 2000.0f); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(50000.0f, false)]
|
||||
public float Height
|
||||
{
|
||||
get { return height; }
|
||||
set { height = Math.Max(value, 2000.0f); }
|
||||
}
|
||||
|
||||
public Vector2 VoronoiSiteInterval
|
||||
{
|
||||
get { return voronoiSiteInterval; }
|
||||
set {
|
||||
voronoiSiteInterval.X = MathHelper.Clamp(value.X, 100.0f, width/2);
|
||||
voronoiSiteInterval.Y = MathHelper.Clamp(value.Y, 100.0f, height/2);
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 VoronoiSiteVariance
|
||||
{
|
||||
get { return voronoiSiteVariance; }
|
||||
set
|
||||
{
|
||||
voronoiSiteVariance = new Vector2(
|
||||
MathHelper.Clamp(value.X, 0, voronoiSiteInterval.X),
|
||||
MathHelper.Clamp(value.Y, 0, voronoiSiteInterval.Y));
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MainPathNodeIntervalRange
|
||||
{
|
||||
get { return mainPathNodeIntervalRange; }
|
||||
set
|
||||
{
|
||||
mainPathNodeIntervalRange.X = MathHelper.Clamp(value.X, 100.0f, width / 2);
|
||||
mainPathNodeIntervalRange.Y = MathHelper.Clamp(value.Y, mainPathNodeIntervalRange.X, width / 2);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(5, false)]
|
||||
public int SmallTunnelCount
|
||||
{
|
||||
get { return smallTunnelCount; }
|
||||
set { smallTunnelCount = MathHelper.Clamp(value, 0, 100); }
|
||||
}
|
||||
|
||||
public Vector2 SmallTunnelLengthRange
|
||||
{
|
||||
get { return smallTunnelLengthRange; }
|
||||
set
|
||||
{
|
||||
smallTunnelLengthRange.X = MathHelper.Clamp(value.X, 100.0f, width);
|
||||
smallTunnelLengthRange.Y = MathHelper.Clamp(value.Y, smallTunnelLengthRange.X, width);
|
||||
}
|
||||
}
|
||||
|
||||
[HasDefaultValue(1, false)]
|
||||
public int RuinCount
|
||||
{
|
||||
get { return ruinCount; }
|
||||
set { ruinCount = MathHelper.Clamp(value, 0, 10); }
|
||||
}
|
||||
|
||||
[HasDefaultValue(0.4f, false)]
|
||||
public float BottomHoleProbability
|
||||
{
|
||||
get { return bottomHoleProbability; }
|
||||
set { bottomHoleProbability = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
public static LevelGenerationParams GetRandom(string seed)
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(seed));
|
||||
|
||||
if (presets == null || !presets.Any())
|
||||
{
|
||||
DebugConsole.ThrowError("Level generation presets not found - using default presets");
|
||||
return new LevelGenerationParams(null);
|
||||
}
|
||||
|
||||
return presets[Rand.Range(0, presets.Count, false)];
|
||||
}
|
||||
|
||||
private LevelGenerationParams(XElement element)
|
||||
{
|
||||
Name = element==null ? "default" : element.Name.ToString();
|
||||
ObjectProperties = ObjectProperty.InitProperties(this, element);
|
||||
|
||||
Vector3 colorVector = ToolBox.GetAttributeVector3(element, "BackgroundColor", new Vector3(50, 46, 20));
|
||||
BackgroundColor = new Color((int)colorVector.X, (int)colorVector.Y, (int)colorVector.Z);
|
||||
|
||||
VoronoiSiteInterval = ToolBox.GetAttributeVector2(element, "VoronoiSiteInterval", new Vector2(3000, 3000));
|
||||
|
||||
VoronoiSiteVariance = ToolBox.GetAttributeVector2(element, "VoronoiSiteVariance", new Vector2(voronoiSiteInterval.X, voronoiSiteInterval.Y) * 0.4f);
|
||||
|
||||
MainPathNodeIntervalRange = ToolBox.GetAttributeVector2(element, "MainPathNodeIntervalRange", new Vector2(5000.0f, 10000.0f));
|
||||
|
||||
SmallTunnelLengthRange = ToolBox.GetAttributeVector2(element, "SmallTunnelLengthRange", new Vector2(5000.0f, 10000.0f));
|
||||
}
|
||||
|
||||
public static void LoadPresets()
|
||||
{
|
||||
presets = new List<LevelGenerationParams>();
|
||||
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.LevelGenerationParameters);
|
||||
if (!files.Any())
|
||||
{
|
||||
files.Add("Content/Map/LevelGenerationParameters.xml");
|
||||
}
|
||||
|
||||
foreach (string file in files)
|
||||
{
|
||||
XDocument doc = ToolBox.TryLoadXml(file);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
presets.Add(new LevelGenerationParams(element));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class LevelRenderer : IDisposable
|
||||
{
|
||||
private static BasicEffect wallEdgeEffect, wallCenterEffect;
|
||||
|
||||
private static Sprite background, backgroundTop;
|
||||
private static Sprite dustParticles;
|
||||
private static Texture2D shaftTexture;
|
||||
|
||||
private static BackgroundSpriteManager backgroundSpriteManager;
|
||||
|
||||
Vector2 dustOffset;
|
||||
|
||||
private Level level;
|
||||
|
||||
private VertexBuffer wallVertices, bodyVertices;
|
||||
|
||||
//public VertexPositionTexture[] WallVertices;
|
||||
//public VertexPositionColor[] BodyVertices;
|
||||
|
||||
public LevelRenderer(Level level)
|
||||
{
|
||||
if (shaftTexture == null) shaftTexture = TextureLoader.FromFile("Content/Map/iceWall.png");
|
||||
|
||||
if (background==null)
|
||||
{
|
||||
background = new Sprite("Content/Map/background2.png", Vector2.Zero);
|
||||
backgroundTop = new Sprite("Content/Map/background.png", Vector2.Zero);
|
||||
dustParticles = new Sprite("Content/Map/dustparticles.png", Vector2.Zero);
|
||||
}
|
||||
|
||||
if (wallEdgeEffect == null)
|
||||
{
|
||||
wallEdgeEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
|
||||
{
|
||||
DiffuseColor = new Vector3(0.8f, 0.8f, 0.8f),
|
||||
VertexColorEnabled = false,
|
||||
TextureEnabled = true,
|
||||
Texture = shaftTexture
|
||||
};
|
||||
wallEdgeEffect.CurrentTechnique = wallEdgeEffect.Techniques["BasicEffect_Texture"];
|
||||
}
|
||||
|
||||
if (wallCenterEffect == null)
|
||||
{
|
||||
wallCenterEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
|
||||
{
|
||||
VertexColorEnabled = false,
|
||||
TextureEnabled = true,
|
||||
Texture = backgroundTop.Texture
|
||||
};
|
||||
wallCenterEffect.CurrentTechnique = wallCenterEffect.Techniques["BasicEffect_Texture"];
|
||||
}
|
||||
|
||||
|
||||
if (backgroundSpriteManager==null)
|
||||
{
|
||||
|
||||
var files = GameMain.SelectedPackage.GetFilesOfType(ContentType.BackgroundSpritePrefabs);
|
||||
if (files.Count > 0)
|
||||
backgroundSpriteManager = new BackgroundSpriteManager(files);
|
||||
else
|
||||
backgroundSpriteManager = new BackgroundSpriteManager("Content/BackgroundSprites/BackgroundSpritePrefabs.xml");
|
||||
}
|
||||
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void PlaceSprites(int amount)
|
||||
{
|
||||
backgroundSpriteManager.PlaceSprites(level, amount);
|
||||
}
|
||||
|
||||
public void Update(float deltaTime)
|
||||
{
|
||||
dustOffset -= Vector2.UnitY * 10.0f * deltaTime;
|
||||
|
||||
backgroundSpriteManager.Update(deltaTime);
|
||||
}
|
||||
|
||||
public void SetWallVertices(VertexPositionTexture[] vertices)
|
||||
{
|
||||
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length,BufferUsage.WriteOnly);
|
||||
wallVertices.SetData(vertices);
|
||||
}
|
||||
|
||||
public void SetBodyVertices(VertexPositionTexture[] vertices)
|
||||
{
|
||||
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
|
||||
bodyVertices.SetData(vertices);
|
||||
}
|
||||
|
||||
public void DrawBackground(SpriteBatch spriteBatch, Camera cam, BackgroundCreatureManager backgroundCreatureManager = null)
|
||||
{
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearWrap);
|
||||
|
||||
Vector2 backgroundPos = cam.WorldViewCenter;
|
||||
|
||||
backgroundPos.Y = -backgroundPos.Y;
|
||||
backgroundPos /= 20.0f;
|
||||
|
||||
if (backgroundPos.Y < 1024)
|
||||
{
|
||||
if (backgroundPos.Y < 0)
|
||||
{
|
||||
backgroundTop.SourceRect = new Rectangle((int)backgroundPos.X, (int)backgroundPos.Y, 1024, (int)Math.Min(-backgroundPos.Y, 1024));
|
||||
backgroundTop.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, Math.Min(-backgroundPos.Y, GameMain.GraphicsHeight)),
|
||||
Vector2.Zero, level.BackgroundColor);
|
||||
}
|
||||
if (backgroundPos.Y > -1024)
|
||||
{
|
||||
background.SourceRect = new Rectangle((int)backgroundPos.X, (int)Math.Max(backgroundPos.Y, 0), 1024, 1024);
|
||||
background.DrawTiled(spriteBatch,
|
||||
(backgroundPos.Y < 0) ? new Vector2(0.0f, (int)-backgroundPos.Y) : Vector2.Zero,
|
||||
new Vector2(GameMain.GraphicsWidth, (int)Math.Ceiling(1024 - backgroundPos.Y)),
|
||||
Vector2.Zero, level.BackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
spriteBatch.Begin(SpriteSortMode.BackToFront,
|
||||
BlendState.AlphaBlend,
|
||||
SamplerState.LinearWrap, DepthStencilState.Default, null, null,
|
||||
cam.Transform);
|
||||
|
||||
backgroundSpriteManager.DrawSprites(spriteBatch, cam);
|
||||
|
||||
if (backgroundCreatureManager!=null) backgroundCreatureManager.Draw(spriteBatch);
|
||||
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
float scale = 1.0f - i * 0.2f;
|
||||
|
||||
//alpha goes from 1.0 to 0.0 when scale is in the range of 0.2-0.1
|
||||
float alpha = (cam.Zoom * scale) < 0.2f ? (cam.Zoom * scale - 0.1f) * 10.0f : 1.0f;
|
||||
if (alpha <= 0.0f) continue;
|
||||
|
||||
Vector2 offset = (new Vector2(cam.WorldViewCenter.X, cam.WorldViewCenter.Y) + dustOffset) * scale;
|
||||
Vector3 origin = new Vector3(cam.WorldView.Width, cam.WorldView.Height, 0.0f) * 0.5f;
|
||||
|
||||
dustParticles.SourceRect = new Rectangle(
|
||||
(int)((offset.X - origin.X + (i * 256)) / scale),
|
||||
(int)((-offset.Y - origin.Y + (i * 256)) / scale),
|
||||
(int)((cam.WorldView.Width) / scale),
|
||||
(int)((cam.WorldView.Height) / scale));
|
||||
|
||||
spriteBatch.Draw(dustParticles.Texture,
|
||||
new Vector2(cam.WorldViewCenter.X, -cam.WorldViewCenter.Y),
|
||||
dustParticles.SourceRect, Color.White * alpha, 0.0f,
|
||||
new Vector2(cam.WorldView.Width, cam.WorldView.Height) * 0.5f / scale, scale, SpriteEffects.None, 1.0f - scale);
|
||||
}
|
||||
|
||||
spriteBatch.End();
|
||||
|
||||
RenderWalls(GameMain.Instance.GraphicsDevice, cam);
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
var cells = level.GetCells(GameMain.GameScreen.Cam.WorldViewCenter, 2);
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(cell.Center.X - 10.0f, -cell.Center.Y-10.0f), new Vector2(20.0f, 20.0f), Color.Cyan, true);
|
||||
|
||||
GUI.DrawLine(spriteBatch,
|
||||
new Vector2(cell.edges[0].point1.X, -cell.edges[0].point1.Y),
|
||||
new Vector2(cell.Center.X, -cell.Center.Y),
|
||||
Color.Blue*0.5f);
|
||||
|
||||
foreach (GraphEdge edge in cell.edges)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, new Vector2(edge.point1.X, -edge.point1.Y),
|
||||
new Vector2(edge.point2.X, -edge.point2.Y), cell.body==null ? Color.Cyan*0.5f : Color.White);
|
||||
}
|
||||
|
||||
foreach (Vector2 point in cell.bodyVertices)
|
||||
{
|
||||
GUI.DrawRectangle(spriteBatch, new Vector2(point.X, -point.Y), new Vector2(10.0f, 10.0f), Color.White, true);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Vector2 pos = new Vector2(0.0f, -level.Size.Y);
|
||||
|
||||
if (GameMain.GameScreen.Cam.WorldView.Y < -pos.Y - 1024) return;
|
||||
|
||||
pos.X = GameMain.GameScreen.Cam.WorldView.X -1024;
|
||||
|
||||
int width = (int)(Math.Ceiling(GameMain.GameScreen.Cam.WorldView.Width / 1024 + 4.0f) * 1024);
|
||||
|
||||
GUI.DrawRectangle(spriteBatch,new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)-GameMain.GameScreen.Cam.WorldView.Y, width, (int)(GameMain.GameScreen.Cam.WorldView.Y + pos.Y) - 30),Color.Black, true);
|
||||
spriteBatch.Draw(shaftTexture,
|
||||
new Rectangle((int)(MathUtils.Round(pos.X, 1024)), (int)pos.Y-1000, width, 1024),
|
||||
new Rectangle(0, 0, width, -1024),
|
||||
level.BackgroundColor, 0.0f,
|
||||
Vector2.Zero,
|
||||
SpriteEffects.None, 0.0f);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void RenderWalls(GraphicsDevice graphicsDevice, Camera cam)
|
||||
{
|
||||
if (wallVertices == null) return;
|
||||
|
||||
wallEdgeEffect.World = cam.ShaderTransform
|
||||
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 100) * 0.5f;
|
||||
wallCenterEffect.World = wallEdgeEffect.World;
|
||||
|
||||
//render the solid center of the wall cells
|
||||
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
|
||||
graphicsDevice.SetVertexBuffer(bodyVertices);
|
||||
wallCenterEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(bodyVertices.VertexCount / 3.0f));
|
||||
|
||||
//render the edges of the wall cells
|
||||
graphicsDevice.SetVertexBuffer(wallVertices);
|
||||
wallEdgeEffect.CurrentTechnique.Passes[0].Apply();
|
||||
graphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, (int)Math.Floor(wallVertices.VertexCount / 3.0f));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (wallVertices!=null) wallVertices.Dispose();
|
||||
if (bodyVertices != null) bodyVertices.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
/// <summary>
|
||||
/// nodes of a binary tree used for generating underwater "dungeons"
|
||||
/// </summary>
|
||||
class BTRoom : RuinShape
|
||||
{
|
||||
private BTRoom[] subRooms;
|
||||
|
||||
public BTRoom Parent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor Corridor
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public BTRoom[] SubRooms
|
||||
{
|
||||
get { return subRooms; }
|
||||
}
|
||||
|
||||
public BTRoom Adjacent
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public BTRoom(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
}
|
||||
|
||||
public void Split(float minDivRatio, float verticalProbability = 0.5f, int minWidth = 200)
|
||||
{
|
||||
subRooms = new BTRoom[2];
|
||||
|
||||
if (Rand.Range(0.0f, 1.0f, false) < verticalProbability &&
|
||||
rect.Width * minDivRatio >= minWidth)
|
||||
{
|
||||
SplitVertical(minDivRatio);
|
||||
}
|
||||
else
|
||||
{
|
||||
SplitHorizontal(minDivRatio);
|
||||
}
|
||||
|
||||
subRooms[0].Parent = this;
|
||||
subRooms[1].Parent = this;
|
||||
|
||||
subRooms[0].Adjacent = subRooms[1];
|
||||
subRooms[1].Adjacent = subRooms[0];
|
||||
}
|
||||
|
||||
private void SplitHorizontal(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, false);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, rect.Width, (int)(rect.Height * div)));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X, rect.Y + subRooms[0].rect.Height, rect.Width, rect.Height - subRooms[0].rect.Height));
|
||||
|
||||
}
|
||||
|
||||
private void SplitVertical(float minDivRatio)
|
||||
{
|
||||
float div = Rand.Range(minDivRatio, 1.0f - minDivRatio, false);
|
||||
subRooms[0] = new BTRoom(new Rectangle(rect.X, rect.Y, (int)(rect.Width * div), rect.Height));
|
||||
subRooms[1] = new BTRoom(new Rectangle(rect.X + subRooms[0].rect.Width, rect.Y, rect.Width - subRooms[0].rect.Width, rect.Height));
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
Walls = new List<Line>();
|
||||
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.Wall));
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
|
||||
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.Wall));
|
||||
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.Wall));
|
||||
}
|
||||
|
||||
public void Scale(Vector2 scale)
|
||||
{
|
||||
rect.Inflate((scale.X - 1.0f) * 0.5f * rect.Width, (scale.Y - 1.0f) * 0.5f * rect.Height);
|
||||
}
|
||||
|
||||
public List<BTRoom> GetLeaves()
|
||||
{
|
||||
return GetLeaves(new List<BTRoom>());
|
||||
}
|
||||
|
||||
private List<BTRoom> GetLeaves(List<BTRoom> leaves)
|
||||
{
|
||||
if (subRooms == null)
|
||||
{
|
||||
leaves.Add(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
subRooms[0].GetLeaves(leaves);
|
||||
subRooms[1].GetLeaves(leaves);
|
||||
}
|
||||
|
||||
return leaves;
|
||||
}
|
||||
|
||||
public void GenerateCorridors(int minWidth, int maxWidth, List<Corridor> corridors)
|
||||
{
|
||||
if (Adjacent != null && Corridor == null)
|
||||
{
|
||||
Corridor = new Corridor(this, Rand.Range(minWidth, maxWidth, false), corridors);
|
||||
}
|
||||
|
||||
if (subRooms != null)
|
||||
{
|
||||
subRooms[0].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
subRooms[1].GenerateCorridors(minWidth, maxWidth, corridors);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CalculateDistancesFromEntrance(BTRoom entrance, List<Corridor> corridors)
|
||||
{
|
||||
entrance.CalculateDistanceFromEntrance(1, new List<Corridor>(corridors));
|
||||
}
|
||||
|
||||
private void CalculateDistanceFromEntrance(int currentDist, List<Corridor> corridors)
|
||||
{
|
||||
if (DistanceFromEntrance == 0)
|
||||
{
|
||||
DistanceFromEntrance = currentDist;
|
||||
}
|
||||
else
|
||||
{
|
||||
DistanceFromEntrance = Math.Min(currentDist, DistanceFromEntrance);
|
||||
}
|
||||
|
||||
currentDist++;
|
||||
|
||||
for (int i = corridors.Count - 1; i >= 0; i = Math.Min(i - 1, corridors.Count - 1))
|
||||
{
|
||||
var corridor = corridors[i];
|
||||
|
||||
if (!corridor.ConnectedRooms.Contains(this)) continue;
|
||||
|
||||
corridors.RemoveAt(i);
|
||||
|
||||
corridor.ConnectedRooms[corridor.ConnectedRooms[0] == this ? 1 : 0].CalculateDistanceFromEntrance(currentDist, corridors);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,189 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
|
||||
class Corridor : RuinShape
|
||||
{
|
||||
private bool isHorizontal;
|
||||
|
||||
public Rectangle Rect
|
||||
{
|
||||
get { return rect; }
|
||||
}
|
||||
|
||||
public bool IsHorizontal
|
||||
{
|
||||
get { return isHorizontal; }
|
||||
}
|
||||
|
||||
public BTRoom[] ConnectedRooms
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Corridor(Rectangle rect)
|
||||
{
|
||||
this.rect = rect;
|
||||
|
||||
isHorizontal = rect.Width > rect.Height;
|
||||
}
|
||||
|
||||
public Corridor(BTRoom room, int width, List<Corridor> corridors)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(room.Adjacent != null);
|
||||
|
||||
ConnectedRooms = new BTRoom[2];
|
||||
ConnectedRooms[0] = room;
|
||||
ConnectedRooms[1] = room.Adjacent;
|
||||
|
||||
Rectangle room1, room2;
|
||||
|
||||
room1 = room.Rect;
|
||||
room2 = room.Adjacent.Rect;
|
||||
|
||||
isHorizontal = (room1.Right <= room2.X || room2.Right <= room1.X);
|
||||
|
||||
//use the leaves as starting points for the corridor
|
||||
if (room.SubRooms != null)
|
||||
{
|
||||
var leaves1 = room.GetLeaves();
|
||||
var leaves2 = room.Adjacent.GetLeaves();
|
||||
|
||||
var suitableLeaves = GetSuitableLeafRooms(leaves1, leaves2, width, isHorizontal);
|
||||
room1 = suitableLeaves[0].Rect;
|
||||
room2 = suitableLeaves[1].Rect;
|
||||
|
||||
ConnectedRooms[0] = suitableLeaves[0];
|
||||
ConnectedRooms[1] = suitableLeaves[1];
|
||||
}
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
int left = Math.Min(room1.Right, room2.Right);
|
||||
int right = Math.Max(room1.X, room2.X);
|
||||
|
||||
int top = Math.Max(room1.Y, room2.Y);
|
||||
int bottom = Math.Min(room1.Bottom, room2.Bottom);
|
||||
|
||||
int yPos = Rand.Range(top, bottom - width, false);
|
||||
|
||||
rect = new Rectangle(left, yPos, right - left, width);
|
||||
}
|
||||
else if (room1.Y > room2.Bottom || room2.Y > room1.Bottom)
|
||||
{
|
||||
int left = Math.Max(room1.X, room2.X);
|
||||
int right = Math.Min(room1.Right, room2.Right);
|
||||
|
||||
int top = Math.Min(room1.Bottom, room2.Bottom);
|
||||
int bottom = Math.Max(room1.Y, room2.Y);
|
||||
|
||||
int xPos = Rand.Range(left, right - width, false);
|
||||
|
||||
rect = new Rectangle(xPos, top, width, bottom - top);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("wat");
|
||||
}
|
||||
|
||||
room.Corridor = this;
|
||||
room.Adjacent.Corridor = this;
|
||||
|
||||
for (int i = corridors.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var corridor = corridors[i];
|
||||
|
||||
if (corridor.rect.Intersects(this.rect))
|
||||
{
|
||||
if (isHorizontal && corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Width < corridor.rect.Width)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
else if (!isHorizontal && !corridor.isHorizontal)
|
||||
{
|
||||
if (this.rect.Height < corridor.rect.Height)
|
||||
return;
|
||||
else
|
||||
corridors.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
corridors.Add(this);
|
||||
}
|
||||
|
||||
public override void CreateWalls()
|
||||
{
|
||||
|
||||
|
||||
Walls = new List<Line>();
|
||||
|
||||
if (IsHorizontal)
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.Right, Rect.Y), RuinStructureType.CorridorWall));
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Bottom), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
}
|
||||
else
|
||||
{
|
||||
Walls.Add(new Line(new Vector2(Rect.X, Rect.Y), new Vector2(Rect.X, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
Walls.Add(new Line(new Vector2(Rect.Right, Rect.Y), new Vector2(Rect.Right, Rect.Bottom), RuinStructureType.CorridorWall));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// find two rooms which have two face-two-face walls that we can place a corridor in between
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private BTRoom[] GetSuitableLeafRooms(List<BTRoom> leaves1, List<BTRoom> leaves2, int width, bool isHorizontal)
|
||||
{
|
||||
int iOffset = Rand.Int(leaves1.Count, false);
|
||||
int jOffset = Rand.Int(leaves2.Count, false);
|
||||
|
||||
|
||||
for (int iCount = 0; iCount < leaves1.Count; iCount++)
|
||||
{
|
||||
int i = (iCount + iOffset) % leaves1.Count;
|
||||
|
||||
for (int jCount = 0; jCount < leaves2.Count; jCount++)
|
||||
{
|
||||
int j = (jCount + jOffset) % leaves2.Count;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
//if (Math.Min(leaves1[i].Rect.Bottom, leaves2[i].Rect.Bottom) - Math.Max(leaves1[i].Rect.Y, leaves2[j].Rect.Y) < width) continue;
|
||||
|
||||
|
||||
if (leaves1[i].Rect.Y > leaves2[j].Rect.Bottom-width) continue;
|
||||
if (leaves1[i].Rect.Bottom < leaves2[j].Rect.Y+width) continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (Math.Min(leaves1[i].Rect.Right, leaves2[i].Rect.Right) - Math.Max(leaves1[i].Rect.X, leaves2[j].Rect.X) < width) continue;
|
||||
|
||||
|
||||
if (leaves1[i].Rect.X > leaves2[j].Rect.Right-width) continue;
|
||||
if (leaves1[i].Rect.Right < leaves2[j].Rect.X+width) continue;
|
||||
}
|
||||
|
||||
|
||||
return new BTRoom[] { leaves1[i], leaves2[j] };
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
abstract class RuinShape
|
||||
{
|
||||
protected Rectangle rect;
|
||||
|
||||
public Rectangle Rect
|
||||
{
|
||||
get { return rect; }
|
||||
}
|
||||
|
||||
|
||||
public int DistanceFromEntrance
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return rect.Center.ToVector2(); }
|
||||
}
|
||||
|
||||
public List<Line> Walls;
|
||||
|
||||
public virtual void CreateWalls() { }
|
||||
|
||||
public Alignment GetLineAlignment(Line line)
|
||||
{
|
||||
if (line.A.Y == line.B.Y)
|
||||
{
|
||||
if (line.A.Y > rect.Center.Y && line.B.Y > rect.Center.Y)
|
||||
{
|
||||
return Alignment.Bottom;
|
||||
}
|
||||
else if (line.A.Y < rect.Center.Y && line.B.Y < rect.Center.Y)
|
||||
{
|
||||
return Alignment.Top;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (line.A.X < rect.Center.X && line.B.X < rect.Center.X)
|
||||
{
|
||||
return Alignment.Left;
|
||||
}
|
||||
else if (line.A.X > rect.Center.X && line.B.X > rect.Center.X)
|
||||
{
|
||||
return Alignment.Right;
|
||||
}
|
||||
}
|
||||
|
||||
return Alignment.Center;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Goes through a list of line segments and "clips off" all parts of the lines that are inside the rectangle
|
||||
/// </summary>
|
||||
public void SplitLines(Rectangle rectangle)
|
||||
{
|
||||
List<Line> newLines = new List<Line>();
|
||||
|
||||
foreach (Line line in Walls)
|
||||
{
|
||||
if (line.A.X == line.B.X) //vertical line
|
||||
{
|
||||
//line doesn't intersect the rectangle
|
||||
if (rectangle.X > line.A.X || rectangle.Right < line.A.X ||
|
||||
rectangle.Y > line.B.Y || rectangle.Bottom < line.A.Y)
|
||||
{
|
||||
newLines.Add(line);
|
||||
}
|
||||
else if (line.A.Y > rectangle.Y && line.B.Y < rectangle.Bottom)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//point A is within the rectangle -> cut a portion from the top of the line
|
||||
else if (line.A.Y >= rectangle.Y && line.A.Y <= rectangle.Bottom)
|
||||
{
|
||||
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
|
||||
}
|
||||
//point B is within the rectangle -> cut a portion from the bottom of the line
|
||||
else if (line.B.Y >= rectangle.Y && line.B.Y <= rectangle.Bottom)
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
|
||||
}
|
||||
//rect is in between the lines -> split the line into two
|
||||
else
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(line.A.X, rectangle.Y), line.Type));
|
||||
newLines.Add(new Line(new Vector2(line.A.X, rectangle.Bottom), line.B, line.Type));
|
||||
}
|
||||
}
|
||||
else if (line.A.Y == line.B.Y) //horizontal line
|
||||
{
|
||||
//line doesn't intersect the rectangle
|
||||
if (rectangle.X > line.B.X || rectangle.Right < line.A.X ||
|
||||
rectangle.Y > line.A.Y || rectangle.Bottom < line.A.Y)
|
||||
{
|
||||
|
||||
newLines.Add(line);
|
||||
}
|
||||
else if (line.A.X > rectangle.X && line.B.X < rectangle.Right)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
//point A is within the rectangle -> cut a portion from the left side of the line
|
||||
else if (line.A.X >= rectangle.X && line.A.X <= rectangle.Right)
|
||||
{
|
||||
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
|
||||
}
|
||||
//point B is within the rectangle -> cut a portion from the right side of the line
|
||||
else if (line.B.X >= rectangle.X && line.B.X <= rectangle.Right)
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
|
||||
}
|
||||
//rect is in between the lines -> split the line into two
|
||||
else
|
||||
{
|
||||
newLines.Add(new Line(line.A, new Vector2(rectangle.X, line.A.Y), line.Type));
|
||||
newLines.Add(new Line(new Vector2(rectangle.Right, line.A.Y), line.B, line.Type));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error in StructureGenerator.SplitLines - lines must be axis aligned");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Walls = newLines;
|
||||
}
|
||||
}
|
||||
|
||||
struct Line
|
||||
{
|
||||
public readonly Vector2 A, B;
|
||||
|
||||
public readonly RuinStructureType Type;
|
||||
|
||||
public Line(Vector2 a, Vector2 b, RuinStructureType type)
|
||||
{
|
||||
Debug.Assert(a.X <= b.X);
|
||||
Debug.Assert(a.Y <= b.Y);
|
||||
|
||||
A = a;
|
||||
B = b;
|
||||
Type = type;
|
||||
}
|
||||
}
|
||||
|
||||
class Ruin
|
||||
{
|
||||
private List<BTRoom> rooms;
|
||||
private List<Corridor> corridors;
|
||||
|
||||
private List<Line> walls;
|
||||
|
||||
private List<RuinShape> allShapes;
|
||||
|
||||
public List<RuinShape> RuinShapes
|
||||
{
|
||||
get { return allShapes; }
|
||||
}
|
||||
|
||||
public List<Line> Walls
|
||||
{
|
||||
get { return walls; }
|
||||
}
|
||||
|
||||
public Rectangle Area
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Ruin(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
|
||||
{
|
||||
Area = area;
|
||||
|
||||
corridors = new List<Corridor>();
|
||||
rooms = new List<BTRoom>();
|
||||
|
||||
walls = new List<Line>();
|
||||
|
||||
allShapes = new List<RuinShape>();
|
||||
|
||||
Generate(closestPathCell, caveCells, area);
|
||||
}
|
||||
|
||||
public void Generate(VoronoiCell closestPathCell, List<VoronoiCell> caveCells, Rectangle area)
|
||||
{
|
||||
corridors.Clear();
|
||||
rooms.Clear();
|
||||
|
||||
//area = new Rectangle(area.X, area.Y - area.Height, area.Width, area.Height);
|
||||
|
||||
int iterations = Rand.Range(3, 4, false);
|
||||
|
||||
float verticalProbability = Rand.Range(0.4f, 0.6f, false);
|
||||
|
||||
BTRoom baseRoom = new BTRoom(area);
|
||||
|
||||
rooms = new List<BTRoom> { baseRoom };
|
||||
|
||||
for (int i = 0; i < iterations; i++)
|
||||
{
|
||||
rooms.ForEach(l => l.Split(0.3f, verticalProbability, 300));
|
||||
|
||||
rooms = baseRoom.GetLeaves();
|
||||
}
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
leaf.Scale
|
||||
(
|
||||
new Vector2(Rand.Range(0.5f, 0.9f, false), Rand.Range(0.5f, 0.9f, false))
|
||||
);
|
||||
}
|
||||
|
||||
baseRoom.GenerateCorridors(200, 256, corridors);
|
||||
|
||||
walls = new List<Line>();
|
||||
|
||||
rooms.ForEach(leaf =>
|
||||
{
|
||||
leaf.CreateWalls();
|
||||
//walls.AddRange(leaf.Walls);
|
||||
});
|
||||
|
||||
//---------------------------
|
||||
|
||||
BTRoom entranceRoom = null;
|
||||
float shortestDistance = 0.0f;
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
float distance = Vector2.Distance(leaf.Rect.Center.ToVector2(), closestPathCell.Center);
|
||||
if (entranceRoom == null || distance < shortestDistance)
|
||||
{
|
||||
entranceRoom = leaf;
|
||||
shortestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
rooms.Remove(entranceRoom);
|
||||
|
||||
//---------------------------
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
leaf.SplitLines(corridor.Rect);
|
||||
}
|
||||
|
||||
walls.AddRange(leaf.Walls);
|
||||
}
|
||||
|
||||
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
corridor.CreateWalls();
|
||||
|
||||
foreach (BTRoom leaf in rooms)
|
||||
{
|
||||
corridor.SplitLines(leaf.Rect);
|
||||
}
|
||||
|
||||
foreach (Corridor corridor2 in corridors)
|
||||
{
|
||||
if (corridor == corridor2) continue;
|
||||
corridor.SplitLines(corridor2.Rect);
|
||||
}
|
||||
|
||||
|
||||
walls.AddRange(corridor.Walls);
|
||||
}
|
||||
|
||||
//leaves.Remove(entranceRoom);
|
||||
|
||||
BTRoom.CalculateDistancesFromEntrance(entranceRoom, corridors);
|
||||
|
||||
allShapes = GenerateStructures(caveCells);
|
||||
}
|
||||
|
||||
private List<RuinShape> GenerateStructures(List<VoronoiCell> caveCells)
|
||||
{
|
||||
List<RuinShape> shapes = new List<RuinShape>(rooms);
|
||||
shapes.AddRange(corridors);
|
||||
|
||||
foreach (RuinShape leaf in shapes)
|
||||
{
|
||||
RuinStructureType wallType = RuinStructureType.Wall;
|
||||
|
||||
if (!(leaf is BTRoom))
|
||||
{
|
||||
wallType = RuinStructureType.CorridorWall;
|
||||
}
|
||||
//rooms further from the entrance are more likely to have hard-to-break walls
|
||||
else if (Rand.Range(0.0f, leaf.DistanceFromEntrance, false) > 1.5f)
|
||||
{
|
||||
wallType = RuinStructureType.HeavyWall;
|
||||
}
|
||||
|
||||
//generate walls --------------------------------------------------------------
|
||||
foreach (Line wall in leaf.Walls)
|
||||
{
|
||||
var structurePrefab = RuinStructure.GetRandom(wallType, leaf.GetLineAlignment(wall));
|
||||
if (structurePrefab == null) continue;
|
||||
|
||||
float radius = (wall.A.X == wall.B.X) ?
|
||||
(structurePrefab.Prefab as StructurePrefab).Size.X * 0.5f :
|
||||
(structurePrefab.Prefab as StructurePrefab).Size.Y * 0.5f;
|
||||
|
||||
Rectangle rect = new Rectangle(
|
||||
(int)(wall.A.X - radius),
|
||||
(int)(wall.B.Y + radius),
|
||||
(int)((wall.B.X - wall.A.X) + radius*2.0f),
|
||||
(int)((wall.B.Y - wall.A.Y) + radius*2.0f));
|
||||
|
||||
//cut a section off from both ends of a horizontal wall to get nicer looking corners
|
||||
if (wall.A.Y == wall.B.Y)
|
||||
{
|
||||
rect.Inflate(-32, 0);
|
||||
if (rect.Width < Submarine.GridSize.X) continue;
|
||||
}
|
||||
|
||||
var structure = new Structure(rect, structurePrefab.Prefab as StructurePrefab, null);
|
||||
structure.MoveWithLevel = true;
|
||||
structure.SetCollisionCategory(Physics.CollisionLevel);
|
||||
}
|
||||
|
||||
//generate backgrounds --------------------------------------------------------------
|
||||
var background = RuinStructure.GetRandom(RuinStructureType.Back, Alignment.Center);
|
||||
if (background == null) continue;
|
||||
|
||||
Rectangle backgroundRect = new Rectangle(leaf.Rect.X, leaf.Rect.Y + leaf.Rect.Height, leaf.Rect.Width, leaf.Rect.Height);
|
||||
|
||||
new Structure(backgroundRect, (background.Prefab as StructurePrefab), null).MoveWithLevel = true;
|
||||
|
||||
}
|
||||
|
||||
//generate props --------------------------------------------------------------
|
||||
for (int i = 0; i < shapes.Count*2; i++ )
|
||||
{
|
||||
Alignment[] alignments = new Alignment[] { Alignment.Top, Alignment.Bottom, Alignment.Right, Alignment.Left, Alignment.Center };
|
||||
|
||||
var prop = RuinStructure.GetRandom(RuinStructureType.Prop, alignments[Rand.Int(alignments.Length, false)]);
|
||||
|
||||
Vector2 size = (prop.Prefab is StructurePrefab) ? ((StructurePrefab)prop.Prefab).Size : Vector2.Zero;
|
||||
|
||||
var shape = shapes[Rand.Int(shapes.Count, false)];
|
||||
|
||||
Vector2 position = shape.Rect.Center.ToVector2();
|
||||
if (prop.Alignment.HasFlag(Alignment.Top))
|
||||
{
|
||||
position = new Vector2(Rand.Range(shape.Rect.X+size.X, shape.Rect.Right - size.X, false), shape.Rect.Bottom - 64);
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Bottom))
|
||||
{
|
||||
position = new Vector2(Rand.Range(shape.Rect.X + size.X, shape.Rect.Right - size.X, false), shape.Rect.Top + 64);
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Right))
|
||||
{
|
||||
position = new Vector2(shape.Rect.Right - 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, false));
|
||||
}
|
||||
else if (prop.Alignment.HasFlag(Alignment.Left))
|
||||
{
|
||||
position = new Vector2(shape.Rect.X + 64, Rand.Range(shape.Rect.Y + size.X, shape.Rect.Bottom - size.Y, false));
|
||||
}
|
||||
|
||||
if (prop.Prefab is ItemPrefab)
|
||||
{
|
||||
var item = new Item((ItemPrefab)prop.Prefab, position, null);
|
||||
item.MoveWithLevel = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
new Structure(new Rectangle(
|
||||
(int)(position.X - size.X/2.0f), (int)(position.Y + size.Y/2.0f),
|
||||
(int)size.X, (int)size.Y),
|
||||
prop.Prefab as StructurePrefab, null).MoveWithLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//generate doors & sensors that close them -------------------------------------------------------------
|
||||
|
||||
var sensorPrefab = ItemPrefab.list.Find(ip => ip.Name == "Alien Motion Sensor") as ItemPrefab;
|
||||
var wirePrefab = ItemPrefab.list.Find(ip => ip.Name == "Wire") as ItemPrefab;
|
||||
|
||||
foreach (Corridor corridor in corridors)
|
||||
{
|
||||
var doorPrefab = RuinStructure.GetRandom(corridor.IsHorizontal ? RuinStructureType.Door : RuinStructureType.Hatch, Alignment.Center);
|
||||
if (doorPrefab == null) continue;
|
||||
|
||||
//find all walls that are parallel to the corridor
|
||||
var suitableWalls = corridor.IsHorizontal ?
|
||||
corridor.Walls.FindAll(c => c.A.Y == c.B.Y) : corridor.Walls.FindAll(c => c.A.X == c.B.X);
|
||||
|
||||
if (!suitableWalls.Any()) continue;
|
||||
|
||||
Vector2 doorPos = corridor.Center;
|
||||
|
||||
//choose a random wall to place the door next to
|
||||
var wall = suitableWalls[Rand.Int(suitableWalls.Count, false)];
|
||||
if (corridor.IsHorizontal)
|
||||
{
|
||||
doorPos.X = (wall.A.X + wall.B.X) / 2.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
doorPos.Y = (wall.A.Y + wall.B.Y) / 2.0f;
|
||||
}
|
||||
|
||||
var door = new Item(doorPrefab.Prefab as ItemPrefab, doorPos, null);
|
||||
door.MoveWithLevel = true;
|
||||
|
||||
door.GetComponent<Items.Components.Door>().IsOpen = Rand.Range(0.0f, 1.0f, false) < 0.8f;
|
||||
|
||||
if (sensorPrefab == null || wirePrefab == null) continue;
|
||||
|
||||
var sensorRoom = corridor.ConnectedRooms.FirstOrDefault(r => r != null && rooms.Contains(r));
|
||||
if (sensorRoom == null) continue;
|
||||
|
||||
var sensor = new Item(sensorPrefab, new Vector2(
|
||||
Rand.Range(sensorRoom.Rect.X, sensorRoom.Rect.Right, false),
|
||||
Rand.Range(sensorRoom.Rect.Y, sensorRoom.Rect.Bottom,false)), null);
|
||||
sensor.MoveWithLevel = true;
|
||||
|
||||
var wire = new Item(wirePrefab, sensorRoom.Center, null).GetComponent<Items.Components.Wire>();
|
||||
wire.Item.MoveWithLevel = false;
|
||||
|
||||
var conn1 = door.Connections.Find(c => c.Name == "set_state");
|
||||
conn1.AddLink(0, wire);
|
||||
wire.Connect(conn1, false);
|
||||
|
||||
var conn2 = sensor.Connections.Find(c => c.Name == "state_out");
|
||||
conn2.AddLink(0, wire);
|
||||
wire.Connect(conn2, false);
|
||||
}
|
||||
|
||||
return shapes;
|
||||
}
|
||||
|
||||
public void Draw(SpriteBatch spriteBatch)
|
||||
{
|
||||
//foreach (BTRoom room in leaves)
|
||||
//{
|
||||
// GUI.DrawRectangle(spriteBatch, room.Rect, Color.White);
|
||||
//}
|
||||
|
||||
//foreach (Corridor corr in corridors)
|
||||
//{
|
||||
// GUI.DrawRectangle(spriteBatch, corr.Rect, Color.Blue);
|
||||
//}
|
||||
|
||||
foreach (Line line in walls)
|
||||
{
|
||||
GUI.DrawLine(spriteBatch, new Vector2(line.A.X, -line.A.Y), new Vector2(line.B.X, -line.B.Y), Color.Red, 0.0f, 10);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.RuinGeneration
|
||||
{
|
||||
[Flags]
|
||||
enum RuinStructureType
|
||||
{
|
||||
Wall = 1, CorridorWall = 2, Prop = 4, Back = 8, Door=16, Hatch=32, HeavyWall=64
|
||||
}
|
||||
|
||||
class RuinStructure
|
||||
{
|
||||
const string ConfigFile = "Content/Map/RuinConfig.xml";
|
||||
|
||||
private static List<RuinStructure> list;
|
||||
|
||||
public readonly MapEntityPrefab Prefab;
|
||||
|
||||
public readonly Alignment Alignment;
|
||||
|
||||
public readonly RuinStructureType Type;
|
||||
|
||||
private int commonness;
|
||||
|
||||
private RuinStructure(XElement element)
|
||||
{
|
||||
string prefab = ToolBox.GetAttributeString(element, "prefab", "").ToLowerInvariant();
|
||||
Prefab = MapEntityPrefab.list.Find(s => s.Name.ToLowerInvariant() == prefab);
|
||||
|
||||
if (Prefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Loading ruin structure failed - structure prefab \""+prefab+" not found");
|
||||
return;
|
||||
}
|
||||
|
||||
string alignmentStr = ToolBox.GetAttributeString(element,"alignment","Bottom");
|
||||
if (!Enum.TryParse<Alignment>(alignmentStr, true, out Alignment))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ruin structure \""+prefab+"\" - "+alignmentStr+" is not a valid alignment");
|
||||
}
|
||||
|
||||
|
||||
string typeStr = ToolBox.GetAttributeString(element,"type","");
|
||||
if (!Enum.TryParse<RuinStructureType>(typeStr,true, out Type))
|
||||
{
|
||||
DebugConsole.ThrowError("Error in ruin structure \"" + prefab + "\" - " + typeStr + " is not a valid type");
|
||||
return;
|
||||
}
|
||||
|
||||
commonness = ToolBox.GetAttributeInt(element, "commonness", 1);
|
||||
|
||||
list.Add(this);
|
||||
}
|
||||
|
||||
private static void Load()
|
||||
{
|
||||
list = new List<RuinStructure>();
|
||||
|
||||
XDocument doc = ToolBox.TryLoadXml(ConfigFile);
|
||||
if (doc == null || doc.Root == null) return;
|
||||
|
||||
foreach (XElement element in doc.Root.Elements())
|
||||
{
|
||||
new RuinStructure(element);
|
||||
}
|
||||
}
|
||||
|
||||
public static RuinStructure GetRandom(RuinStructureType type, Alignment alignment)
|
||||
{
|
||||
if (list==null)
|
||||
{
|
||||
DebugConsole.Log("Loading ruin structures...");
|
||||
Load();
|
||||
}
|
||||
|
||||
var matchingStructures = list.FindAll(rs => rs.Type.HasFlag(type) && rs.Alignment.HasFlag(alignment));
|
||||
|
||||
if (!matchingStructures.Any()) return null;
|
||||
|
||||
int totalCommonness = matchingStructures.Sum(m => m.commonness);
|
||||
|
||||
int randomNumber = Rand.Int(totalCommonness + 1, false);
|
||||
|
||||
foreach (RuinStructure ruinStructure in matchingStructures)
|
||||
{
|
||||
if (randomNumber <= ruinStructure.commonness)
|
||||
{
|
||||
return ruinStructure;
|
||||
}
|
||||
|
||||
randomNumber -= ruinStructure.commonness;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,998 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 11:30 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
|
||||
* Bell Laboratories.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This code was originally written by Stephan Fortune in C code. I, Shane O'Sullivan,
|
||||
* have since modified it, encapsulating it in a C++ class and, fixing memory leaks and
|
||||
* adding accessors to the Voronoi Edges.
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Java Version by Zhenyu Pan
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
/// <summary>
|
||||
/// Description of Voronoi.
|
||||
/// </summary>
|
||||
public class Voronoi
|
||||
{
|
||||
// ************* Private members ******************
|
||||
double borderMinX, borderMaxX, borderMinY, borderMaxY;
|
||||
int siteidx;
|
||||
double xmin, xmax, ymin, ymax, deltax, deltay;
|
||||
int nvertices;
|
||||
int nedges;
|
||||
int nsites;
|
||||
Site[] sites;
|
||||
Site bottomsite;
|
||||
int sqrt_nsites;
|
||||
double minDistanceBetweenSites;
|
||||
int PQcount;
|
||||
int PQmin;
|
||||
int PQhashsize;
|
||||
Halfedge[] PQhash;
|
||||
|
||||
const int LE = 0;
|
||||
const int RE = 1;
|
||||
|
||||
int ELhashsize;
|
||||
Halfedge[] ELhash;
|
||||
Halfedge ELleftend, ELrightend;
|
||||
List<GraphEdge> allEdges;
|
||||
|
||||
|
||||
// ************* Public methods ******************
|
||||
// ******************************************
|
||||
|
||||
// constructor
|
||||
public Voronoi ( double minDistanceBetweenSites )
|
||||
{
|
||||
siteidx = 0;
|
||||
sites = null;
|
||||
|
||||
allEdges = null;
|
||||
this.minDistanceBetweenSites = minDistanceBetweenSites;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param xValuesIn Array of X values for each site.
|
||||
* @param yValuesIn Array of Y values for each site. Must be identical length to yValuesIn
|
||||
* @param minX The minimum X of the bounding box around the voronoi
|
||||
* @param maxX The maximum X of the bounding box around the voronoi
|
||||
* @param minY The minimum Y of the bounding box around the voronoi
|
||||
* @param maxY The maximum Y of the bounding box around the voronoi
|
||||
* @return
|
||||
*/
|
||||
// تستدعى هذه العملية لإنشاء مخطط فورونوي
|
||||
public List<GraphEdge> generateVoronoi ( double[] xValuesIn, double[] yValuesIn, double minX, double maxX, double minY, double maxY )
|
||||
{
|
||||
sort(xValuesIn, yValuesIn, xValuesIn.Length);
|
||||
|
||||
// Check bounding box inputs - if mins are bigger than maxes, swap them
|
||||
double temp = 0;
|
||||
if ( minX > maxX )
|
||||
{
|
||||
temp = minX;
|
||||
minX = maxX;
|
||||
maxX = temp;
|
||||
}
|
||||
if ( minY > maxY )
|
||||
{
|
||||
temp = minY;
|
||||
minY = maxY;
|
||||
maxY = temp;
|
||||
}
|
||||
|
||||
borderMinX = minX;
|
||||
borderMinY = minY;
|
||||
borderMaxX = maxX;
|
||||
borderMaxY = maxY;
|
||||
|
||||
siteidx = 0;
|
||||
voronoi_bd ();
|
||||
return allEdges;
|
||||
}
|
||||
|
||||
|
||||
/*********************************************************
|
||||
* Private methods - implementation details
|
||||
********************************************************/
|
||||
|
||||
private void sort ( double[] xValuesIn, double[] yValuesIn, int count )
|
||||
{
|
||||
sites = null;
|
||||
allEdges = new List<GraphEdge>();
|
||||
|
||||
nsites = count;
|
||||
nvertices = 0;
|
||||
nedges = 0;
|
||||
|
||||
double sn = (double)nsites + 4;
|
||||
sqrt_nsites = (int) Math.Sqrt ( sn );
|
||||
|
||||
// Copy the inputs so we don't modify the originals
|
||||
double[] xValues = new double[count];
|
||||
double[] yValues = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
xValues[i] = xValuesIn[i];
|
||||
yValues[i] = yValuesIn[i];
|
||||
}
|
||||
sortNode ( xValues, yValues, count );
|
||||
}
|
||||
|
||||
private void qsort ( Site[] sites )
|
||||
{
|
||||
List<Site> listSites = new List<Site>( sites.Length );
|
||||
for ( int i = 0; i < sites.Length; i++ )
|
||||
{
|
||||
listSites.Add ( sites[i] );
|
||||
}
|
||||
|
||||
listSites.Sort ( new SiteSorterYX () );
|
||||
|
||||
// Copy back into the array
|
||||
for (int i=0; i < sites.Length; i++)
|
||||
{
|
||||
sites[i] = listSites[i];
|
||||
}
|
||||
}
|
||||
|
||||
private void sortNode ( double[] xValues, double[] yValues, int numPoints )
|
||||
{
|
||||
nsites = numPoints;
|
||||
sites = new Site[nsites];
|
||||
xmin = xValues[0];
|
||||
ymin = yValues[0];
|
||||
xmax = xValues[0];
|
||||
ymax = yValues[0];
|
||||
|
||||
for ( int i = 0; i < nsites; i++ )
|
||||
{
|
||||
sites[i] = new Site();
|
||||
sites[i].coord.setPoint ( xValues[i], yValues[i] );
|
||||
sites[i].sitenbr = i;
|
||||
|
||||
if ( xValues[i] < xmin )
|
||||
xmin = xValues[i];
|
||||
else if ( xValues[i] > xmax )
|
||||
xmax = xValues[i];
|
||||
|
||||
if ( yValues[i] < ymin )
|
||||
ymin = yValues[i];
|
||||
else if ( yValues[i] > ymax )
|
||||
ymax = yValues[i];
|
||||
}
|
||||
|
||||
qsort ( sites );
|
||||
deltax = xmax - xmin;
|
||||
deltay = ymax - ymin;
|
||||
}
|
||||
|
||||
private Site nextone ()
|
||||
{
|
||||
Site s;
|
||||
if ( siteidx < nsites )
|
||||
{
|
||||
s = sites[siteidx];
|
||||
siteidx++;
|
||||
return s;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Edge bisect ( Site s1, Site s2 )
|
||||
{
|
||||
double dx, dy, adx, ady;
|
||||
Edge newedge;
|
||||
|
||||
newedge = new Edge();
|
||||
|
||||
newedge.reg[0] = s1;
|
||||
newedge.reg[1] = s2;
|
||||
|
||||
newedge.ep [0] = null;
|
||||
newedge.ep[1] = null;
|
||||
|
||||
dx = s2.coord.x - s1.coord.x;
|
||||
dy = s2.coord.y - s1.coord.y;
|
||||
|
||||
adx = dx > 0 ? dx : -dx;
|
||||
ady = dy > 0 ? dy : -dy;
|
||||
newedge.c = (double)(s1.coord.x * dx + s1.coord.y * dy + (dx * dx + dy* dy) * 0.5);
|
||||
|
||||
if ( adx > ady )
|
||||
{
|
||||
newedge.a = 1.0;
|
||||
newedge.b = dy / dx;
|
||||
newedge.c /= dx;
|
||||
}
|
||||
else
|
||||
{
|
||||
newedge.a = dx / dy;
|
||||
newedge.b = 1.0;
|
||||
newedge.c /= dy;
|
||||
}
|
||||
|
||||
newedge.edgenbr = nedges;
|
||||
nedges++;
|
||||
|
||||
return newedge;
|
||||
}
|
||||
|
||||
private void makevertex ( Site v )
|
||||
{
|
||||
v.sitenbr = nvertices;
|
||||
nvertices++;
|
||||
}
|
||||
|
||||
private bool PQinitialize ()
|
||||
{
|
||||
PQcount = 0;
|
||||
PQmin = 0;
|
||||
PQhashsize = 4 * sqrt_nsites;
|
||||
PQhash = new Halfedge[ PQhashsize ];
|
||||
|
||||
for ( int i = 0; i < PQhashsize; i++ )
|
||||
{
|
||||
PQhash [i] = new Halfedge();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int PQbucket ( Halfedge he )
|
||||
{
|
||||
int bucket;
|
||||
|
||||
bucket = (int) ((he.ystar - ymin) / deltay * PQhashsize);
|
||||
if ( bucket < 0 )
|
||||
bucket = 0;
|
||||
if ( bucket >= PQhashsize )
|
||||
bucket = PQhashsize - 1;
|
||||
if ( bucket < PQmin )
|
||||
PQmin = bucket;
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
// push the HalfEdge into the ordered linked list of vertices
|
||||
private void PQinsert ( Halfedge he, Site v, double offset )
|
||||
{
|
||||
Halfedge last, next;
|
||||
|
||||
he.vertex = v;
|
||||
he.ystar = (double)(v.coord.y + offset);
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
|
||||
while
|
||||
(
|
||||
(next = last.PQnext) != null
|
||||
&&
|
||||
(he.ystar > next.ystar || (he.ystar == next.ystar && v.coord.x > next.vertex.coord.x))
|
||||
)
|
||||
{
|
||||
last = next;
|
||||
}
|
||||
|
||||
he.PQnext = last.PQnext;
|
||||
last.PQnext = he;
|
||||
PQcount++;
|
||||
}
|
||||
|
||||
// remove the HalfEdge from the list of vertices
|
||||
private void PQdelete ( Halfedge he )
|
||||
{
|
||||
Halfedge last;
|
||||
|
||||
if (he.vertex != null)
|
||||
{
|
||||
last = PQhash [ PQbucket (he) ];
|
||||
while ( last.PQnext != he )
|
||||
{
|
||||
last = last.PQnext;
|
||||
}
|
||||
|
||||
last.PQnext = he.PQnext;
|
||||
PQcount--;
|
||||
he.vertex = null;
|
||||
}
|
||||
}
|
||||
|
||||
private bool PQempty ()
|
||||
{
|
||||
return ( PQcount == 0 );
|
||||
}
|
||||
|
||||
private Point PQ_min ()
|
||||
{
|
||||
Point answer = new Point ();
|
||||
|
||||
while ( PQhash[PQmin].PQnext == null )
|
||||
{
|
||||
PQmin++;
|
||||
}
|
||||
|
||||
answer.x = PQhash[PQmin].PQnext.vertex.coord.x;
|
||||
answer.y = PQhash[PQmin].PQnext.ystar;
|
||||
return answer;
|
||||
}
|
||||
|
||||
private Halfedge PQextractmin ()
|
||||
{
|
||||
Halfedge curr;
|
||||
|
||||
curr = PQhash[PQmin].PQnext;
|
||||
PQhash[PQmin].PQnext = curr.PQnext;
|
||||
PQcount--;
|
||||
|
||||
return curr;
|
||||
}
|
||||
|
||||
private Halfedge HEcreate(Edge e, int pm)
|
||||
{
|
||||
Halfedge answer = new Halfedge();
|
||||
answer.ELedge = e;
|
||||
answer.ELpm = pm;
|
||||
answer.PQnext = null;
|
||||
answer.vertex = null;
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private bool ELinitialize()
|
||||
{
|
||||
ELhashsize = 2 * sqrt_nsites;
|
||||
ELhash = new Halfedge[ELhashsize];
|
||||
|
||||
for (int i = 0; i < ELhashsize; i++)
|
||||
{
|
||||
ELhash[i] = null;
|
||||
}
|
||||
|
||||
ELleftend = HEcreate ( null, 0 );
|
||||
ELrightend = HEcreate ( null, 0 );
|
||||
ELleftend.ELleft = null;
|
||||
ELleftend.ELright = ELrightend;
|
||||
ELrightend.ELleft = ELleftend;
|
||||
ELrightend.ELright = null;
|
||||
ELhash[0] = ELleftend;
|
||||
ELhash[ELhashsize - 1] = ELrightend;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Halfedge ELright( Halfedge he )
|
||||
{
|
||||
return he.ELright;
|
||||
}
|
||||
|
||||
private Halfedge ELleft( Halfedge he )
|
||||
{
|
||||
return he.ELleft;
|
||||
}
|
||||
|
||||
private Site leftreg( Halfedge he )
|
||||
{
|
||||
if (he.ELedge == null)
|
||||
{
|
||||
return bottomsite;
|
||||
}
|
||||
return (he.ELpm == LE ? he.ELedge.reg[LE] : he.ELedge.reg[RE]);
|
||||
}
|
||||
|
||||
private void ELinsert( Halfedge lb, Halfedge newHe )
|
||||
{
|
||||
newHe.ELleft = lb;
|
||||
newHe.ELright = lb.ELright;
|
||||
(lb.ELright).ELleft = newHe;
|
||||
lb.ELright = newHe;
|
||||
}
|
||||
|
||||
/*
|
||||
* This delete routine can't reclaim node, since pointers from hash table
|
||||
* may be present.
|
||||
*/
|
||||
private void ELdelete( Halfedge he )
|
||||
{
|
||||
(he.ELleft).ELright = he.ELright;
|
||||
(he.ELright).ELleft = he.ELleft;
|
||||
he.deleted = true;
|
||||
}
|
||||
|
||||
/* Get entry from hash table, pruning any deleted nodes */
|
||||
private Halfedge ELgethash( int b )
|
||||
{
|
||||
Halfedge he;
|
||||
if (b < 0 || b >= ELhashsize)
|
||||
return null;
|
||||
|
||||
he = ELhash[b];
|
||||
if (he == null || !he.deleted )
|
||||
return he;
|
||||
|
||||
/* Hash table points to deleted half edge. Patch as necessary. */
|
||||
ELhash[b] = null;
|
||||
return null;
|
||||
}
|
||||
|
||||
private Halfedge ELleftbnd( Point p )
|
||||
{
|
||||
int bucket;
|
||||
Halfedge he;
|
||||
|
||||
/* Use hash table to get close to desired halfedge */
|
||||
// use the hash function to find the place in the hash map that this
|
||||
// HalfEdge should be
|
||||
bucket = (int) ((p.x - xmin) / deltax * ELhashsize);
|
||||
|
||||
// make sure that the bucket position is within the range of the hash
|
||||
// array
|
||||
if ( bucket < 0 ) bucket = 0;
|
||||
if ( bucket >= ELhashsize ) bucket = ELhashsize - 1;
|
||||
|
||||
he = ELgethash ( bucket );
|
||||
|
||||
// if the HE isn't found, search backwards and forwards in the hash map
|
||||
// for the first non-null entry
|
||||
if ( he == null )
|
||||
{
|
||||
for ( int i = 1; i < ELhashsize; i++ )
|
||||
{
|
||||
if ( (he = ELgethash ( bucket - i ) ) != null )
|
||||
break;
|
||||
if ( (he = ELgethash ( bucket + i ) ) != null )
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now search linear list of halfedges for the correct one */
|
||||
if ( he == ELleftend || ( he != ELrightend && right_of (he, p) ) )
|
||||
{
|
||||
// keep going right on the list until either the end is reached, or
|
||||
// you find the 1st edge which the point isn't to the right of
|
||||
do
|
||||
{
|
||||
he = he.ELright;
|
||||
}
|
||||
while ( he != ELrightend && right_of(he, p) );
|
||||
he = he.ELleft;
|
||||
}
|
||||
else
|
||||
// if the point is to the left of the HalfEdge, then search left for
|
||||
// the HE just to the left of the point
|
||||
{
|
||||
do
|
||||
{
|
||||
he = he.ELleft;
|
||||
}
|
||||
while ( he != ELleftend && !right_of(he, p) );
|
||||
}
|
||||
|
||||
/* Update hash table and reference counts */
|
||||
if ( bucket > 0 && bucket < ELhashsize - 1)
|
||||
{
|
||||
ELhash[bucket] = he;
|
||||
}
|
||||
|
||||
return he;
|
||||
}
|
||||
|
||||
private void pushGraphEdge( Site leftSite, Site rightSite, Vector2 point1, Vector2 point2 )
|
||||
{
|
||||
GraphEdge newEdge = new GraphEdge(point1, point2);
|
||||
allEdges.Add ( newEdge );
|
||||
|
||||
newEdge.site1 = leftSite;
|
||||
newEdge.site2 = rightSite;
|
||||
}
|
||||
|
||||
private void clip_line( Edge e )
|
||||
{
|
||||
double pxmin, pxmax, pymin, pymax;
|
||||
Site s1, s2;
|
||||
|
||||
double x1 = e.reg[0].coord.x;
|
||||
double y1 = e.reg[0].coord.y;
|
||||
double x2 = e.reg[1].coord.x;
|
||||
double y2 = e.reg[1].coord.y;
|
||||
double x = x2- x1;
|
||||
double y = y2 - y1;
|
||||
|
||||
// if the distance between the two points this line was created from is
|
||||
// less than the square root of 2 عن جد؟, then ignore it
|
||||
if ( Math.Sqrt ( (x*x) + (y*y) ) < minDistanceBetweenSites )
|
||||
{
|
||||
return;
|
||||
}
|
||||
pxmin = borderMinX;
|
||||
pymin = borderMinY;
|
||||
pxmax = borderMaxX;
|
||||
pymax = borderMaxY;
|
||||
|
||||
if ( e.a == 1.0 && e.b >= 0.0 )
|
||||
{
|
||||
s1 = e.ep[1];
|
||||
s2 = e.ep[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
s1 = e.ep[0];
|
||||
s2 = e.ep[1];
|
||||
}
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
y1 = pymin;
|
||||
|
||||
if ( s1 != null && s1.coord.y > pymin )
|
||||
y1 = s1.coord.y;
|
||||
if ( y1 > pymax )
|
||||
y1 = pymax;
|
||||
x1 = e.c - e.b * y1;
|
||||
y2 = pymax;
|
||||
|
||||
if ( s2 != null && s2.coord.y < pymax )
|
||||
y2 = s2.coord.y;
|
||||
if ( y2 < pymin )
|
||||
y2 = pymin;
|
||||
x2 = e.c - e.b * y2;
|
||||
if ( ( (x1 > pxmax) & (x2 > pxmax) ) | ( (x1 < pxmin) & (x2 < pxmin) ) )
|
||||
return;
|
||||
|
||||
if ( x1 > pxmax )
|
||||
{
|
||||
x1 = pxmax;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x1 < pxmin )
|
||||
{
|
||||
x1 = pxmin;
|
||||
y1 = ( e.c - x1 ) / e.b;
|
||||
}
|
||||
if ( x2 > pxmax )
|
||||
{
|
||||
x2 = pxmax;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
if ( x2 < pxmin )
|
||||
{
|
||||
x2 = pxmin;
|
||||
y2 = ( e.c - x2 ) / e.b;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
x1 = pxmin;
|
||||
if ( s1 != null && s1.coord.x > pxmin )
|
||||
x1 = s1.coord.x;
|
||||
if ( x1 > pxmax )
|
||||
x1 = pxmax;
|
||||
y1 = e.c - e.a * x1;
|
||||
|
||||
x2 = pxmax;
|
||||
if ( s2 != null && s2.coord.x < pxmax )
|
||||
x2 = s2.coord.x;
|
||||
if ( x2 < pxmin )
|
||||
x2 = pxmin;
|
||||
y2 = e.c - e.a * x2;
|
||||
|
||||
if (((y1 > pymax) & (y2 > pymax)) | ((y1 < pymin) & (y2 < pymin)))
|
||||
return;
|
||||
|
||||
if ( y1 > pymax )
|
||||
{
|
||||
y1 = pymax;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y1 < pymin )
|
||||
{
|
||||
y1 = pymin;
|
||||
x1 = ( e.c - y1 ) / e.a;
|
||||
}
|
||||
if ( y2 > pymax )
|
||||
{
|
||||
y2 = pymax;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
if ( y2 < pymin )
|
||||
{
|
||||
y2 = pymin;
|
||||
x2 = ( e.c - y2 ) / e.a;
|
||||
}
|
||||
}
|
||||
|
||||
pushGraphEdge(e.reg[0], e.reg[1], new Vector2((float)x1, (float)y1), new Vector2((float)x2, (float)y2));
|
||||
}
|
||||
|
||||
private void endpoint( Edge e, int lr, Site s )
|
||||
{
|
||||
e.ep[lr] = s;
|
||||
if ( e.ep[RE - lr] == null )
|
||||
return;
|
||||
clip_line ( e );
|
||||
}
|
||||
|
||||
/* returns true if p is to right of halfedge e */
|
||||
private bool right_of(Halfedge el, Point p)
|
||||
{
|
||||
Edge e;
|
||||
Site topsite;
|
||||
bool right_of_site;
|
||||
bool above, fast;
|
||||
double dxp, dyp, dxs, t1, t2, t3, yl;
|
||||
|
||||
e = el.ELedge;
|
||||
topsite = e.reg[1];
|
||||
|
||||
if ( p.x > topsite.coord.x )
|
||||
right_of_site = true;
|
||||
else
|
||||
right_of_site = false;
|
||||
|
||||
if ( right_of_site && el.ELpm == LE )
|
||||
return true;
|
||||
if (!right_of_site && el.ELpm == RE )
|
||||
return false;
|
||||
|
||||
if ( e.a == 1.0 )
|
||||
{
|
||||
dxp = p.x - topsite.coord.x;
|
||||
dyp = p.y - topsite.coord.y;
|
||||
fast = false;
|
||||
|
||||
if ( (!right_of_site & (e.b < 0.0)) | (right_of_site & (e.b >= 0.0)) )
|
||||
{
|
||||
above = dyp >= e.b * dxp;
|
||||
fast = above;
|
||||
}
|
||||
else
|
||||
{
|
||||
above = p.x + p.y * e.b > e.c;
|
||||
if ( e.b < 0.0 )
|
||||
above = !above;
|
||||
if ( !above )
|
||||
fast = true;
|
||||
}
|
||||
if ( !fast )
|
||||
{
|
||||
dxs = topsite.coord.x - ( e.reg[0] ).coord.x;
|
||||
above = e.b * (dxp * dxp - dyp * dyp)
|
||||
< dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b);
|
||||
|
||||
if ( e.b < 0 )
|
||||
above = !above;
|
||||
}
|
||||
}
|
||||
else // e.b == 1.0
|
||||
{
|
||||
yl = e.c - e.a * p.x;
|
||||
t1 = p.y - yl;
|
||||
t2 = p.x - topsite.coord.x;
|
||||
t3 = yl - topsite.coord.y;
|
||||
above = t1 * t1 > t2 * t2 + t3 * t3;
|
||||
}
|
||||
return ( el.ELpm == LE ? above : !above );
|
||||
}
|
||||
|
||||
private Site rightreg(Halfedge he)
|
||||
{
|
||||
if (he.ELedge == (Edge) null)
|
||||
// if this halfedge has no edge, return the bottom site (whatever
|
||||
// that is)
|
||||
{
|
||||
return (bottomsite);
|
||||
}
|
||||
|
||||
// if the ELpm field is zero, return the site 0 that this edge bisects,
|
||||
// otherwise return site number 1
|
||||
return (he.ELpm == LE ? he.ELedge.reg[RE] : he.ELedge.reg[LE]);
|
||||
}
|
||||
|
||||
private double dist( Site s, Site t )
|
||||
{
|
||||
double dx, dy;
|
||||
dx = s.coord.x - t.coord.x;
|
||||
dy = s.coord.y - t.coord.y;
|
||||
return Math.Sqrt ( dx * dx + dy * dy );
|
||||
}
|
||||
|
||||
// create a new site where the HalfEdges el1 and el2 intersect - note that
|
||||
// the Point in the argument list is not used, don't know why it's there
|
||||
private Site intersect( Halfedge el1, Halfedge el2 )
|
||||
{
|
||||
Edge e1, e2, e;
|
||||
Halfedge el;
|
||||
double d, xint, yint;
|
||||
bool right_of_site;
|
||||
Site v; // vertex
|
||||
|
||||
e1 = el1.ELedge;
|
||||
e2 = el2.ELedge;
|
||||
|
||||
if ( e1 == null || e2 == null )
|
||||
return null;
|
||||
|
||||
// if the two edges bisect the same parent, return null
|
||||
if ( e1.reg[1] == e2.reg[1] )
|
||||
return null;
|
||||
|
||||
d = e1.a * e2.b - e1.b * e2.a;
|
||||
if ( -1.0e-10 < d && d < 1.0e-10 )
|
||||
return null;
|
||||
|
||||
xint = ( e1.c * e2.b - e2.c * e1.b ) / d;
|
||||
yint = ( e2.c * e1.a - e1.c * e2.a ) / d;
|
||||
|
||||
if ( (e1.reg[1].coord.y < e2.reg[1].coord.y)
|
||||
|| (e1.reg[1].coord.y == e2.reg[1].coord.y && e1.reg[1].coord.x < e2.reg[1].coord.x) )
|
||||
{
|
||||
el = el1;
|
||||
e = e1;
|
||||
}
|
||||
else
|
||||
{
|
||||
el = el2;
|
||||
e = e2;
|
||||
}
|
||||
|
||||
right_of_site = xint >= e.reg[1].coord.x;
|
||||
if ((right_of_site && el.ELpm == LE)
|
||||
|| (!right_of_site && el.ELpm == RE))
|
||||
return null;
|
||||
|
||||
// create a new site at the point of intersection - this is a new vector
|
||||
// event waiting to happen
|
||||
v = new Site();
|
||||
v.coord.x = xint;
|
||||
v.coord.y = yint;
|
||||
return v;
|
||||
}
|
||||
|
||||
/*
|
||||
* implicit parameters: nsites, sqrt_nsites, xmin, xmax, ymin, ymax, deltax,
|
||||
* deltay (can all be estimates). Performance suffers if they are wrong;
|
||||
* better to make nsites, deltax, and deltay too big than too small. (?)
|
||||
*/
|
||||
private bool voronoi_bd()
|
||||
{
|
||||
Site newsite, bot, top, temp, p;
|
||||
Site v;
|
||||
Point newintstar = null;
|
||||
int pm;
|
||||
Halfedge lbnd, rbnd, llbnd, rrbnd, bisector;
|
||||
Edge e;
|
||||
|
||||
PQinitialize();
|
||||
ELinitialize();
|
||||
|
||||
bottomsite = nextone();
|
||||
newsite = nextone();
|
||||
while (true)
|
||||
{
|
||||
if (!PQempty())
|
||||
{
|
||||
newintstar = PQ_min();
|
||||
}
|
||||
// if the lowest site has a smaller y value than the lowest vector
|
||||
// intersection,
|
||||
// process the site otherwise process the vector intersection
|
||||
|
||||
if (newsite != null && (PQempty()
|
||||
|| newsite.coord.y < newintstar.y
|
||||
|| (newsite.coord.y == newintstar.y
|
||||
&& newsite.coord.x < newintstar.x)))
|
||||
{
|
||||
/* new site is smallest -this is a site event */
|
||||
// get the first HalfEdge to the LEFT of the new site
|
||||
lbnd = ELleftbnd((newsite.coord));
|
||||
// get the first HalfEdge to the RIGHT of the new site
|
||||
rbnd = ELright(lbnd);
|
||||
// if this halfedge has no edge,bot =bottom site (whatever that
|
||||
// is)
|
||||
bot = rightreg(lbnd);
|
||||
// create a new edge that bisects
|
||||
e = bisect(bot, newsite);
|
||||
|
||||
// create a new HalfEdge, setting its ELpm field to 0
|
||||
bisector = HEcreate(e, LE);
|
||||
// insert this new bisector edge between the left and right
|
||||
// vectors in a linked list
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if the new bisector intersects with the left edge,
|
||||
// remove the left edge's vertex, and put in the new one
|
||||
if ((p = intersect(lbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(lbnd);
|
||||
PQinsert(lbnd, p, dist(p, newsite));
|
||||
}
|
||||
lbnd = bisector;
|
||||
// create a new HalfEdge, setting its ELpm field to 1
|
||||
bisector = HEcreate(e, RE);
|
||||
// insert the new HE to the right of the original bisector
|
||||
// earlier in the IF stmt
|
||||
ELinsert(lbnd, bisector);
|
||||
|
||||
// if this new bisector intersects with the new HalfEdge
|
||||
if ((p = intersect(bisector, rbnd)) != null)
|
||||
{
|
||||
// push the HE into the ordered linked list of vertices
|
||||
PQinsert(bisector, p, dist(p, newsite));
|
||||
}
|
||||
newsite = nextone();
|
||||
} else if (!PQempty())
|
||||
/* intersection is smallest - this is a vector event */
|
||||
{
|
||||
// pop the HalfEdge with the lowest vector off the ordered list
|
||||
// of vectors
|
||||
lbnd = PQextractmin();
|
||||
// get the HalfEdge to the left of the above HE
|
||||
llbnd = ELleft(lbnd);
|
||||
// get the HalfEdge to the right of the above HE
|
||||
rbnd = ELright(lbnd);
|
||||
// get the HalfEdge to the right of the HE to the right of the
|
||||
// lowest HE
|
||||
rrbnd = ELright(rbnd);
|
||||
// get the Site to the left of the left HE which it bisects
|
||||
bot = leftreg(lbnd);
|
||||
// get the Site to the right of the right HE which it bisects
|
||||
top = rightreg(rbnd);
|
||||
|
||||
v = lbnd.vertex; // get the vertex that caused this event
|
||||
makevertex(v); // set the vertex number - couldn't do this
|
||||
// earlier since we didn't know when it would be processed
|
||||
endpoint(lbnd.ELedge, lbnd.ELpm, v);
|
||||
// set the endpoint of
|
||||
// the left HalfEdge to be this vector
|
||||
endpoint(rbnd.ELedge, rbnd.ELpm, v);
|
||||
// set the endpoint of the right HalfEdge to
|
||||
// be this vector
|
||||
ELdelete(lbnd); // mark the lowest HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
PQdelete(rbnd);
|
||||
// remove all vertex events to do with the right HE
|
||||
ELdelete(rbnd); // mark the right HE for
|
||||
// deletion - can't delete yet because there might be pointers
|
||||
// to it in Hash Map
|
||||
pm = LE; // set the pm variable to zero
|
||||
|
||||
if (bot.coord.y > top.coord.y)
|
||||
// if the site to the left of the event is higher than the
|
||||
// Site
|
||||
{ // to the right of it, then swap them and set the 'pm'
|
||||
// variable to 1
|
||||
temp = bot;
|
||||
bot = top;
|
||||
top = temp;
|
||||
pm = RE;
|
||||
}
|
||||
e = bisect(bot, top); // create an Edge (or line)
|
||||
// that is between the two Sites. This creates the formula of
|
||||
// the line, and assigns a line number to it
|
||||
bisector = HEcreate(e, pm); // create a HE from the Edge 'e',
|
||||
// and make it point to that edge
|
||||
// with its ELedge field
|
||||
ELinsert(llbnd, bisector); // insert the new bisector to the
|
||||
// right of the left HE
|
||||
endpoint(e, RE - pm, v); // set one endpoint to the new edge
|
||||
// to be the vector point 'v'.
|
||||
// If the site to the left of this bisector is higher than the
|
||||
// right Site, then this endpoint
|
||||
// is put in position 0; otherwise in pos 1
|
||||
|
||||
// if left HE and the new bisector intersect, then delete
|
||||
// the left HE, and reinsert it
|
||||
if ((p = intersect(llbnd, bisector)) != null)
|
||||
{
|
||||
PQdelete(llbnd);
|
||||
PQinsert(llbnd, p, dist(p, bot));
|
||||
}
|
||||
|
||||
// if right HE and the new bisector intersect, then
|
||||
// reinsert it
|
||||
if ((p = intersect(bisector, rrbnd)) != null)
|
||||
{
|
||||
PQinsert(bisector, p, dist(p, bot));
|
||||
}
|
||||
} else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (lbnd = ELright(ELleftend); lbnd != ELrightend; lbnd = ELright(lbnd))
|
||||
{
|
||||
e = lbnd.ELedge;
|
||||
clip_line(e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, float minX, float minY, float maxX, float maxY)
|
||||
{
|
||||
double[] xVal = new double[sites.Count];
|
||||
double[] yVal = new double[sites.Count];
|
||||
for (int i = 0; i < sites.Count; i++)
|
||||
{
|
||||
xVal[i] = sites[i].X;
|
||||
yVal[i] = sites[i].Y;
|
||||
}
|
||||
return generateVoronoi(xVal, yVal, minX, maxX, minY, maxY);
|
||||
}
|
||||
|
||||
public List<GraphEdge> MakeVoronoiGraph(List<Vector2> sites, int width, int height)
|
||||
{
|
||||
double[] xVal = new double[sites.Count];
|
||||
double[] yVal = new double[sites.Count];
|
||||
for (int i = 0; i < sites.Count; i++)
|
||||
{
|
||||
xVal[i] = sites[i].X;
|
||||
yVal[i] = sites[i].Y;
|
||||
}
|
||||
return generateVoronoi(xVal, yVal, 0, width, 0, height);
|
||||
}
|
||||
|
||||
} // Voronoi Class End
|
||||
} // namespace Voronoi2 End
|
||||
@@ -0,0 +1,259 @@
|
||||
/*
|
||||
* Created by SharpDevelop.
|
||||
* User: Burhan
|
||||
* Date: 17/06/2014
|
||||
* Time: 09:29 م
|
||||
*
|
||||
* To change this template use Tools | Options | Coding | Edit Standard Headers.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2011 James Humphreys. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are
|
||||
permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of
|
||||
conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list
|
||||
of conditions and the following disclaimer in the documentation and/or other materials
|
||||
provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY James Humphreys ``AS IS\" AND ANY EXPRESS OR IMPLIED
|
||||
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR
|
||||
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
The views and conclusions contained in the software and documentation are those of the
|
||||
authors and should not be interpreted as representing official policies, either expressed
|
||||
or implied, of James Humphreys.
|
||||
*/
|
||||
|
||||
/*
|
||||
* C# Version by Burhan Joukhadar
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose without fee is hereby granted, provided that this entire notice
|
||||
* is included in all copies of any software which is or includes a copy
|
||||
* or modification of this software and in all copies of the supporting
|
||||
* documentation for such software.
|
||||
* THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
|
||||
* WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
|
||||
* REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
|
||||
* OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
|
||||
*/
|
||||
|
||||
|
||||
using FarseerPhysics.Dynamics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Voronoi2
|
||||
{
|
||||
public class Point
|
||||
{
|
||||
public double x, y;
|
||||
|
||||
public void setPoint ( double x, double y )
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
// use for sites and vertecies
|
||||
public class Site
|
||||
{
|
||||
public Point coord;
|
||||
public int sitenbr;
|
||||
|
||||
public void SetPoint(Vector2 point)
|
||||
{
|
||||
coord.setPoint(point.X, point.Y);
|
||||
}
|
||||
|
||||
public Site ()
|
||||
{
|
||||
coord = new Point();
|
||||
}
|
||||
}
|
||||
|
||||
public class Edge
|
||||
{
|
||||
public double a = 0, b = 0, c = 0;
|
||||
public Site[] ep;
|
||||
public Site[] reg;
|
||||
public int edgenbr;
|
||||
|
||||
public Edge ()
|
||||
{
|
||||
ep = new Site[2];
|
||||
reg = new Site[2];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Halfedge
|
||||
{
|
||||
public Halfedge ELleft, ELright;
|
||||
public Edge ELedge;
|
||||
public bool deleted;
|
||||
public int ELpm;
|
||||
public Site vertex;
|
||||
public double ystar;
|
||||
public Halfedge PQnext;
|
||||
|
||||
public Halfedge ()
|
||||
{
|
||||
PQnext = null;
|
||||
}
|
||||
}
|
||||
|
||||
public enum CellType
|
||||
{
|
||||
Solid, Empty, Edge, Path, Removed
|
||||
}
|
||||
|
||||
public class VoronoiCell
|
||||
{
|
||||
public List<GraphEdge> edges;
|
||||
public Site site;
|
||||
|
||||
public List<Vector2> bodyVertices;
|
||||
|
||||
public Body body;
|
||||
|
||||
public CellType CellType;
|
||||
|
||||
public Vector2 Translation;
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return new Vector2((float)site.coord.x, (float)site.coord.y)+Translation; }
|
||||
}
|
||||
|
||||
public VoronoiCell(Vector2[] vertices)
|
||||
{
|
||||
edges = new List<GraphEdge>();
|
||||
bodyVertices = new List<Vector2>();
|
||||
|
||||
Vector2 midPoint = Vector2.Zero;
|
||||
foreach (Vector2 vertex in vertices)
|
||||
{
|
||||
midPoint += vertex;
|
||||
}
|
||||
midPoint /= vertices.Length;
|
||||
|
||||
|
||||
for (int i = 1; i < vertices.Length; i++ )
|
||||
{
|
||||
GraphEdge ge = new GraphEdge(vertices[i-1], vertices[i]);
|
||||
|
||||
System.Diagnostics.Debug.Assert(ge.point1 != ge.point2);
|
||||
|
||||
edges.Add(ge);
|
||||
}
|
||||
|
||||
GraphEdge lastEdge = new GraphEdge(vertices[0], vertices[vertices.Length-1]);
|
||||
|
||||
edges.Add(lastEdge);
|
||||
|
||||
site = new Site();
|
||||
site.SetPoint(midPoint);
|
||||
}
|
||||
|
||||
public VoronoiCell(Site site)
|
||||
{
|
||||
edges = new List<GraphEdge>();
|
||||
bodyVertices = new List<Vector2>();
|
||||
//bodies = new List<Body>();
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
public bool IsPointInside(Vector2 point)
|
||||
{
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (MathUtils.LinesIntersect(point, Center, edge.point1, edge.point2)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class GraphEdge
|
||||
{
|
||||
public Vector2 point1, point2;
|
||||
public Site site1, site2;
|
||||
public VoronoiCell cell1, cell2;
|
||||
|
||||
public bool isSolid;
|
||||
|
||||
public bool OutsideLevel;
|
||||
|
||||
public Vector2 Center
|
||||
{
|
||||
get { return (point1 + point2) / 2.0f; }
|
||||
}
|
||||
|
||||
public GraphEdge(Vector2 point1, Vector2 point2)
|
||||
{
|
||||
this.point1 = point1;
|
||||
this.point2 = point2;
|
||||
}
|
||||
|
||||
public VoronoiCell AdjacentCell(VoronoiCell cell)
|
||||
{
|
||||
if (cell1==cell)
|
||||
{
|
||||
return cell2;
|
||||
}
|
||||
else if (cell2==cell)
|
||||
{
|
||||
return cell1;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the normal of the edge that points outwards from the specified cell
|
||||
/// </summary>
|
||||
public Vector2 GetNormal(VoronoiCell cell)
|
||||
{
|
||||
Vector2 dir = Vector2.Normalize(point1 - point2);
|
||||
|
||||
Vector2 normal = new Vector2(dir.Y, -dir.X);
|
||||
|
||||
if (cell != null && Vector2.Dot(normal, Vector2.Normalize(Center - cell.Center)) < 0)
|
||||
{
|
||||
normal = -normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
|
||||
// للترتيب
|
||||
public class SiteSorterYX : IComparer<Site>
|
||||
{
|
||||
public int Compare ( Site p1, Site p2 )
|
||||
{
|
||||
Point s1 = p1.coord;
|
||||
Point s2 = p2.coord;
|
||||
if ( s1.y < s2.y ) return -1;
|
||||
if ( s1.y > s2.y ) return 1;
|
||||
if ( s1.x < s2.x ) return -1;
|
||||
if ( s1.x > s2.x ) return 1;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Microsoft.Xna.Framework.Content;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WaterRenderer : IDisposable
|
||||
{
|
||||
const int DefaultBufferSize = 1500;
|
||||
|
||||
private Vector2 wavePos;
|
||||
|
||||
public VertexPositionTexture[] vertices = new VertexPositionTexture[DefaultBufferSize];
|
||||
|
||||
private Effect waterEffect;
|
||||
private BasicEffect basicEffect;
|
||||
|
||||
public int PositionInBuffer = 0;
|
||||
|
||||
private Texture2D waterTexture;
|
||||
|
||||
public Texture2D WaterTexture
|
||||
{
|
||||
get { return waterTexture; }
|
||||
}
|
||||
|
||||
public WaterRenderer(GraphicsDevice graphicsDevice, ContentManager content)
|
||||
{
|
||||
#if WINDOWS
|
||||
waterEffect = content.Load<Effect>("watershader");
|
||||
#endif
|
||||
#if LINUX
|
||||
|
||||
waterEffect = content.Load<Effect>("watershader_opengl");
|
||||
#endif
|
||||
|
||||
waterTexture = TextureLoader.FromFile("Content/waterbump.png");
|
||||
waterEffect.Parameters["xWaveWidth"].SetValue(0.05f);
|
||||
waterEffect.Parameters["xWaveHeight"].SetValue(0.05f);
|
||||
|
||||
#if WINDOWS
|
||||
//waterEffect.Parameters["xTexture"].SetValue(waterTexture);
|
||||
#endif
|
||||
#if LINUX
|
||||
waterEffect.Parameters["xWaterBumpMap"].SetValue(waterTexture);
|
||||
#endif
|
||||
|
||||
if (basicEffect == null)
|
||||
{
|
||||
basicEffect = new BasicEffect(GameMain.Instance.GraphicsDevice);
|
||||
basicEffect.VertexColorEnabled = false;
|
||||
|
||||
basicEffect.TextureEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderBack(SpriteBatch spriteBatch, RenderTarget2D texture, float blurAmount = 0.0f)
|
||||
{
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, SamplerState.LinearWrap, null, null, waterEffect);
|
||||
|
||||
waterEffect.CurrentTechnique = waterEffect.Techniques["WaterShader"];
|
||||
waterEffect.Parameters["xWavePos"].SetValue(wavePos);
|
||||
waterEffect.Parameters["xBlurDistance"].SetValue(blurAmount);
|
||||
//waterEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
#if WINDOWS
|
||||
waterEffect.Parameters["xTexture"].SetValue(texture);
|
||||
spriteBatch.Draw(waterTexture, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
||||
#elif LINUX
|
||||
|
||||
spriteBatch.Draw(texture, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White);
|
||||
#endif
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
public void ScrollWater(float deltaTime)
|
||||
{
|
||||
wavePos.X += 0.006f * deltaTime;
|
||||
wavePos.Y += 0.006f * deltaTime;
|
||||
}
|
||||
|
||||
public void Render(GraphicsDevice graphicsDevice, Camera cam, RenderTarget2D texture, Matrix transform)
|
||||
{
|
||||
if (vertices == null) return;
|
||||
if (vertices.Length < 0) return;
|
||||
|
||||
basicEffect.Texture = texture;
|
||||
|
||||
basicEffect.View = Matrix.Identity;
|
||||
basicEffect.World = transform
|
||||
* Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
|
||||
|
||||
basicEffect.CurrentTechnique.Passes[0].Apply();
|
||||
|
||||
graphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;
|
||||
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, vertices.Length / 3);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposing) return;
|
||||
|
||||
if (waterEffect != null)
|
||||
{
|
||||
waterEffect.Dispose();
|
||||
waterEffect = null;
|
||||
}
|
||||
|
||||
if (basicEffect != null)
|
||||
{
|
||||
basicEffect.Dispose();
|
||||
basicEffect = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
using FarseerPhysics;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Voronoi2;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class WrappingWall : IDisposable
|
||||
{
|
||||
public const float WallWidth = 20000.0f;
|
||||
|
||||
private VertexBuffer wallVertices, bodyVertices;
|
||||
|
||||
private Vector2 midPos;
|
||||
private int slot;
|
||||
|
||||
private Vector2 offset;
|
||||
|
||||
private List<VoronoiCell> cells;
|
||||
|
||||
public VertexBuffer WallVertices
|
||||
{
|
||||
get { return wallVertices; }
|
||||
}
|
||||
|
||||
public VertexBuffer BodyVertices
|
||||
{
|
||||
get { return bodyVertices; }
|
||||
}
|
||||
|
||||
public Vector2 Offset
|
||||
{
|
||||
get { return offset; }
|
||||
}
|
||||
|
||||
public List<VoronoiCell> Cells
|
||||
{
|
||||
get { return cells; }
|
||||
}
|
||||
|
||||
public Vector2 MidPos
|
||||
{
|
||||
get { return midPos; }
|
||||
}
|
||||
|
||||
public WrappingWall(List<VoronoiCell> pathCells, List<VoronoiCell> mapCells, Rectangle ignoredArea, int dir = -1)
|
||||
{
|
||||
cells = new List<VoronoiCell>();
|
||||
|
||||
VoronoiCell edgeCell = null;
|
||||
foreach (VoronoiCell cell in mapCells)
|
||||
{
|
||||
if (ignoredArea.Contains(cell.Center)) continue;
|
||||
if (Math.Sign(cell.Center.X - ignoredArea.Center.X) != Math.Sign(dir)) continue;
|
||||
if (edgeCell == null || cell.Center.Y < edgeCell.Center.Y)
|
||||
{
|
||||
edgeCell = cell;
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 wallSectionSize = new Vector2(2000.0f, 2000.0f);
|
||||
Vector2 startPos = (dir < 0) ?
|
||||
edgeCell.Center + Vector2.UnitX * WallWidth * dir :
|
||||
edgeCell.Center + WallWidth * Vector2.UnitX * (dir - 1);
|
||||
|
||||
midPos = startPos + Vector2.UnitX * WallWidth/2;
|
||||
|
||||
List<Vector2> bottomVertices = new List<Vector2>();
|
||||
|
||||
for (float x = 0; x <= WallWidth; x += wallSectionSize.X)
|
||||
{
|
||||
Vector2 center = new Vector2(startPos.X + x, edgeCell.Center.Y);
|
||||
float distFromCenter = Math.Abs(x - WallWidth / 2);
|
||||
float distFromEdge = WallWidth / 2 - distFromCenter;
|
||||
float normalizedDist = distFromEdge / (WallWidth / 2);
|
||||
|
||||
float variance = 1000.0f * normalizedDist;
|
||||
bottomVertices.Add(center + new Vector2(Rand.Range(-variance, variance, false), Rand.Range(-variance, variance, false)*2.0f));
|
||||
}
|
||||
|
||||
for (int i = 1; i < bottomVertices.Count; i++)
|
||||
{
|
||||
Vector2[] vertices = new Vector2[4];
|
||||
vertices[0] = bottomVertices[i];
|
||||
vertices[1] = bottomVertices[i - 1];
|
||||
vertices[2] = vertices[1] + Vector2.UnitY * wallSectionSize.Y;
|
||||
vertices[3] = vertices[0] + Vector2.UnitY * wallSectionSize.Y;
|
||||
|
||||
VoronoiCell wallCell = new VoronoiCell(vertices);
|
||||
wallCell.edges[0].cell1 = wallCell;
|
||||
wallCell.edges[1].cell1 = wallCell;
|
||||
wallCell.edges[2].cell1 = wallCell;
|
||||
wallCell.edges[3].cell1 = wallCell;
|
||||
|
||||
wallCell.edges[0].isSolid = true;
|
||||
wallCell.edges[2].isSolid = true;
|
||||
|
||||
|
||||
if (i > 1)
|
||||
{
|
||||
wallCell.edges[1].cell2 = cells[i - 2];
|
||||
cells[i - 2].edges[3].cell2 = wallCell;
|
||||
}
|
||||
|
||||
cells.Add(wallCell);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetWallVertices(VertexPositionTexture[] vertices)
|
||||
{
|
||||
wallVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionTexture.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
|
||||
wallVertices.SetData(vertices);
|
||||
}
|
||||
|
||||
public void SetBodyVertices(VertexPositionColor[] vertices)
|
||||
{
|
||||
bodyVertices = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColor.VertexDeclaration, vertices.Length, BufferUsage.WriteOnly);
|
||||
bodyVertices.SetData(vertices);
|
||||
}
|
||||
|
||||
|
||||
public static void UpdateWallShift(Vector2 pos, WrappingWall[,] walls)
|
||||
{
|
||||
if (pos.X < walls[0, 1].midPos.X && walls[0,0].midPos.X > pos.X)
|
||||
{
|
||||
walls[0, 0].Shift(-2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[0, 0] = walls[0, 1];
|
||||
walls[0, 1] = temp;
|
||||
}
|
||||
else if (pos.X > walls[0, 0].midPos.X && walls[0,1].midPos.X < pos.X && walls[0,1].slot<0)
|
||||
{
|
||||
walls[0, 1].Shift(2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[0, 0] = walls[0, 1];
|
||||
walls[0, 1] = temp;
|
||||
}
|
||||
else if (pos.X > walls[1, 1].midPos.X && walls[1,0].midPos.X < pos.X)
|
||||
{
|
||||
walls[1, 0].Shift(2);
|
||||
|
||||
var temp = walls[1, 0];
|
||||
walls[1, 0] = walls[1, 1];
|
||||
walls[1, 1] = temp;
|
||||
}
|
||||
else if (pos.X < walls[1, 0].midPos.X && walls[1, 1].midPos.X > pos.X && walls[1, 1].slot > 0)
|
||||
{
|
||||
walls[1, 1].Shift(-2);
|
||||
|
||||
var temp = walls[0, 0];
|
||||
walls[1, 0] = walls[1, 1];
|
||||
walls[1, 1] = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public void Shift(int amount)
|
||||
{
|
||||
slot += amount;
|
||||
|
||||
Vector2 moveAmount = Vector2.UnitX * WallWidth * amount;
|
||||
Vector2 simMoveAmount = ConvertUnits.ToSimUnits(moveAmount);
|
||||
|
||||
foreach (VoronoiCell cell in cells)
|
||||
{
|
||||
cell.body.SetTransform(cell.body.Position + simMoveAmount, 0.0f);
|
||||
cell.Translation += moveAmount;
|
||||
}
|
||||
|
||||
midPos += moveAmount;
|
||||
offset += moveAmount;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (wallVertices != null)
|
||||
{
|
||||
wallVertices.Dispose();
|
||||
wallVertices = null;
|
||||
}
|
||||
if (bodyVertices != null)
|
||||
{
|
||||
bodyVertices.Dispose();
|
||||
bodyVertices = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user