private static void Main(string[] args) { var start1 = new SphereCoordinate(0.5 * Math.PI, -0.25 * Math.PI); Console.WriteLine((CartesianVector)start1); var end1 = new SphereCoordinate(0.5 * Math.PI, 0.25 * Math.PI); Console.WriteLine((CartesianVector)end1); var start2 = new SphereCoordinate(0.25 * Math.PI, 0); Console.WriteLine((CartesianVector)start2); var end2 = new SphereCoordinate(0.75 * Math.PI, 0); Console.WriteLine((CartesianVector)end2); var arc1 = new GreatCircleSegment(start1, end1); var arc2 = new GreatCircleSegment(start2, end2); Console.WriteLine(); Console.WriteLine(arc1.Midpoint); Console.WriteLine(new CartesianVector(0, 0, 1)); Console.WriteLine((SphereCoordinate)arc1.Midpoint); Console.WriteLine((SphereCoordinate)new CartesianVector(0, 0, 1)); Console.WriteLine((CartesianVector)(SphereCoordinate)arc1.Midpoint); Console.WriteLine(); SphereCoordinate intersection; Console.WriteLine(arc1.Intersects(arc2, out intersection) + " " + intersection + " " + (CartesianVector)intersection); Console.WriteLine(); var quarterSpherePoly = new VoronoiCell(new SphereCoordinate(0, 0), new SphereCoordinate(0.5 * Math.PI, 0), new SphereCoordinate(0.5 * Math.PI, 0.5 * Math.PI), new SphereCoordinate(0.5 * Math.PI, Math.PI)); Console.WriteLine("Area of quarter sphere Polygon: " + quarterSpherePoly.Area); Console.WriteLine("Area of whole sphere: " + 4 * Math.PI); Console.WriteLine("Area of quarter sphere: " + Math.PI); Console.WriteLine(); Console.WriteLine(Math.Acos(new CartesianVector(0, 1, 0).DotProduct(new SphereCoordinate(Math.PI / 2, 0)))); Console.WriteLine(Math.Acos(new CartesianVector(0, 1, 0).DotProduct(new SphereCoordinate(Math.PI / 2, -Math.PI / 2)))); Console.WriteLine(Math.Acos(new CartesianVector(0, 1, 0).DotProduct(new SphereCoordinate(Math.PI / 2, 0)))); Console.WriteLine(Math.Acos(new CartesianVector(0, 1, 0).DotProduct(new SphereCoordinate(Math.PI / 2, -Math.PI / 2)))); Console.WriteLine(); Console.WriteLine(Math.Acos(new CartesianVector(1, 1, 0).AsUnitVector.DotProduct(new SphereCoordinate(Math.PI / 2, 0)))); Console.WriteLine(Math.Acos(new CartesianVector(1, 1, 0).AsUnitVector.DotProduct(new SphereCoordinate(Math.PI / 4, -Math.PI / 2)))); Console.WriteLine(); Console.WriteLine(Math.Acos(new CartesianVector(0, 1, 0).DotProduct(new CartesianVector(1, 1, 0).AsUnitVector))); Console.WriteLine(Math.PI / 4); Console.ReadLine(); }
static void Main(string[] args) { try { AlgoTradingClass td = new AlgoTradingClass(); VoronoiCell vgc = new VoronoiCell(); double[] X = { 0.5, -0.5 }; double[] Y = { 0, 0.5, 0.1 }; td.AlgoTradingDemo1CS(); //MWArray[] Results2 = (MWArray[])td.AlgoTrading1CS(void); MWArray[] Results = (MWArray[])vgc.NDimVoronoiDiagram(3, (MWNumericArray)X, (MWNumericArray)Y); double[,] EdgeX = (double[,])Results[0].ToArray(); double[,] EdgeY = (double[,])Results[1].ToArray(); } catch (Exception exception) { Console.WriteLine("Error: {0}", exception); } }
private void HandleLimbCollision(Impact collision, Limb limb) { if (limb?.body?.FarseerBody == null || limb.character == null) { return; } if (limb.Mass > MinImpactLimbMass) { Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? Vector2.UnitY : Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(collision.Velocity, -normal), 50.0f) * Math.Min(limb.Mass / 100.0f, 1); ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, collision.ImpactPos, applyDamage: false); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge?.Contact != null) { if (contactEdge.Contact.Enabled && contactEdge.Contact.IsTouching && contactEdge.Other?.UserData is VoronoiCell) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 <Vector2> temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-collision.Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, collision.ImpactPos, applyDamage: false); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.001f) { Vector2 velChange = Vector2.Normalize(Body.LinearVelocity) * contactDot; if (!MathUtils.IsValid(velChange)) { GameAnalyticsManager.AddErrorEventOnce( "SubmarineBody.HandleLimbCollision:" + submarine.ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Invalid velocity change in SubmarineBody.HandleLimbCollision (submarine velocity: " + Body.LinearVelocity + ", avgContactNormal: " + avgContactNormal + ", contactDot: " + contactDot + ", velChange: " + velChange + ")"); return; } Body.LinearVelocity -= velChange; float damageAmount = contactDot * Body.Mass / limb.character.Mass; limb.character.LastDamageSource = submarine; limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(collision.ImpactPos), limb, AfflictionPrefab.ImpactDamage.Instantiate(damageAmount).ToEnumerable(), 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } }
public static Body GeneratePolygons(List <VoronoiCell> cells, Level level, out List <Vector2[]> renderTriangles) { renderTriangles = new List <Vector2[]>(); List <Vector2> tempVertices = new List <Vector2>(); List <Vector2> bodyPoints = new List <Vector2>(); Body cellBody = new Body() { SleepingAllowed = false, BodyType = BodyType.Static, CollisionCategories = Physics.CollisionLevel }; GameMain.World.Add(cellBody); for (int n = cells.Count - 1; n >= 0; n--) { VoronoiCell cell = cells[n]; bodyPoints.Clear(); tempVertices.Clear(); foreach (GraphEdge ge in cell.Edges) { if (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.01f) { continue; } if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.Point1, v) < 1.0f)) { tempVertices.Add(ge.Point1); bodyPoints.Add(ge.Point1); } if (!tempVertices.Any(v => Vector2.DistanceSquared(ge.Point2, v) < 1.0f)) { tempVertices.Add(ge.Point2); bodyPoints.Add(ge.Point2); } } if (tempVertices.Count < 3 || bodyPoints.Count < 2) { cells.RemoveAt(n); continue; } Vector2 minVert = tempVertices[0]; Vector2 maxVert = tempVertices[0]; foreach (var vert in tempVertices) { minVert = new Vector2( Math.Min(minVert.X, vert.X), Math.Min(minVert.Y, vert.Y)); maxVert = new Vector2( Math.Max(maxVert.X, vert.X), Math.Max(maxVert.Y, vert.Y)); } Vector2 center = (minVert + maxVert) / 2; renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, center)); 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; } cellBody.UserData = cell; var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(center)); for (int i = 0; i < triangles.Count; i++) { //don't create a triangle if the area of the triangle is too small //(apparently Farseer doesn't like polygons with a very small area, see Shape.ComputeProperties) Vector2 a = triangles[i][0]; Vector2 b = triangles[i][1]; Vector2 c = triangles[i][2]; float area = Math.Abs(a.X * (b.Y - c.Y) + b.X * (c.Y - a.Y) + c.X * (a.Y - b.Y)) / 2.0f; if (area < 1.0f) { continue; } Vertices bodyVertices = new Vertices(triangles[i]); PolygonShape polygon = new PolygonShape(bodyVertices, 5.0f); Fixture fixture = new Fixture(polygon) { UserData = cell }; cellBody.Add(fixture, resetMassData: false); if (fixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon) { DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")"); GameAnalyticsManager.AddErrorEventOnce( "CaveGenerator.GeneratePolygons:InvalidTriangle", GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed); } } cell.Body = cellBody; } cellBody.CollisionCategories = Physics.CollisionLevel; cellBody.ResetMassData(); return(cellBody); }
void VoronoiTesselation() { bool usesUserDefinedSites = false; if (voronoiSites != null && voronoiSites.Count > 0) { numProvinces = voronoiSites.Count; usesUserDefinedSites = true; } if (centers == null || centers.Length != numProvinces) { centers = new Point[numProvinces]; } for (int k = 0; k < centers.Length; k++) { if (usesUserDefinedSites) { Vector2 p = voronoiSites [k]; centers [k] = new Point(p.x, p.y); } else { centers [k] = new Point(UnityEngine.Random.Range(-0.49f, 0.49f), UnityEngine.Random.Range(-0.49f, 0.49f)); } } if (voronoi == null) { voronoi = new VoronoiFortune(); } for (int k = 0; k < goodGridRelaxation; k++) { voronoi.AssignData(centers); voronoi.DoVoronoi(); if (k < goodGridRelaxation - 1) { for (int j = 0; j < numProvinces; j++) { Point centroid = voronoi.cells [j].centroid; centers [j] = (centers [j] + centroid) / 2; } } } // Make cell regions: we assume cells have only 1 region but that can change in the future for (int k = 0; k < voronoi.cells.Length; k++) { VoronoiCell voronoiCell = voronoi.cells [k]; Vector2 center = voronoiCell.center.vector3; MapProvince cell = new MapProvince(center); MapRegion cr = new MapRegion(cell); if (edgeNoise > 0) { cr.polygon = voronoiCell.GetPolygon(voronoiCell.center, edgeMaxLength, edgeNoise); } else { cr.polygon = voronoiCell.GetPolygon(); } if (cr.polygon != null) { // Add segments int segmentsCount = voronoiCell.segments.Count; for (int i = 0; i < segmentsCount; i++) { Segment s = voronoiCell.segments [i]; if (!s.deleted) { if (edgeNoise > 0) { cr.segments.AddRange(s.subdivisions); } else { cr.segments.Add(s); } } } cell.region = cr; mapProvinces.Add(cell); } } }
public bool OnCollision(Fixture f1, Fixture f2, Contact contact) { Limb limb = f2.Body.UserData as Limb; if (limb != null) { bool collision = HandleLimbCollision(contact, limb); if (collision && limb.Mass > 100.0f) { Vector2 normal = Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); ApplyImpact(impact, -normal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, contact); } } return(collision); } VoronoiCell cell = f2.Body.UserData as VoronoiCell; if (cell != null) { var collisionNormal = Vector2.Normalize(ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center); float wallImpact = Vector2.Dot(Velocity, -collisionNormal); ApplyImpact(wallImpact, -collisionNormal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(wallImpact, -collisionNormal, contact); } Vector2 n; FixedArray2 <Vector2> particlePos; contact.GetWorldManifold(out n, out particlePos); #if CLIENT int particleAmount = (int)(wallImpact * 10.0f); for (int i = 0; i < particleAmount; i++) { GameMain.ParticleManager.CreateParticle("iceshards", ConvertUnits.ToDisplayUnits(particlePos[0]) + Rand.Vector(Rand.Range(1.0f, 50.0f)), Rand.Vector(Rand.Range(50.0f, 500.0f)) + Velocity); } #endif return(true); } Submarine otherSub = f2.Body.UserData as Submarine; if (otherSub != null) { Debug.Assert(otherSub != submarine); Vector2 normal; FixedArray2 <Vector2> points; contact.GetWorldManifold(out normal, out points); if (contact.FixtureA.Body == otherSub.SubBody.Body.FarseerBody) { normal = -normal; } float thisMass = Body.Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass); float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass); float massRatio = otherMass / (thisMass + otherMass); float impact = (Vector2.Dot(Velocity - otherSub.Velocity, normal) / 2.0f) * massRatio; ApplyImpact(impact, normal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, normal, contact); } return(true); } return(true); }
public static Vector3 GetFirstCorner(VoronoiCell cell, VoronoiDirection direction) { return(cell.Corners[direction]); }
public static float OuterToInner(VoronoiCell cell, VoronoiDirection direction) { return(InnerRadius(cell, direction) / OuterRadius(cell, direction)); }
public static float OuterRadius(VoronoiCell cell, VoronoiDirection direction) { return((cell.Corners[direction].magnitude + cell.Corners[direction + 1].magnitude) * 0.5f); }
private void HandleLimbCollision(Contact contact, Limb limb) { if (limb.Mass > 100.0f) { Vector2 normal = Vector2.DistanceSquared(Body.SimPosition, limb.SimPosition) < 0.0001f ? Vector2.UnitY : Vector2.Normalize(Body.SimPosition - limb.SimPosition); float impact = Math.Min(Vector2.Dot(Velocity - limb.LinearVelocity, -normal), 50.0f) / 5.0f * Math.Min(limb.Mass / 200.0f, 1); ApplyImpact(impact, -normal, contact); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impact, -normal, contact); } } //find all contacts between the limb and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = limb.body.FarseerBody.ContactList; while (contactEdge.Next != null) { if (contactEdge.Contact.Enabled && contactEdge.Other.UserData is VoronoiCell && contactEdge.Contact.IsTouching) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if the limb is in contact with the level, apply an artifical impact to prevent the sub from bouncing on top of it //not a very realistic way to handle the collisions (makes it seem as if the characters were made of reinforced concrete), //but more realistic than bouncing and prevents using characters as "bumpers" that prevent all collision damage //TODO: apply impact damage and/or gib the character that got crushed between the sub and the level? Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { Vector2 contactNormal; FixedArray2 <Vector2> temp; levelContact.GetWorldManifold(out contactNormal, out temp); //if the contact normal is pointing from the limb towards the level cell it's touching, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(limb.body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the limb ApplyImpact((Vector2.Dot(-Velocity, contactNormal) / 2.0f) / levelContacts.Count, contactNormal, levelContact); } avgContactNormal /= levelContacts.Count; float contactDot = Vector2.Dot(Body.LinearVelocity, -avgContactNormal); if (contactDot > 0.001f) { Body.LinearVelocity -= Vector2.Normalize(Body.LinearVelocity) * contactDot; float damageAmount = contactDot * Body.Mass / limb.character.Mass; Vector2 n; FixedArray2 <Vector2> contactPos; contact.GetWorldManifold(out n, out contactPos); limb.character.DamageLimb(ConvertUnits.ToDisplayUnits(contactPos[0]), limb, DamageType.Blunt, damageAmount, 0.0f, 0.0f, true, 0.0f); if (limb.character.IsDead) { foreach (LimbJoint limbJoint in limb.character.AnimController.LimbJoints) { if (limbJoint.IsSevered || (limbJoint.LimbA != limb && limbJoint.LimbB != limb)) { continue; } limb.character.AnimController.SeverLimbJoint(limbJoint); } } } }
private void GenerateRuin(List <VoronoiCell> mainPath) { Vector2 ruinSize = new Vector2(Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server), Rand.Range(5000.0f, 8000.0f, Rand.RandSync.Server)); float ruinRadius = Math.Max(ruinSize.X, ruinSize.Y) * 0.5f; Vector2 ruinPos = cells[Rand.Int(cells.Count, Rand.RandSync.Server)].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(MathUtils.ToPoint(ruinPos - ruinSize * 0.5f), MathUtils.ToPoint(ruinSize))); 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 void Generate(bool mirror = false) { Stopwatch sw = new Stopwatch(); sw.Start(); if (loaded != null) { loaded.Unload(); } loaded = this; positionsOfInterest = new List <InterestingPosition>(); Voronoi voronoi = new Voronoi(1.0); List <Vector2> sites = new List <Vector2>(); bodies = new List <Body>(); Rand.SetSyncedSeed(ToolBox.StringToInt(seed)); #if CLIENT renderer = new LevelRenderer(this); backgroundColor = generationParams.BackgroundColor; float avgValue = (backgroundColor.R + backgroundColor.G + backgroundColor.G) / 3; GameMain.LightManager.AmbientLight = new Color(backgroundColor * (10.0f / avgValue), 1.0f); #endif 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, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); endPosition = new Vector2( borders.Width - Rand.Range(minWidth, minWidth * 2, Rand.RandSync.Server), Rand.Range(borders.Height * 0.5f, borders.Height - minWidth * 2, Rand.RandSync.Server)); 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, Rand.RandSync.Server); x < endPosition.X - Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server); x += Rand.Range(nodeInterval.X, nodeInterval.Y, Rand.RandSync.Server)) { pathNodes.Add(new Vector2(x, Rand.Range(pathBorders.Y, pathBorders.Bottom, Rand.RandSync.Server))); } 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, Rand.RandSync.Server)]; tunnelStartPos.X = MathHelper.Clamp(tunnelStartPos.X, pathBorders.X, pathBorders.Right); float tunnelLength = Rand.Range( generationParams.SmallTunnelLengthRange.X, generationParams.SmallTunnelLengthRange.Y, Rand.RandSync.Server); var tunnelNodes = MathUtils.GenerateJaggedLine( tunnelStartPos, new Vector2(tunnelStartPos.X, pathBorders.Bottom) + Rand.Vector(tunnelLength, Rand.RandSync.Server), 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, Rand.RandSync.Server), y + Rand.Range(-siteVariance.Y, siteVariance.Y, Rand.RandSync.Server)); 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, Rand.RandSync.Server)]; //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 <Vector2[]> triangles; bodies = CaveGenerator.GeneratePolygons(cellsWithBody, out triangles); #if CLIENT List <VertexPositionTexture> bodyVertices = CaveGenerator.GenerateRenderVerticeList(triangles); renderer.SetBodyVertices(bodyVertices.ToArray()); renderer.SetWallVertices(CaveGenerator.GenerateWallShapes(cells)); renderer.PlaceSprites(generationParams.BackgroundSpriteAmount); #endif 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("**********************************************************************************"); }
public VoronoiLine(Line L, VoronoiCell neigbor, int indexNeigbor) { IndexNeigbor = indexNeigbor; Neigbor = neigbor; _line = ParseLine(L); }
public static List <Body> GeneratePolygons(List <VoronoiCell> cells, out List <Vector2[]> renderTriangles, bool setSolid = true) { renderTriangles = new List <Vector2[]>(); 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; } renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, cell.Center)); 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; } var 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 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, Rand.RandSync.Server) == 0) { continue; //skip some positions to make the cells more irregular } sites.Add(new Vector2(x, y) + Rand.Vector(siteVariance, Rand.RandSync.Server)); } } 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, Rand.RandSync.Server)]; //randomly take steps further away from the startpoint to make the cave expand further if (Rand.Int(4, Rand.RandSync.Server) == 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); }
private void HandleSubCollision(Impact impact, Submarine otherSub) { Debug.Assert(otherSub != submarine); Vector2 normal = impact.Normal; if (impact.Target.Body == otherSub.SubBody.Body.FarseerBody) { normal = -normal; } float thisMass = Body.Mass + submarine.DockedTo.Sum(s => s.PhysicsBody.Mass); float otherMass = otherSub.PhysicsBody.Mass + otherSub.DockedTo.Sum(s => s.PhysicsBody.Mass); float massRatio = otherMass / (thisMass + otherMass); float impulse = (Vector2.Dot(impact.Velocity, normal) / 2.0f) * massRatio; //apply impact to this sub (the other sub takes care of this in its own collision callback) ApplyImpact(impulse, normal, impact.ImpactPos); foreach (Submarine dockedSub in submarine.DockedTo) { dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos); } //find all contacts between this sub and level walls List <Contact> levelContacts = new List <Contact>(); ContactEdge contactEdge = Body?.FarseerBody?.ContactList; while (contactEdge?.Next != null) { if (contactEdge.Contact.Enabled && contactEdge.Other.UserData is VoronoiCell && contactEdge.Contact.IsTouching) { levelContacts.Add(contactEdge.Contact); } contactEdge = contactEdge.Next; } if (levelContacts.Count == 0) { return; } //if this sub is in contact with the level, apply artifical impacts //to both subs to prevent the other sub from bouncing on top of this one //and to fake the other sub "crushing" this one against a wall Vector2 avgContactNormal = Vector2.Zero; foreach (Contact levelContact in levelContacts) { levelContact.GetWorldManifold(out Vector2 contactNormal, out FixedArray2 <Vector2> temp); //if the contact normal is pointing from the sub towards the level cell we collided with, flip the normal VoronoiCell cell = levelContact.FixtureB.UserData is VoronoiCell ? ((VoronoiCell)levelContact.FixtureB.UserData) : ((VoronoiCell)levelContact.FixtureA.UserData); var cellDiff = ConvertUnits.ToDisplayUnits(Body.SimPosition) - cell.Center; if (Vector2.Dot(contactNormal, cellDiff) < 0) { contactNormal = -contactNormal; } avgContactNormal += contactNormal; //apply impacts at the positions where this sub is touching the level ApplyImpact((Vector2.Dot(impact.Velocity, contactNormal) / 2.0f) * massRatio / levelContacts.Count, contactNormal, impact.ImpactPos); } avgContactNormal /= levelContacts.Count; //apply an impact to the other sub float contactDot = Vector2.Dot(otherSub.PhysicsBody.LinearVelocity, -avgContactNormal); if (contactDot > 0.0f) { if (otherSub.PhysicsBody.LinearVelocity.LengthSquared() > 0.0001f) { otherSub.PhysicsBody.LinearVelocity -= Vector2.Normalize(otherSub.PhysicsBody.LinearVelocity) * contactDot; } impulse = Vector2.Dot(otherSub.Velocity, normal); otherSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos); foreach (Submarine dockedSub in otherSub.DockedTo) { dockedSub.SubBody.ApplyImpact(impulse, normal, impact.ImpactPos); } } }
public static Vector3 GetSolidEdgeMiddle(VoronoiCell cell, VoronoiDirection direction) { return((cell.Corners[direction] + cell.Corners[direction + 1]) * (0.5f * SolidFactor)); }
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); }
public static float InnerRadius(VoronoiCell cell, VoronoiDirection direction) { return(Vector3.Lerp(cell.Corners[direction], cell.Corners[direction + 1], 0.5f).magnitude); }
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) { var adjacentCell = edge.AdjacentCell(currentCell); if (limits.Contains(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y)) { allowedEdges.Add(edge); } } //steer towards target if (Rand.Range(0.0f, 1.0f, Rand.RandSync.Server) > wanderAmount || allowedEdges.Count == 0) { double smallestDist = double.PositiveInfinity; for (int i = 0; i < currentCell.Edges.Count; i++) { var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell); double dist = MathUtils.Distance( adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y); if (dist < smallestDist) { edgeIndex = i; smallestDist = dist; } } } //choose random edge (ignoring ones where the adjacent cell is outside limits) else { edgeIndex = Rand.Int(allowedEdges.Count, Rand.RandSync.Server); 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 float InnerToOuter(VoronoiCell cell, VoronoiDirection direction) { return(1f / OuterToInner(cell, direction)); }
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 (Vector2.DistanceSquared(ge.Point1, ge.Point2) < 0.001f) { 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); } } //add edges to the borders of the graph foreach (var cell in cells) { Vector2?point1 = null, point2 = null; foreach (GraphEdge ge in cell.Edges) { if (MathUtils.NearlyEqual(ge.Point1.X, borders.X) || MathUtils.NearlyEqual(ge.Point1.X, borders.Right) || MathUtils.NearlyEqual(ge.Point1.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point1.Y, borders.Bottom)) { if (point1 == null) { point1 = ge.Point1; } else if (point2 == null) { if (MathUtils.NearlyEqual(point1.Value, ge.Point1)) { continue; } point2 = ge.Point1; } } if (MathUtils.NearlyEqual(ge.Point2.X, borders.X) || MathUtils.NearlyEqual(ge.Point2.X, borders.Right) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Y) || MathUtils.NearlyEqual(ge.Point2.Y, borders.Bottom)) { if (point1 == null) { point1 = ge.Point2; } else { if (MathUtils.NearlyEqual(point1.Value, ge.Point2)) { continue; } point2 = ge.Point2; } } if (point1.HasValue && point2.HasValue) { Debug.Assert(point1 != point2); bool point1OnSide = MathUtils.NearlyEqual(point1.Value.X, borders.X) || MathUtils.NearlyEqual(point1.Value.X, borders.Right); bool point2OnSide = MathUtils.NearlyEqual(point2.Value.X, borders.X) || MathUtils.NearlyEqual(point2.Value.X, borders.Right); //one point is one the side, another on top/bottom // -> the cell is in the corner of the level, we need 2 edges if (point1OnSide != point2OnSide) { Vector2 cornerPos = new Vector2( point1.Value.X < borders.Center.X ? borders.X : borders.Right, point1.Value.Y < borders.Center.Y ? borders.Y : borders.Bottom); cell.Edges.Add( new GraphEdge(point1.Value, cornerPos) { Cell1 = cell, IsSolid = true, Site1 = cell.Site, OutsideLevel = true }); cell.Edges.Add( new GraphEdge(point2.Value, cornerPos) { Cell1 = cell, IsSolid = true, Site1 = cell.Site, OutsideLevel = true }); } else { cell.Edges.Add( new GraphEdge(point1.Value, point2.Value) { Cell1 = cell, IsSolid = true, Site1 = cell.Site, OutsideLevel = true }); } break; } } } return(cells); }
public static Vector3 GetSecondSolidCorner(VoronoiCell cell, VoronoiDirection direction) { return(cell.Corners[direction + 1] * SolidFactor); }
public static List <VoronoiCell> GeneratePath(List <VoronoiCell> targetCells, List <VoronoiCell> cells) { Stopwatch sw2 = new Stopwatch(); sw2.Start(); List <VoronoiCell> pathCells = new List <VoronoiCell>(); if (targetCells.Count == 0) { return(pathCells); } VoronoiCell currentCell = targetCells[0]; currentCell.CellType = CellType.Path; pathCells.Add(currentCell); int currentTargetIndex = 0; int iterationsLeft = cells.Count / 2; do { int edgeIndex = 0; double smallestDist = double.PositiveInfinity; for (int i = 0; i < currentCell.Edges.Count; i++) { var adjacentCell = currentCell.Edges[i].AdjacentCell(currentCell); if (adjacentCell == null) { continue; } double dist = MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, targetCells[currentTargetIndex].Site.Coord.X, targetCells[currentTargetIndex].Site.Coord.Y); dist += MathUtils.Distance(adjacentCell.Site.Coord.X, adjacentCell.Site.Coord.Y, currentCell.Site.Coord.X, currentCell.Site.Coord.Y) * 0.5f; //disfavor short edges to prevent generating a very small passage if (Vector2.DistanceSquared(currentCell.Edges[i].Point1, currentCell.Edges[i].Point2) < 150.0f * 150.0f) { //divide by the number of times the current cell has been used // prevents the path from getting "stuck" (jumping back and forth between adjacent cells) // if there's no other way to the destination than going through a short edge dist *= 10.0f / Math.Max(pathCells.Count(c => c == currentCell), 1.0f); } if (dist < smallestDist) { edgeIndex = i; smallestDist = dist; } } currentCell = currentCell.Edges[edgeIndex].AdjacentCell(currentCell); currentCell.CellType = CellType.Path; pathCells.Add(currentCell); iterationsLeft--; if (currentCell == targetCells[currentTargetIndex]) { currentTargetIndex++; if (currentTargetIndex >= targetCells.Count) { break; } } } while (currentCell != targetCells[targetCells.Count - 1] && iterationsLeft > 0); Debug.WriteLine("gettooclose: " + sw2.ElapsedMilliseconds + " ms"); sw2.Restart(); return(pathCells); }
/// <summary> /// Makes the cell rounder by subdividing the edges and offsetting them at the middle /// </summary> /// <param name="minEdgeLength">How small the individual subdivided edges can be (smaller values produce rounder shapes, but require more geometry)</param> public static void RoundCell(VoronoiCell cell, float minEdgeLength = 500.0f, float roundingAmount = 0.5f, float irregularity = 0.1f) { List <GraphEdge> tempEdges = new List <GraphEdge>(); foreach (GraphEdge edge in cell.Edges) { if (!edge.IsSolid || edge.OutsideLevel) { tempEdges.Add(edge); continue; } //If the edge is next to an empty cell and there's another solid cell at the other side of the empty one, //don't touch this edge. Otherwise we may end up closing off small passages between cells. var adjacentEmptyCell = edge.AdjacentCell(cell); if (adjacentEmptyCell?.CellType == CellType.Solid) { adjacentEmptyCell = null; } if (adjacentEmptyCell != null) { GraphEdge adjacentEdge = null; //find the edge at the opposite side of the adjacent cell foreach (GraphEdge otherEdge in adjacentEmptyCell.Edges) { if (Vector2.Dot(adjacentEmptyCell.Center - edge.Center, adjacentEmptyCell.Center - otherEdge.Center) < 0 && otherEdge.AdjacentCell(adjacentEmptyCell)?.CellType == CellType.Solid) { adjacentEdge = otherEdge; break; } } if (adjacentEdge != null) { tempEdges.Add(edge); continue; } } List <Vector2> edgePoints = new List <Vector2>(); Vector2 edgeNormal = edge.GetNormal(cell); float edgeLength = Vector2.Distance(edge.Point1, edge.Point2); int pointCount = (int)Math.Max(Math.Ceiling(edgeLength / minEdgeLength), 1); Vector2 edgeDir = edge.Point2 - edge.Point1; for (int i = 0; i <= pointCount; i++) { if (i == 0) { edgePoints.Add(edge.Point1); } else if (i == pointCount) { edgePoints.Add(edge.Point2); } else { float centerF = 0.5f - Math.Abs(0.5f - (i / (float)pointCount)); float randomVariance = Rand.Range(0, irregularity, Rand.RandSync.Server); Vector2 extrudedPoint = edge.Point1 + edgeDir * (i / (float)pointCount) + edgeNormal * edgeLength * (roundingAmount + randomVariance) * centerF; var nearbyCells = Level.Loaded.GetCells(extrudedPoint, searchDepth: 2); bool isInside = false; foreach (var nearbyCell in nearbyCells) { if (nearbyCell == cell || nearbyCell.CellType != CellType.Solid) { continue; } //check if extruding the edge causes it to go inside another one if (nearbyCell.IsPointInside(extrudedPoint)) { isInside = true; break; } //check if another edge will be inside this cell after the extrusion Vector2 triangleCenter = (edge.Point1 + edge.Point2 + extrudedPoint) / 3; foreach (GraphEdge nearbyEdge in nearbyCell.Edges) { if (!MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, extrudedPoint) && !MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point2, extrudedPoint) && !MathUtils.LinesIntersect(nearbyEdge.Point1, triangleCenter, edge.Point1, edge.Point2)) { isInside = true; break; } } if (isInside) { break; } } if (!isInside) { edgePoints.Add(extrudedPoint); } } } for (int i = 0; i < edgePoints.Count - 1; i++) { tempEdges.Add(new GraphEdge(edgePoints[i], edgePoints[i + 1]) { Cell1 = edge.Cell1, Cell2 = edge.Cell2, IsSolid = edge.IsSolid, Site1 = edge.Site1, Site2 = edge.Site2, OutsideLevel = edge.OutsideLevel, NextToCave = edge.NextToCave, NextToMainPath = edge.NextToMainPath, NextToSidePath = edge.NextToSidePath }); } } cell.Edges = tempEdges; }
public static List <Body> GeneratePolygons(List <VoronoiCell> cells, Level level, out List <Vector2[]> renderTriangles, bool setSolid = true) { renderTriangles = new List <Vector2[]>(); var bodies = new List <Body>(); List <Vector2> tempVertices = new List <Vector2>(); List <Vector2> bodyPoints = new List <Vector2>(); Body cellBody = new Body(GameMain.World) { SleepingAllowed = false, BodyType = BodyType.Static, CollisionCategories = Physics.CollisionLevel }; bodies.Add(cellBody); 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; } renderTriangles.AddRange(MathUtils.TriangulateConvexHull(tempVertices, cell.Center)); 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; } cellBody.UserData = cell; var triangles = MathUtils.TriangulateConvexHull(bodyPoints, ConvertUnits.ToSimUnits(cell.Center)); 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.DistanceSquared(triangles[i][0], triangles[i][1]) < 0.006f || Vector2.DistanceSquared(triangles[i][0], triangles[i][2]) < 0.006f || Vector2.DistanceSquared(triangles[i][1], triangles[i][2]) < 0.006f) { continue; } Vertices bodyVertices = new Vertices(triangles[i]); var newFixture = FixtureFactory.AttachPolygon(bodyVertices, 5.0f, cellBody); newFixture.UserData = cell; if (newFixture.Shape.MassData.Area < FarseerPhysics.Settings.Epsilon) { DebugConsole.ThrowError("Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + ")"); GameAnalyticsManager.AddErrorEventOnce( "CaveGenerator.GeneratePolygons:InvalidTriangle", GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, "Invalid triangle created by CaveGenerator (" + triangles[i][0] + ", " + triangles[i][1] + ", " + triangles[i][2] + "). Seed: " + level.Seed); } } cell.body = cellBody; } return(bodies); }