using UnityEngine; using UnityEngine.Profiling; using System; using System.Collections.Generic; public delegate void SetPixelDelegate(int x, int y); public class SVGBasicDraw { private struct Vector2Ext { private readonly float _delta; private readonly Vector2 _point; public float t { get { return _delta; } } public Vector2 point { get { return _point; } } public Vector2Ext(Vector2 point, float t) { _point = point; _delta = t; } } private Vector2 _currentPoint; public SetPixelDelegate SetPixel; public Vector2 currentPoint { get { return _currentPoint; } } public SetPixelDelegate SetPixelMethod { set { SetPixel = value; } } public SVGBasicDraw() { _currentPoint = new Vector2(0f, 0f); } private static void Swap(ref T x1, ref T x2) { T temp = x1; x1 = x2; x2 = temp; } public void MoveTo(float x, float y) { _currentPoint.x = x; _currentPoint.y = y; } public void MoveTo(Vector2 p) { _currentPoint = p; } public void Line(int x0, int y0, int x1, int y1) { bool steep = (Math.Abs(y1 - y0) > Math.Abs(x1 - x0)); if(steep) { Swap(ref x0, ref y0); Swap(ref x1, ref y1); } if(x0 > x1) { Swap(ref x0, ref x1); Swap(ref y0, ref y1); } int deltax = x1 - x0, deltay = Math.Abs(y1 - y0), error = -(deltax + 1) / 2, y = y0, ystep = (y0 < y1) ? 1 : -1; for(int x = x0; x <= x1; x++) { if(steep) SetPixel(y, x); else SetPixel(x, y); error += deltay; if(error >= 0) { y += ystep; error -= deltax; } } } public void Line(float x0, float y0, float x1, float y1) { Line((int)x0, (int)y0, (int)x1, (int)y1); } public void Line(Vector2 p1, Vector2 p2) { Line(p1.x, p1.y, p2.x, p2.y); } public void LineTo(float x, float y) { Vector2 temp = new Vector2(x, y); Line(_currentPoint, temp); _currentPoint = temp; } public void LineTo(Vector2 p) { Line(_currentPoint, p); _currentPoint = p; } public void Rect(float x0, float y0, float x1, float y1) { MoveTo(x0, y0); LineTo(x1, y0); MoveTo(x1, y0); LineTo(x1, y1); MoveTo(x1, y1); LineTo(x0, y1); MoveTo(x0, y1); LineTo(x0, y0); } public void Rect(Vector2 p1, Vector2 p2) { Rect(p1.x, p1.y, p2.x, p2.y); } public void Rect(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) { MoveTo(p1); LineTo(p2); LineTo(p3); LineTo(p4); LineTo(p1); } private void Circle(int x0, int y0, float radius) { float chuvi = 2f * Mathf.PI * radius; int _delta = (int)(chuvi / 2f); if(_delta > 50) _delta = 50; float _angle = (2 * Mathf.PI) / _delta; float tx, ty, temp; tx = x0; ty = radius + y0; Vector2 fPoint = new Vector2(tx, ty); MoveTo(fPoint); for(int i = 1; i <= _delta; i++) { temp = i * _angle; tx = radius * (float)Math.Sin(temp) + x0; ty = radius * (float)Math.Cos(temp) + y0; Vector2 tPoint = new Vector2(tx, ty); LineTo(tPoint); } LineTo(fPoint); } public void Circle(float x0, float y0, float r) { Circle((int)x0, (int)y0, r); } public void Circle(Vector2 p, float r) { Circle((int)p.x, (int)p.y, r); } private void Ellipse(int cx, int cy, int rx, int ry, float angle) { float chuvi = 2f * Mathf.PI * (float)Math.Sqrt(rx * rx + ry * ry); int steps = (int)(chuvi / 3); if(steps > 50) steps = 50; float beta = angle * Mathf.Deg2Rad; float sinbeta = Mathf.Sin(beta); float cosbeta = Mathf.Cos(beta); steps = 360 / steps; int i = 0; float alpha = i * Mathf.Deg2Rad; float sinalpha = Mathf.Sin(alpha); float cosalpha = Mathf.Cos(alpha); float _x = cx + (rx * cosalpha * cosbeta - ry * sinalpha * sinbeta); float _y = cy + (rx * cosalpha * sinbeta + ry * sinalpha * cosbeta); float _fPointx = _x; float _fPointy = _y; MoveTo(_x, _y); for(i = 1; i < 360; i += steps) { alpha = i * Mathf.Deg2Rad; sinalpha = Mathf.Sin(alpha); cosalpha = Mathf.Cos(alpha); _x = cx + (rx * cosalpha * cosbeta - ry * sinalpha * sinbeta); _y = cy + (rx * cosalpha * sinbeta + ry * sinalpha * cosbeta); LineTo(_x, _y); } LineTo(_fPointx, _fPointy); } public void Ellipse(float x0, float y0, float rx, float ry, float angle) { Ellipse((int)x0, (int)y0, (int)rx, (int)ry, angle); } public void Ellipse(Vector2 p, float rx, float ry, float angle) { Ellipse((int)p.x, (int)p.y, (int)rx, (int)ry, angle); } public void Arc(Vector2 p1, float rx, float ry, float angle, bool largeArcFlag, bool sweepFlag, Vector2 p2) { Profiler.BeginSample("SVGBasicDraw.Arc(...)"); float tx, ty; double trx2, try2, tx2, ty2; float temp1, temp2; float _radian = (angle * Mathf.PI / 180.0f); float _CosRadian = (float)Math.Cos(_radian); float _SinRadian = (float)Math.Sin(_radian); temp1 = (p1.x - p2.x) / 2.0f; temp2 = (p1.y - p2.y) / 2.0f; tx = (_CosRadian * temp1) + (_SinRadian * temp2); ty = (-_SinRadian * temp1) + (_CosRadian * temp2); trx2 = rx * rx; try2 = ry * ry; tx2 = tx * tx; ty2 = ty * ty; double radiiCheck = tx2 / trx2 + ty2 / try2; if(radiiCheck > 1) { rx = (float)Math.Sqrt((float)radiiCheck) * rx; ry = (float)Math.Sqrt((float)radiiCheck) * ry; trx2 = rx * rx; try2 = ry * ry; } double tm1; tm1 = (trx2 * try2 - trx2 * ty2 - try2 * tx2) / (trx2 * ty2 + try2 * tx2); tm1 = (tm1 < 0) ? 0 : tm1; float tm2; tm2 = (largeArcFlag == sweepFlag) ? -(float)Math.Sqrt((float)tm1) : (float)Math.Sqrt((float)tm1); float tcx, tcy; tcx = tm2 * ((rx * ty) / ry); tcy = tm2 * (-(ry * tx) / rx); float cx, cy; cx = _CosRadian * tcx - _SinRadian * tcy + ((p1.x + p2.x) / 2.0f); cy = _SinRadian * tcx + _CosRadian * tcy + ((p1.y + p2.y) / 2.0f); float ux = (tx - tcx) / rx; float uy = (ty - tcy) / ry; float vx = (-tx - tcx) / rx; float vy = (-ty - tcy) / ry; float _angle, _delta; float p, n, t; n = (float)Math.Sqrt((ux * ux) + (uy * uy)); p = ux; _angle = (uy < 0) ? -(float)Math.Acos(p / n) : (float)Math.Acos(p / n); _angle = _angle * 180.0f / Mathf.PI; _angle %= 360f; n = (float)Math.Sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy)); p = ux * vx + uy * vy; t = p / n; if((Math.Abs(t) >= 0.99999f) && (Math.Abs(t) < 1.000009f)) { if(t > 0) t = 1f; else t = -1f; } _delta = (ux * vy - uy * vx < 0) ? -(float)Math.Acos(t) : (float)Math.Acos(t); _delta = _delta * 180.0f / Mathf.PI; if(!sweepFlag && _delta > 0) _delta -= 360f; else if(sweepFlag && _delta < 0) _delta += 360f; _delta %= 360f; int number = 100; float deltaT = _delta / number; Vector2 _point = new Vector2(0, 0); float t_angle; for(int i = 0; i <= number; i++) { t_angle = (deltaT * i + _angle) * Mathf.PI / 180.0f; _point.x = _CosRadian * rx * (float)Math.Cos(t_angle) - _SinRadian * ry * (float)Math.Sin(t_angle) + cx; _point.y = _SinRadian * rx * (float)Math.Cos(t_angle) + _CosRadian * ry * (float)Math.Sin(t_angle) + cy; LineTo(_point); } Profiler.EndSample(); } public void ArcTo(float r1, float r2, float angle, bool largeArcFlag, bool sweepFlag, Vector2 p) { Vector2 _tempPoint = new Vector2(_currentPoint.x, _currentPoint.y); Arc(_tempPoint, r1, r2, angle, largeArcFlag, sweepFlag, p); _currentPoint = p; } private static float BelongPosition(Vector2 a, Vector2 b, Vector2 c) { float _up = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y)); float _under = ((b.x - a.x) * (b.x - a.x)) + ((b.y - a.y) * (b.y - a.y)); float _r = _up / _under; return _r; } //Caculate Distance from c point to line segment [a,b] //return d point is the point on that line segment. private static int NumberOfLimitForCubic(Vector2 a, Vector2 b, Vector2 c, Vector2 d) { float _r1 = BelongPosition(a, d, b); float _r2 = BelongPosition(a, d, c); if((_r1 * _r2) > 0) return 0; return 1; } private static float Distance(Vector2 a, Vector2 b, Vector2 c) { float _up = ((a.y - c.y) * (b.x - a.x)) - ((a.x - c.x) * (b.y - a.y)); float _under = ((b.x - a.x) * (b.x - a.x)) + ((b.y - a.y) * (b.y - a.y)); return Math.Abs(_up / _under) * (float)Math.Sqrt(_under); } private static Vector2 EvaluateForCubic(float t, Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) { Vector2 result = new Vector2(0, 0); float b0 = (1.0f - t); float b1 = b0 * b0 * b0; float b2 = 3 * t * b0 * b0; float b3 = 3 * t * t * b0; float b4 = t * t * t; result.x = b1 * p1.x + b2 * p2.x + b3 * p3.x + b4 * p4.x; result.y = b1 * p1.y + b2 * p2.y + b3 * p3.y + b4 * p4.y; return result; } private static Vector2 EvaluateForQuadratic(float t, Vector2 p1, Vector2 p2, Vector2 p3) { Vector2 result = Vector2.zero; float b0 = (1.0f - t); float b1 = b0 * b0; float b2 = 2 * t * b0; float b3 = t * t; result.x = b1 * p1.x + b2 * p2.x + b3 * p3.x; result.y = b1 * p1.y + b2 * p2.y + b3 * p3.y; return result; } private static readonly LiteStack _stack = new LiteStack(); private static readonly List _limitList = new List(); private void CubicCurve(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, int numberOfLimit, bool cubic) { Profiler.BeginSample("SVGBasicDraw.CubicCurve(...)"); MoveTo(p1); //MoveTo the first Point; //How many times the curve change form innegative -> negative or vice versa int _limit = numberOfLimit; float t1, t2, _flatness; t1 = 0.0f; //t1 is the start point of [0..1]. t2 = 1.0f; //t2 is the end point of [0..1] _flatness = 1.0f; Vector2Ext _pStart, _pEnd, _pMid; _pStart = new Vector2Ext(cubic ? EvaluateForCubic(t1, p1, p2, p3, p4) : EvaluateForQuadratic(t1, p1, p2, p3), t1); _pEnd = new Vector2Ext(cubic ? EvaluateForCubic(t2, p1, p2, p3, p4) : EvaluateForQuadratic(t2, p1, p2, p3), t2); // The point on Line Segment[_pStart, _pEnd] correlate with _t _stack.Clear(); _stack.Push(_pEnd); //Push End Point into Stack //Array of Change Point _limitList.Clear(); if(_limitList.Capacity < _limit + 1) _limitList.Capacity = _limit + 1; int _count = 0; while(true) { _count++; float _tm = (t1 + t2) / 2; //tm is a middle of t1 .. t2. [t1 .. tm .. t2] //The point on the Curve correlate with tm _pMid = new Vector2Ext(cubic ? EvaluateForCubic(_tm, p1, p2, p3, p4) : EvaluateForQuadratic(_tm, p1, p2, p3), _tm); //Calculate Distance from Middle Point to the Flatnet float dist = Distance(_pStart.point, _stack.Peek().point, _pMid.point); //flag = true, Curve Segment must be drawn, else continue calculate other middle point. bool flag = false; if(dist < _flatness) { int i = 0; float mm = 0.0f; for(i = 0; i < _limit; i++) { mm = (t1 + _tm) / 2; Vector2Ext _q = new Vector2Ext(cubic ? EvaluateForCubic(mm, p1, p2, p3, p4) : EvaluateForQuadratic(mm, p1, p2, p3), mm); if(_limitList.Count - 1 < i) _limitList.Add(_q); else _limitList[i] = _q; dist = Distance(_pStart.point, _pMid.point, _q.point); if(dist >= _flatness) break; else _tm = mm; } if(i == _limit) flag = true; else { //Continue calculate the first point has Distance > Flatness _stack.Push(_pMid); for(int j = 0; j <= i; ++j) _stack.Push(_limitList[j]); t2 = mm; } } if(flag) { LineTo(_pStart.point); LineTo(_pMid.point); _pStart = _stack.Pop(); if(_stack.Count == 0) break; _pMid = _stack.Peek(); t1 = t2; t2 = _pMid.t; } else if(t2 > _tm) { //If Distance > Flatness and t1 < tm < t2 then new t2 is tm. _stack.Push(_pMid); t2 = _tm; } } LineTo(_pStart.point); Profiler.EndSample(); } public void CubicCurve(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) { int _temp = NumberOfLimitForCubic(p1, p2, p3, p4); CubicCurve(p1, p2, p3, p4, _temp, true); } public void CubicCurveTo(Vector2 p1, Vector2 p2, Vector2 p) { Vector2 _tempPoint = new Vector2(_currentPoint.x, _currentPoint.y); CubicCurve(_tempPoint, p1, p2, p); _currentPoint = p; } public void QuadraticCurve(Vector2 p1, Vector2 p2, Vector2 p3) { Vector2 p4 = new Vector2(p2.x, p2.y); CubicCurve(p1, p2, p3, p4, 0, false); _currentPoint = p3; } public void QuadraticCurveTo(Vector2 p1, Vector2 p) { Vector2 _tempPoint = new Vector2(_currentPoint.x, _currentPoint.y); QuadraticCurve(_tempPoint, p1, p); _currentPoint = p; } }