public static float IncludedAngleCos(Vector2 v1, Vector2 v2) { v1.Normalize(); v2.Normalize(); float dot = Vector2.Dot(v1, v2); return(2 * dot / (v1.Length() + v2.Length())); }
private KeyValuePair <HalfEdge, float>?FindFirstParallelEdge(Seed seed, float length, float distance, float parallelThreshold) { Contract.Requires(seed != null); var start = seed.Origin.Position; var end = seed.Origin.Position + seed.Direction * length; var segment = new LineSegment2(start, end); //Calculate the expanded bounds to query. This is as wide as the parallel check distance var p = seed.Direction.Perpendicular() * distance / 2; var a = start + p; var b = start - p; var c = end + p; var d = end - p; var queryBounds = new BoundingRectangle( Vector2.Min(Vector2.Min(a, b), Vector2.Min(c, d)), Vector2.Max(Vector2.Max(a, b), Vector2.Max(c, d)) ); //now get all lines which intersect this bounds and check them for parallelism var candidates = _mesh.FindEdges(queryBounds); KeyValuePair <HalfEdge, float>?firstParallel = null; foreach (var candidate in candidates) { var dirCandidate = candidate.Segment.Line.Direction; var dir = segment.Line.Direction; //Dot product directions of lines to check parallelism (compare with threshold) var dot = Math.Abs(Vector2.Dot(dir, dirCandidate)); if (dot > parallelThreshold) { //Our query bounds were larger than the actual area we wanted to query (because we're limited to axis aligned bounds) //Check that this line enters the smaller OABB area //We'll do this check by checking if the line segment intersects any of the four OABB segments (AB, BC, CD, DA) if (new LineSegment2(a, b).Intersects(candidate.Segment).HasValue || new LineSegment2(b, c).Intersects(candidate.Segment).HasValue || new LineSegment2(c, d).Intersects(candidate.Segment).HasValue || new LineSegment2(d, a).Intersects(candidate.Segment).HasValue) { //check how far along this segment the parallelism starts var startDist = segment.ClosestPointDistanceAlongSegment(candidate.StartVertex.Position); var endDist = segment.ClosestPointDistanceAlongSegment(candidate.EndVertex.Position); var minDist = Math.Min(startDist, endDist); if (firstParallel == null || minDist < firstParallel.Value.Value) { firstParallel = new KeyValuePair <HalfEdge, float>(candidate, minDist); } } } } return(firstParallel); }
private void CreateOutline(IEnumerable <Vector2> shape, bool createFace) { Contract.Requires(shape != null); var vertices = (IReadOnlyList <Vertex>)shape.Select(_mesh.GetOrConstructVertex).ToArray(); var edges = new List <HalfEdge>(vertices.Count * 3); // Create the outer edges of the floor for (var i = 0; i < vertices.Count; i++) { //Start and end vertex of this wall var b = vertices[i]; var c = vertices[(i + 1) % vertices.Count]; //Create a series of edges between these two vertices (not just one edge, because we drop seeds along the line as we go) CreateImpassableEdge(b, c, edges); //We want to measure the internal angle at vertex "b", for that we need the previous vertex (which we'll call "a") var a = vertices[(i + vertices.Count - 1) % vertices.Count]; //Calculate the inner angle between these vectors (not always clockwise!) var ab = Vector2.Normalize(b.Position - a.Position); var bc = Vector2.Normalize(c.Position - b.Position); var dot = Vector2.Dot(bc, -ab); var det = bc.Cross(-ab); var angle = (float)(Math.Atan2(det, dot) % (Math.PI * 2)); angle = det < 0 ? angle * -1 : (float)Math.PI * 2 - angle; if (angle < Math.PI * 0.51) { //0 -> 90 degrees //Do nothing! } else if (angle <= Math.PI * 1.01) { //90 -> 180 if (_random.RandomBoolean()) { PerpendicularSeed(b, ab); } else { PerpendicularSeed(b, bc); } } else if (angle <= Math.PI * 1.51) { //180 -> 270 //if (_random.RandomBoolean()) // BisectorSeed(b, -ab, -bc); //Negated, to ensure bisection is on the correct side (angle is > 180, so by default bisection would be on wrong side) //else { PerpendicularSeed(b, ab); PerpendicularSeed(b, bc); } } else { //270 -> 360 //BisectorSeed(b, -ab, -bc); //Negated, to ensure bisection is on the correct side (angle is > 180, so by default bisection would be on wrong side) PerpendicularSeed(b, ab); PerpendicularSeed(b, bc); } } if (createFace) { //Ensure we're always creating the clockwise face if (!edges.Select(a => a.EndVertex.Position).IsClockwise()) { edges.Reverse(); for (var i = 0; i < edges.Count; i++) { edges[i] = edges[i].Pair; } } //Create face //todo: attach spacespec metadata (passed in instead of bool:createFace) var f = _mesh.GetOrConstructFace(edges); f.Tag = new FloorplanFaceTag(false); } }
private void GrowSeed(Seed seed) { Contract.Requires(seed != null); //Decide how far we're going to grow this seed var length = _seedDistance.SelectFloatValue(_random, _metadata); if (length < 0) { throw new InvalidOperationException("Seed distance must be > 0"); } //Find the first edge which is parallel with this one var firstParallel = FindFirstParallelEdge( seed, length * _parallelLengthMultiplier.SelectFloatValue(_random, _metadata), _parallelCheckWidth.SelectFloatValue(_random, _metadata), _cosineParallelAngleThreshold.SelectFloatValue(_random, _metadata) ); //Check for intersections with other edges var firstIntersection = FindFirstIntersection(seed, length + _seedDistance.MinValue); //Reject seeds with parallel edges in certain circumstances if (firstParallel.HasValue) { //There's a parallel edge and we don't intersect anything, so just ignore this seed altogether if (!firstIntersection.HasValue) { return; } //There's a parallel edge and the first intersection if *after* the parallelism starts, so ignore this seed altogether if (firstIntersection.Value.Value.DistanceAlongB > firstParallel.Value.Value) { return; } } if (firstIntersection.HasValue) { var intersectVert = _mesh.GetOrConstructVertex(firstIntersection.Value.Value.Position); if (intersectVert.Equals(seed.Origin)) { return; } //If the edge doesn't contain this vertex already then split the edge we've hit if (!firstIntersection.Value.Key.ConnectsTo(intersectVert)) { HalfEdge ab, bc; _mesh.Split(firstIntersection.Value.Key, intersectVert, out ab, out bc); var t = firstIntersection.Value.Key.Tag ?? firstIntersection.Value.Key.Pair.Tag; ab.Tag = new FloorplanHalfEdgeTag(t.IsImpassable); bc.Tag = new FloorplanHalfEdgeTag(t.IsImpassable); } //Create an edge to this intersection point _mesh.GetOrConstructHalfEdge(seed.Origin, intersectVert).Tag = new FloorplanHalfEdgeTag(false); //If this is not an external wall we can create a seed continuing forward var tag = firstIntersection.Value.Key.Tag ?? firstIntersection.Value.Key.Pair.Tag; var continuationChance = _intersectionContinuationChance.SelectFloatValue(_random, _metadata); if (!tag.IsImpassable && _random.RandomBoolean(1 - continuationChance)) { //New wall will be perpendicular to the wall we've hit... var direction = firstIntersection.Value.Key.Segment.Line.Direction.Perpendicular(); //...but which perpendicular? var dotRight = Vector2.Dot(direction, seed.Direction); var dotLeft = Vector2.Dot(-direction, seed.Direction); if (dotLeft > dotRight) { direction *= -1; } //create new seed var wallLength = Vector2.Distance(seed.Origin.Position, intersectVert.Position); CreateSeed(intersectVert, direction, seed.T + wallLength); } } else { //Create edge along this distance var end = _mesh.GetOrConstructVertex(seed.Origin.Position + seed.Direction * length); _mesh.GetOrConstructHalfEdge(seed.Origin, end).Tag = new FloorplanHalfEdgeTag(false); float seedChance = _seedChance.SelectFloatValue(_random, _metadata); if (_random.RandomBoolean(seedChance)) { CreateSeed(end, seed.Direction, seed.T + length); } else { // Choose which directions to grow in (LF, LR, FR, LFR) we're going to do var newWalls = _random.RandomInteger(0, 3); //Put some seeds at the end (right, left and straight on) if (newWalls != 0) { CreateSeed(end, seed.Direction.Perpendicular(), seed.T + length); } if (newWalls != 2) { CreateSeed(end, -seed.Direction.Perpendicular(), seed.T + length); } if (newWalls != 1) { CreateSeed(end, seed.Direction, seed.T + length); } } } }