Renamed project folders from Subsurface to Barotrauma

This commit is contained in:
Regalis
2017-06-04 15:00:53 +03:00
parent ad03c8bf0d
commit 94c6a8ea1b
697 changed files with 157 additions and 211 deletions
@@ -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);
}
}
}
+953
View File
@@ -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;
}
}
}
+998
View File
@@ -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;
}
}
}
}