public static void CalculateBounds(ref Rectangle rect, Vector2 parentPosition, Vector2 position, Vector2 origin, Vector2 scale, float rotation, float width, float height) { if (rotation == 0f) { rect.X = (int)(parentPosition.X + position.X - origin.X * scale.X); rect.Y = (int)(parentPosition.Y + position.Y - origin.Y * scale.Y); rect.Width = (int)(width * scale.X); rect.Height = (int)(height * scale.Y); } else { // special care for rotated bounds. we need to find our absolute min/max values and create the bounds from that var worldPosX = parentPosition.X + position.X; var worldPosY = parentPosition.Y + position.Y; Matrix2D tempMat; // set the reference point to world reference taking origin into account var transformMatrix = Matrix2D.CreateTranslation(-worldPosX - origin.X, -worldPosY - origin.Y); Matrix2D.CreateScale(scale.X, scale.Y, out tempMat); // scale -> Matrix2D.Multiply(ref transformMatrix, ref tempMat, out transformMatrix); Matrix2D.CreateRotation(rotation, out tempMat); // rotate -> Matrix2D.Multiply(ref transformMatrix, ref tempMat, out transformMatrix); Matrix2D.CreateTranslation(worldPosX, worldPosY, out tempMat); // translate back Matrix2D.Multiply(ref transformMatrix, ref tempMat, out transformMatrix); // TODO: this is a bit silly. we can just leave the worldPos translation in the Matrix and avoid this // get all four corners in world space var topLeft = new Vector2(worldPosX, worldPosY); var topRight = new Vector2(worldPosX + width, worldPosY); var bottomLeft = new Vector2(worldPosX, worldPosY + height); var bottomRight = new Vector2(worldPosX + width, worldPosY + height); // transform the corners into our work space Vector2Ext.Transform(ref topLeft, ref transformMatrix, out topLeft); Vector2Ext.Transform(ref topRight, ref transformMatrix, out topRight); Vector2Ext.Transform(ref bottomLeft, ref transformMatrix, out bottomLeft); Vector2Ext.Transform(ref bottomRight, ref transformMatrix, out bottomRight); // find the min and max values so we can concoct our bounding box var minX = (int)Mathf.MinOf(topLeft.X, bottomRight.X, topRight.X, bottomLeft.X); var maxX = (int)Mathf.MaxOf(topLeft.X, bottomRight.X, topRight.X, bottomLeft.X); var minY = (int)Mathf.MinOf(topLeft.Y, bottomRight.Y, topRight.Y, bottomLeft.Y); var maxY = (int)Mathf.MaxOf(topLeft.Y, bottomRight.Y, topRight.Y, bottomLeft.Y); rect.Location = new Point(minX, minY); rect.Width = (int)(maxX - minX); rect.Height = (int)(maxY - minY); } }
public void SetFusedData(bool shouldFuseBottom, ref Segment segment) { // store the angle off for later. For extreme angles we add extra verts to smooth the joint Angle = Vector2Ext.Angle(segment.Point.Position - Point.Position, NextPoint.Position - Point.Position); ShouldFuseBottom = shouldFuseBottom; if (shouldFuseBottom) { HasFusedPoint = ShapeCollisions.LineToLine(segment.Bl, segment.Br, Bl, Br, out FusedPoint); } else { HasFusedPoint = ShapeCollisions.LineToLine(segment.Tl, segment.Tr, Tl, Tr, out FusedPoint); } }
public static void DrawHollowRect(this Batcher batcher, float x, float y, float width, float height, Color color, float thickness = 1) { var tl = Vector2Ext.Round(new Vector2(x, y)); var tr = Vector2Ext.Round(new Vector2(x + width, y)); var br = Vector2Ext.Round(new Vector2(x + width, y + height)); var bl = Vector2Ext.Round(new Vector2(x, y + height)); batcher.SetIgnoreRoundingDestinations(true); batcher.DrawLine(tl, tr, color, thickness); batcher.DrawLine(tr, br, color, thickness); batcher.DrawLine(br, bl, color, thickness); batcher.DrawLine(bl, tl, color, thickness); batcher.SetIgnoreRoundingDestinations(false); }
public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 position, float radius, Color color, float thickness = 1f, int resolution = 12) { var last = Vector2.UnitX * radius; var lastP = Vector2Ext.Perpendicular(last); for (int i = 1; i <= resolution; i++) { var at = Mathf.AngleToVector(i * MathHelper.PiOver2 / resolution, radius); var atP = Vector2Ext.Perpendicular(at); DrawLine(spriteBatch, position + last, position + at, color, thickness); DrawLine(spriteBatch, position - last, position - at, color, thickness); DrawLine(spriteBatch, position + lastP, position + atP, color, thickness); DrawLine(spriteBatch, position - lastP, position - atP, color, thickness); last = at; lastP = atP; } }
void PatchJaggedJoint(ref Segment segment, ref int vertIndex) { Vector2 intersection; if (segment.ShouldFuseBottom) { if (Vector2Ext.GetRayIntersection(segment.Tl, segment.Tr, _lastSegment.Tl, _lastSegment.Tr, out intersection)) { AddVert(vertIndex++, intersection, new Vector2(1, 1), segment.Point.Color); _indices.Add((short)vertIndex); _indices.Add((short)(vertIndex + 4)); _indices.Add((short)(vertIndex - 1)); _indices.Add((short)(vertIndex - 1)); _indices.Add((short)(vertIndex + 4)); _indices.Add((short)(vertIndex - 5)); } } else { if (Vector2Ext.GetRayIntersection(segment.Bl, segment.Br, _lastSegment.Bl, _lastSegment.Br, out intersection)) { var firstSegmentOffset = vertIndex == 5 ? 1 : 0; AddVert(vertIndex++, intersection, new Vector2(1, 0), segment.Point.Color); _indices.Add((short)(vertIndex + 4)); _indices.Add((short)(vertIndex + 3)); _indices.Add((short)(vertIndex - 1)); _indices.Add((short)(vertIndex - 3 + firstSegmentOffset)); _indices.Add((short)(vertIndex + 4)); _indices.Add((short)(vertIndex - 1)); } } }
public static bool TestPointTriangle(Vector2 point, Vector2 a, Vector2 b, Vector2 c) { // if point to the right of AB then outside triangle if (Vector2Ext.Cross(point - a, b - a) < 0f) { return(false); } // if point to the right of BC then outside of triangle if (Vector2Ext.Cross(point - b, c - b) < 0f) { return(false); } // if point to the right of ca then outside of triangle if (Vector2Ext.Cross(point - c, a - c) < 0f) { return(false); } // point is in or on triangle return(true); }
public static void DrawCircle(this Batcher batcher, Vector2 position, float radius, Color color, float thickness = 1f, int resolution = 12) { var last = Vector2.UnitX * radius; var lastP = Vector2Ext.Perpendicular(last); batcher.SetIgnoreRoundingDestinations(true); for (int i = 1; i <= resolution; i++) { var at = Mathf.AngleToVector(i * MathHelper.PiOver2 / resolution, radius); var atP = Vector2Ext.Perpendicular(at); DrawLine(batcher, position + last, position + at, color, thickness); DrawLine(batcher, position - last, position - at, color, thickness); DrawLine(batcher, position + lastP, position + atP, color, thickness); DrawLine(batcher, position - lastP, position - atP, color, thickness); last = at; lastP = atP; } batcher.SetIgnoreRoundingDestinations(false); }
public void DrawInto(Batcher batcher, ref FontCharacterSource text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effect, float depth) { var flipAdjustment = Vector2.Zero; var flippedVert = (effect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically; var flippedHorz = (effect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally; if (flippedVert || flippedHorz) { Vector2 size; MeasureString(ref text, out size); if (flippedHorz) { origin.X *= -1; flipAdjustment.X = -size.X; } if (flippedVert) { origin.Y *= -1; flipAdjustment.Y = _font.LineSpacing - size.Y; } } // TODO: This looks excessive... i suspect we could do most of this with simple vector math and avoid this much matrix work. var requiresTransformation = flippedHorz || flippedVert || rotation != 0f || scale != Vector2.One; if (requiresTransformation) { Matrix2D temp; Matrix2D.CreateTranslation(-origin.X, -origin.Y, out _transformationMatrix); Matrix2D.CreateScale((flippedHorz ? -scale.X : scale.X), (flippedVert ? -scale.Y : scale.Y), out temp); Matrix2D.Multiply(ref _transformationMatrix, ref temp, out _transformationMatrix); Matrix2D.CreateTranslation(flipAdjustment.X, flipAdjustment.Y, out temp); Matrix2D.Multiply(ref temp, ref _transformationMatrix, out _transformationMatrix); Matrix2D.CreateRotation(rotation, out temp); Matrix2D.Multiply(ref _transformationMatrix, ref temp, out _transformationMatrix); Matrix2D.CreateTranslation(position.X, position.Y, out temp); Matrix2D.Multiply(ref _transformationMatrix, ref temp, out _transformationMatrix); } // Get the default glyph here once. SpriteFont.Glyph?defaultGlyph = null; if (_font.DefaultCharacter.HasValue) { defaultGlyph = _glyphs[_font.DefaultCharacter.Value]; } var currentGlyph = SpriteFont.Glyph.Empty; var offset = requiresTransformation ? Vector2.Zero : position - origin; var firstGlyphOfLine = true; for (var i = 0; i < text.Length; ++i) { var c = text[i]; if (c == '\r') { continue; } if (c == '\n') { offset.X = requiresTransformation ? 0f : position.X - origin.X; offset.Y += _font.LineSpacing; firstGlyphOfLine = true; continue; } if (!_glyphs.TryGetValue(c, out currentGlyph)) { if (!defaultGlyph.HasValue) { throw new ArgumentException("Errors.TextContainsUnresolvableCharacters", "text"); } currentGlyph = defaultGlyph.Value; } // The first character on a line might have a negative left side bearing. // In this scenario, SpriteBatch/SpriteFont normally offset the text to the right, // so that text does not hang off the left side of its rectangle. if (firstGlyphOfLine) { offset.X += Math.Max(currentGlyph.LeftSideBearing, 0); firstGlyphOfLine = false; } else { offset.X += _font.Spacing + currentGlyph.LeftSideBearing; } var p = offset; if (flippedHorz) { p.X += currentGlyph.BoundsInTexture.Width; } p.X += currentGlyph.Cropping.X; if (flippedVert) { p.Y += currentGlyph.BoundsInTexture.Height - _font.LineSpacing; } p.Y += currentGlyph.Cropping.Y; // transform our point if we need to if (requiresTransformation) { Vector2Ext.Transform(ref p, ref _transformationMatrix, out p); } var destRect = RectangleExt.FromFloats(p.X, p.Y, currentGlyph.BoundsInTexture.Width * scale.X, currentGlyph.BoundsInTexture.Height * scale.Y); batcher.Draw(_font.Texture, destRect, currentGlyph.BoundsInTexture, color, rotation, Vector2.Zero, effect, depth); offset.X += currentGlyph.Width + currentGlyph.RightSideBearing; } }
public static Point RoundToPoint(this Vector2 vec) { var roundedVec = Vector2Ext.Round(vec); return(new Point((int)roundedVec.X, (int)roundedVec.Y)); }
/// <summary> /// Computes a triangle list that fully covers the area enclosed by the given set of points. If points are not CCW, pass false for /// the arePointsCCW parameter /// </summary> /// <param name="points">A list of points that defines an enclosing path.</param> /// <param name="count">The number of points in the path.</param> public void Triangulate(Vector2[] points, bool arePointsCCW = true) { var count = points.Length; // set up previous and next links to effectively from a double-linked vert list Initialize(count); // loop breaker for polys that are not triangulatable var iterations = 0; // start at vert 0 var index = 0; // keep removing verts until just a triangle is left while (count > 3 && iterations < 500) { iterations++; // test if current vert is an ear var isEar = true; var a = points[_triPrev[index]]; var b = points[index]; var c = points[_triNext[index]]; // an ear must be convex (here counterclockwise) if (Vector2Ext.IsTriangleCCW(a, b, c)) { // loop over all verts not part of the tentative ear var k = _triNext[_triNext[index]]; do { // if vert k is inside the ear triangle, then this is not an ear if (TestPointTriangle(points[k], a, b, c)) { isEar = false; break; } k = _triNext[k]; } while (k != _triPrev[index]); } else { // the ear triangle is clockwise so points[i] is not an ear isEar = false; } // if current vert is an ear, delete it and visit the previous vert if (isEar) { // triangle is an ear TriangleIndices.Add(_triPrev[index]); TriangleIndices.Add(index); TriangleIndices.Add(_triNext[index]); // delete vert by redirecting next and previous links of neighboring verts past it // decrement vertext count _triNext[_triPrev[index]] = _triNext[index]; _triPrev[_triNext[index]] = _triPrev[index]; count--; // visit the previous vert next index = _triPrev[index]; } else { // current vert is not an ear. visit the next vert index = _triNext[index]; } } // output the final triangle TriangleIndices.Add(_triPrev[index]); TriangleIndices.Add(index); TriangleIndices.Add(_triNext[index]); if (!arePointsCCW) { TriangleIndices.Reverse(); } }
void CalculateVertices() { if (!_areVertsDirty || _points.Length < 2) { return; } _areVertsDirty = false; _indices.Reset(); _vertices.Reset(); var maxX = float.MinValue; var minX = float.MaxValue; var maxY = float.MinValue; var minY = float.MaxValue; if (_useStartEndWidths) { _maxWidth = System.Math.Max(_startWidth, _endWidth); } // calculate line length first and simulataneously get our min/max points for the bounds var lineLength = 0f; var halfMaxWidth = _maxWidth * 0.5f; _points.Buffer[0].LengthFromPreviousPoint = 0; for (var i = 0; i < _points.Length - 1; i++) { var distance = Vector2.Distance(_points.Buffer[i].Position, _points.Buffer[i + 1].Position); _points.Buffer[i + 1].LengthFromPreviousPoint = distance; lineLength += distance; maxX = Mathf.MaxOf(maxX, _points.Buffer[i].Position.X + halfMaxWidth, _points.Buffer[i + 1].Position.X + halfMaxWidth); minX = Mathf.MinOf(minX, _points.Buffer[i].Position.X - halfMaxWidth, _points.Buffer[i + 1].Position.X - halfMaxWidth); maxY = Mathf.MaxOf(maxY, _points.Buffer[i].Position.Y + halfMaxWidth, _points.Buffer[i + 1].Position.Y + halfMaxWidth); minY = Mathf.MinOf(minY, _points.Buffer[i].Position.Y - halfMaxWidth, _points.Buffer[i + 1].Position.Y - halfMaxWidth); } _bounds.X = minX; _bounds.Y = minY; _bounds.Width = maxX - minX; _bounds.Height = maxY - minY; // special case: single segment if (_points.Length == 2) { if (_useStartEndWidths) { _points.Buffer[0].Width = _startWidth; _points.Buffer[1].Width = _endWidth; } if (_useStartEndColors) { _points.Buffer[0].Color = _startColor; _points.Buffer[1].Color = _endColor; } _firstSegment.SetPoints(ref _points.Buffer[0], ref _points.Buffer[1]); AddSingleSegmentLine(ref _firstSegment, _points.Buffer[1].Color); return; } var distanceSoFar = 0f; var fusedPoint = Vector2.Zero; var vertIndex = 0; var thirdPoint = new SegmentPoint(); for (var i = 0; i < _points.Length - 1; i++) { var firstPoint = _points.Buffer[i]; var secondPoint = _points.Buffer[i + 1]; var hasThirdPoint = _points.Length > i + 2; if (hasThirdPoint) { thirdPoint = _points.Buffer[i + 2]; } // we need the distance along the line of both the first and second points. distanceSoFar will always be for the furthest point // which is the previous point before adding the current segment distance. var firstPointDistance = distanceSoFar; distanceSoFar += secondPoint.LengthFromPreviousPoint; var firstPointRatio = firstPointDistance / lineLength; var secondPointRatio = distanceSoFar / lineLength; var thirdPointRatio = 0f; if (hasThirdPoint) { thirdPointRatio = (distanceSoFar + thirdPoint.LengthFromPreviousPoint) / lineLength; } if (_useStartEndColors) { ColorExt.Lerp(ref _startColor, ref _endColor, out firstPoint.Color, firstPointRatio); ColorExt.Lerp(ref _startColor, ref _endColor, out secondPoint.Color, secondPointRatio); if (hasThirdPoint) { ColorExt.Lerp(ref _startColor, ref _endColor, out thirdPoint.Color, thirdPointRatio); } } if (_useStartEndWidths) { firstPoint.Width = Mathf.Lerp(_startWidth, _endWidth, firstPointRatio); secondPoint.Width = Mathf.Lerp(_startWidth, _endWidth, secondPointRatio); if (hasThirdPoint) { thirdPoint.Width = Mathf.Lerp(_startWidth, _endWidth, thirdPointRatio); } } if (i == 0) { _firstSegment.SetPoints(ref firstPoint, ref secondPoint); _secondSegment.SetPoints(ref secondPoint, ref thirdPoint); } else { Utils.Swap(ref _firstSegment, ref _secondSegment); if (hasThirdPoint) { _secondSegment.SetPoints(ref secondPoint, ref thirdPoint); } } // dont recalculate the fusedPoint for the last segment since there will be no third point to work with if (hasThirdPoint) { var shouldFuseBottom = Vector2Ext.IsTriangleCCW(firstPoint.Position, secondPoint.Position, thirdPoint.Position); _secondSegment.SetFusedData(shouldFuseBottom, ref _firstSegment); } // special care needs to be take with the first segment since it has a different vert count if (i == 0) { AddFirstSegment(ref _firstSegment, ref _secondSegment, ref vertIndex); } else { AddSegment(ref _firstSegment, ref vertIndex); } _lastSegment.CloneFrom(ref _firstSegment); } }
public override void OnAddedToEntity() { OriginNormalized = Vector2Ext.HalfVector(); }