|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Linq; |
|
|
|
using Vector2 = UnityEngine.Vector2; |
|
|
|
using Vector3 = UnityEngine.Vector3; |
|
|
|
readonly List<float> _commands = new List<float>(); |
|
|
|
readonly List<float> _commands; |
|
|
|
|
|
|
|
|
|
|
|
internal PathCache flatten(float scale) { |
|
|
|
if (this._cache != null && this._cache.canReuse(scale)) { |
|
|
|
return this._cache; |
|
|
|
} |
|
|
|
|
|
|
|
this._cache = new PathCache(scale); |
|
|
|
|
|
|
|
var i = 0; |
|
|
|
while (i < this._commands.Count) { |
|
|
|
var cmd = (PathCommand) this._commands[i]; |
|
|
|
switch (cmd) { |
|
|
|
case PathCommand.moveTo: |
|
|
|
this._cache.addPath(); |
|
|
|
this._cache.addPoint(this._commands[i + 1], this._commands[i + 2], PointFlags.corner); |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.lineTo: |
|
|
|
this._cache.addPoint(this._commands[i + 1], this._commands[i + 2], PointFlags.corner); |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.bezierTo: |
|
|
|
this._cache.tessellateBezier( |
|
|
|
this._commands[i + 1], this._commands[i + 2], |
|
|
|
this._commands[i + 3], this._commands[i + 4], |
|
|
|
this._commands[i + 5], this._commands[i + 6], PointFlags.corner); |
|
|
|
i += 7; |
|
|
|
break; |
|
|
|
case PathCommand.close: |
|
|
|
this._cache.closePath(); |
|
|
|
i++; |
|
|
|
break; |
|
|
|
case PathCommand.winding: |
|
|
|
this._cache.pathWinding((PathWinding) this._commands[i + 1]); |
|
|
|
i += 2; |
|
|
|
break; |
|
|
|
default: |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this._cache.normalize(); |
|
|
|
return this._cache; |
|
|
|
} |
|
|
|
|
|
|
|
public Path() { |
|
|
|
public Path(int capacity = 128) { |
|
|
|
this._commands = new List<float>(capacity); |
|
|
|
this._reset(); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
i += 2; |
|
|
|
break; |
|
|
|
default: |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
D.assert(false, () => "unknown cmd: " + cmd); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _expandBounds(float x, float y) { |
|
|
|
this._minX = Mathf.Min(this._minX, x); |
|
|
|
this._minY = Mathf.Min(this._minY, y); |
|
|
|
this._maxX = Mathf.Max(this._maxX, x); |
|
|
|
this._maxY = Mathf.Max(this._maxY, y); |
|
|
|
} |
|
|
|
|
|
|
|
public Rect getBounds() { |
|
|
|
if (this._minX >= this._maxX || this._minY >= this._maxY) { |
|
|
|
return Rect.zero; |
|
|
|
internal PathCache flatten(float scale) { |
|
|
|
scale = Mathf.Round(scale * 2.0f) / 2.0f; // round to 0.5f
|
|
|
|
|
|
|
|
if (this._cache != null && this._cache.canReuse(scale)) { |
|
|
|
return this._cache; |
|
|
|
|
|
|
|
return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); |
|
|
|
} |
|
|
|
|
|
|
|
this._cache = new PathCache(scale); |
|
|
|
void _appendCommands(float[] commands) { |
|
|
|
while (i < commands.Length) { |
|
|
|
var cmd = (PathCommand) commands[i]; |
|
|
|
while (i < this._commands.Count) { |
|
|
|
var cmd = (PathCommand) this._commands[i]; |
|
|
|
this._commandx = commands[i + 1]; |
|
|
|
this._commandy = commands[i + 2]; |
|
|
|
this._cache.addPath(); |
|
|
|
this._cache.addPoint(this._commands[i + 1], this._commands[i + 2], PointFlags.corner); |
|
|
|
this._expandBounds(this._commandx, this._commandy); |
|
|
|
this._expandBounds(commands[i + 1], commands[i + 2]); |
|
|
|
this._commandx = commands[i + 1]; |
|
|
|
this._commandy = commands[i + 2]; |
|
|
|
this._cache.addPoint(this._commands[i + 1], this._commands[i + 2], PointFlags.corner); |
|
|
|
this._expandBounds(this._commandx, this._commandy); |
|
|
|
this._expandBounds(commands[i + 1], commands[i + 2]); |
|
|
|
this._expandBounds(commands[i + 3], commands[i + 4]); |
|
|
|
this._expandBounds(commands[i + 5], commands[i + 6]); |
|
|
|
this._commandx = commands[i + 5]; |
|
|
|
this._commandy = commands[i + 6]; |
|
|
|
this._cache.tessellateBezier( |
|
|
|
this._commands[i + 1], this._commands[i + 2], |
|
|
|
this._commands[i + 3], this._commands[i + 4], |
|
|
|
this._commands[i + 5], this._commands[i + 6], PointFlags.corner); |
|
|
|
this._cache.closePath(); |
|
|
|
this._cache.pathWinding((PathWinding) this._commands[i + 1]); |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
D.assert(false, () => "unknown cmd: " + cmd); |
|
|
|
this._commands.AddRange(commands); |
|
|
|
this._cache.normalize(); |
|
|
|
return this._cache; |
|
|
|
} |
|
|
|
|
|
|
|
void _expandBounds(float x, float y) { |
|
|
|
if (x < this._minX) { |
|
|
|
this._minX = x; |
|
|
|
} |
|
|
|
if (y < this._minY) { |
|
|
|
this._minY = y; |
|
|
|
} |
|
|
|
|
|
|
|
if (x > this._maxX) { |
|
|
|
this._maxX = x; |
|
|
|
} |
|
|
|
if (y > this._maxY) { |
|
|
|
this._maxY = y; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public Rect getBounds() { |
|
|
|
if (this._minX >= this._maxX || this._minY >= this._maxY) { |
|
|
|
return Rect.zero; |
|
|
|
} |
|
|
|
|
|
|
|
return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); |
|
|
|
} |
|
|
|
|
|
|
|
void _appendMoveTo(float x, float y) { |
|
|
|
this._commands.Add((float) PathCommand.moveTo); |
|
|
|
this._commands.Add(x); |
|
|
|
this._commands.Add(y); |
|
|
|
|
|
|
|
this._commandx = x; |
|
|
|
this._commandy = y; |
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _appendLineTo(float x, float y) { |
|
|
|
this._expandBounds(this._commandx, this._commandy); |
|
|
|
this._expandBounds(x, y); |
|
|
|
|
|
|
|
this._commands.Add((float) PathCommand.lineTo); |
|
|
|
this._commands.Add(x); |
|
|
|
this._commands.Add(y); |
|
|
|
|
|
|
|
this._commandx = x; |
|
|
|
this._commandy = y; |
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _appendBezierTo(float x1, float y1, float x2, float y2, float x3, float y3) { |
|
|
|
this._expandBounds(this._commandx, this._commandy); |
|
|
|
this._expandBounds(x1, y1); |
|
|
|
this._expandBounds(x2, y2); |
|
|
|
this._expandBounds(x3, y3); |
|
|
|
|
|
|
|
this._commands.Add((float) PathCommand.bezierTo); |
|
|
|
this._commands.Add(x1); |
|
|
|
this._commands.Add(y1); |
|
|
|
this._commands.Add(x2); |
|
|
|
this._commands.Add(y2); |
|
|
|
this._commands.Add(x3); |
|
|
|
this._commands.Add(y3); |
|
|
|
|
|
|
|
this._commandx = x3; |
|
|
|
this._commandy = y3; |
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _appendClose() { |
|
|
|
this._commands.Add((float) PathCommand.close); |
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
void _appendWinding(float winding) { |
|
|
|
this._commands.Add((float) PathCommand.winding); |
|
|
|
this._commands.Add(winding); |
|
|
|
this._cache = null; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.moveTo, |
|
|
|
x + x0, y + y0, |
|
|
|
}); |
|
|
|
this._appendMoveTo(x + x0, y + y0); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.moveTo, |
|
|
|
x, y, |
|
|
|
}); |
|
|
|
this._appendMoveTo(x, y); |
|
|
|
|
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.lineTo, |
|
|
|
x + x0, y + y0, |
|
|
|
}); |
|
|
|
this._appendLineTo(x + x0, y + y0); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.lineTo, |
|
|
|
x, y, |
|
|
|
}); |
|
|
|
this._appendLineTo(x, y); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.bezierTo, |
|
|
|
c1x, c1y, c2x, c2y, x, y, |
|
|
|
}); |
|
|
|
this._appendBezierTo(c1x, c1y, c2x, c2y, x, y); |
|
|
|
} |
|
|
|
|
|
|
|
public void relativeCubicTo(float c1x, float c1y, float c2x, float c2y, float x, float y) { |
|
|
|
|
|
|
var x0 = this._commandx; |
|
|
|
var y0 = this._commandy; |
|
|
|
|
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.bezierTo, |
|
|
|
(x0 + 2.0f / 3.0f * (cx - x0)), (y0 + 2.0f / 3.0f * (cy - y0)), |
|
|
|
(x + 2.0f / 3.0f * (cx - x)), (y + 2.0f / 3.0f * (cy - y)), |
|
|
|
x, y, |
|
|
|
}); |
|
|
|
const float twoThird = 2.0f / 3.0f; |
|
|
|
this._appendBezierTo( |
|
|
|
x0 + twoThird * (cx - x0), y0 + twoThird * (cy - y0), |
|
|
|
x + twoThird * (cx - x), y + twoThird * (cy - y), |
|
|
|
x, y); |
|
|
|
|
|
|
|
|
|
|
|
public void relativeQuadraticBezierTo(float cx, float cy, float x, float y) { |
|
|
|
var x0 = this._commandx; |
|
|
|
var y0 = this._commandy; |
|
|
|
|
|
|
|
|
|
|
public void conicTo(float x1, float y1, float x2, float y2, float w) { |
|
|
|
if (!(w > 0)) { |
|
|
|
this.lineTo(x2, y2); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (w.isInfinite()) { |
|
|
|
this.lineTo(x1, y1); |
|
|
|
this.lineTo(x2, y2); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (w == 1) { |
|
|
|
this.quadraticBezierTo(x1, y1, x2, y2); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var x0 = this._commandx; |
|
|
|
var y0 = this._commandy; |
|
|
|
|
|
|
|
var conic = new _Conic { |
|
|
|
x0 = x0, y0 = y0, |
|
|
|
x1 = x1, y1 = y1, |
|
|
|
x2 = x2, y2 = y2, |
|
|
|
w = w, |
|
|
|
}; |
|
|
|
|
|
|
|
var quadX = new float[5]; |
|
|
|
var quadY = new float[5]; |
|
|
|
conic.chopIntoQuadsPOW2(quadX, quadY, 1); |
|
|
|
|
|
|
|
this.quadraticBezierTo(quadX[1], quadY[1], quadX[2], quadY[2]); |
|
|
|
this.quadraticBezierTo(quadX[3], quadY[3], quadX[4], quadY[4]); |
|
|
|
} |
|
|
|
|
|
|
|
public void relativeConicTo(float x1, float y1, float x2, float y2, float w) { |
|
|
|
var x0 = this._commandx; |
|
|
|
var y0 = this._commandy; |
|
|
|
|
|
|
|
this.conicTo(x0 + x1, y0 + y1, x0 + x2, y0 + y2, w); |
|
|
|
} |
|
|
|
|
|
|
|
// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
|
|
|
|
public void arcToPoint(Offset arcEnd, |
|
|
|
Radius radius = null, |
|
|
|
float rotation = 0.0f, |
|
|
|
bool largeArc = false, |
|
|
|
bool clockwise = false) { |
|
|
|
radius = radius ?? Radius.zero; |
|
|
|
|
|
|
|
D.assert(PaintingUtils._offsetIsValid(arcEnd)); |
|
|
|
D.assert(PaintingUtils._radiusIsValid(radius)); |
|
|
|
|
|
|
|
var x0 = this._commandx; |
|
|
|
var y0 = this._commandy; |
|
|
|
var x1 = arcEnd.dx; |
|
|
|
var y1 = arcEnd.dy; |
|
|
|
|
|
|
|
var rx = Mathf.Abs(radius.x); |
|
|
|
var ry = Mathf.Abs(radius.y); |
|
|
|
|
|
|
|
if (rx == 0 || ry == 0) { |
|
|
|
this.lineTo(x1, y1); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
if (x0 == x1 && y0 == y1) { |
|
|
|
this.lineTo(x1, y1); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var midPointDistanceX = (x0 - x1) * 0.5f; |
|
|
|
var midPointDistanceY = (y0 - y1) * 0.5f; |
|
|
|
|
|
|
|
var pointTransform = Matrix3.makeRotate(rotation); |
|
|
|
var transformedMidPoint = pointTransform.mapXY(midPointDistanceX, midPointDistanceY); |
|
|
|
|
|
|
|
var squareRx = rx * rx; |
|
|
|
var squareRy = ry * ry; |
|
|
|
var squareX = transformedMidPoint.dx * transformedMidPoint.dx; |
|
|
|
var squareY = transformedMidPoint.dy * transformedMidPoint.dy; |
|
|
|
|
|
|
|
// Check if the radii are big enough to draw the arc, scale radii if not.
|
|
|
|
// http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
|
|
|
|
var radiiScale = squareX / squareRx + squareY / squareRy; |
|
|
|
if (radiiScale > 1) { |
|
|
|
radiiScale = Mathf.Sqrt(radiiScale); |
|
|
|
rx *= radiiScale; |
|
|
|
ry *= radiiScale; |
|
|
|
} |
|
|
|
|
|
|
|
pointTransform.setScale(1 / rx, 1 / ry); |
|
|
|
pointTransform.preRotate(-rotation); |
|
|
|
|
|
|
|
var unitPts = new [] { |
|
|
|
pointTransform.mapXY(x0, y0), |
|
|
|
pointTransform.mapXY(x1, y1), |
|
|
|
}; |
|
|
|
|
|
|
|
var delta = unitPts[1] - unitPts[0]; |
|
|
|
|
|
|
|
var d = delta.dx * delta.dx + delta.dy * delta.dy; |
|
|
|
var scaleFactorSquared = Mathf.Max(1 / d - 0.25f, 0.0f); |
|
|
|
|
|
|
|
var scaleFactor = Mathf.Sqrt(scaleFactorSquared); |
|
|
|
if (!clockwise != largeArc) { // flipped from the original implementation
|
|
|
|
scaleFactor = -scaleFactor; |
|
|
|
} |
|
|
|
delta = delta.scale(scaleFactor); |
|
|
|
|
|
|
|
var centerPoint = unitPts[0] + unitPts[1]; |
|
|
|
centerPoint *= 0.5f; |
|
|
|
centerPoint = centerPoint.translate(-delta.dy, delta.dx); |
|
|
|
unitPts[0] -= centerPoint; |
|
|
|
unitPts[1] -= centerPoint; |
|
|
|
|
|
|
|
var theta1 = Mathf.Atan2(unitPts[0].dy, unitPts[0].dx); |
|
|
|
var theta2 = Mathf.Atan2(unitPts[1].dy, unitPts[1].dx); |
|
|
|
var thetaArc = theta2 - theta1; |
|
|
|
if (thetaArc < 0 && clockwise) { // arcSweep flipped from the original implementation
|
|
|
|
thetaArc += Mathf.PI * 2; |
|
|
|
} else if (thetaArc > 0 && !clockwise) { // arcSweep flipped from the original implementation
|
|
|
|
thetaArc -= Mathf.PI * 2; |
|
|
|
} |
|
|
|
pointTransform.setRotate(rotation); |
|
|
|
pointTransform.preScale(rx, ry); |
|
|
|
|
|
|
|
// the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
|
|
|
|
int segments = Mathf.CeilToInt(Mathf.Abs(thetaArc / (2 * Mathf.PI / 3))); |
|
|
|
var thetaWidth = thetaArc / segments; |
|
|
|
var t = Mathf.Tan(0.5f * thetaWidth); |
|
|
|
if (!t.isFinite()) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var startTheta = theta1; |
|
|
|
var w = Mathf.Sqrt(0.5f + Mathf.Cos(thetaWidth) * 0.5f); |
|
|
|
|
|
|
|
bool expectIntegers = ScalarUtils.ScalarNearlyZero(Mathf.PI/2 - Mathf.Abs(thetaWidth)) && |
|
|
|
ScalarUtils.ScalarIsInteger(rx) && ScalarUtils.ScalarIsInteger(ry) && |
|
|
|
ScalarUtils.ScalarIsInteger(x1) && ScalarUtils.ScalarIsInteger(y1); |
|
|
|
|
|
|
|
for (int i = 0; i < segments; ++i) { |
|
|
|
var endTheta = startTheta + thetaWidth; |
|
|
|
var sinEndTheta = ScalarUtils.ScalarSinCos(endTheta, out var cosEndTheta); |
|
|
|
|
|
|
|
unitPts[1] = new Offset(cosEndTheta, sinEndTheta); |
|
|
|
unitPts[1] += centerPoint; |
|
|
|
unitPts[0] = unitPts[1]; |
|
|
|
unitPts[0] = unitPts[0].translate(t * sinEndTheta, -t * cosEndTheta); |
|
|
|
var mapped = new [] { |
|
|
|
pointTransform.mapPoint(unitPts[0]), |
|
|
|
pointTransform.mapPoint(unitPts[1]), |
|
|
|
}; |
|
|
|
|
|
|
|
/* |
|
|
|
Computing the arc width introduces rounding errors that cause arcs to start |
|
|
|
outside their marks. A round rect may lose convexity as a result. If the input |
|
|
|
values are on integers, place the conic on integers as well. |
|
|
|
*/ |
|
|
|
if (expectIntegers) { |
|
|
|
for (int index = 0; i < mapped.Length; index++) { |
|
|
|
mapped[index] = new Offset( |
|
|
|
Mathf.Round(mapped[index].dx), |
|
|
|
Mathf.Round(mapped[index].dy) |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this.conicTo(mapped[0].dx, mapped[0].dy, mapped[1].dx, mapped[1].dy, w); |
|
|
|
startTheta = endTheta; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.close, |
|
|
|
}); |
|
|
|
this._appendClose(); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.winding, |
|
|
|
(float) dir |
|
|
|
}); |
|
|
|
this._appendWinding((float) dir); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.moveTo, rect.left, rect.top, |
|
|
|
(float) PathCommand.lineTo, rect.left, rect.bottom, |
|
|
|
(float) PathCommand.lineTo, rect.right, rect.bottom, |
|
|
|
(float) PathCommand.lineTo, rect.right, rect.top, |
|
|
|
(float) PathCommand.close |
|
|
|
}); |
|
|
|
this._appendMoveTo(rect.left, rect.top); |
|
|
|
this._appendLineTo(rect.left, rect.bottom); |
|
|
|
this._appendLineTo(rect.right, rect.bottom); |
|
|
|
this._appendLineTo(rect.right, rect.top); |
|
|
|
this._appendClose(); |
|
|
|
} |
|
|
|
|
|
|
|
public void addRRect(RRect rrect) { |
|
|
|
|
|
|
float x = rrect.left; |
|
|
|
float y = rrect.top; |
|
|
|
|
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.moveTo, x, y + ryTL, |
|
|
|
(float) PathCommand.lineTo, x, y + h - ryBL, |
|
|
|
(float) PathCommand.bezierTo, x, y + h - ryBL * (1 - _KAPPA90), |
|
|
|
x + rxBL * (1 - _KAPPA90), y + h, x + rxBL, y + h, |
|
|
|
(float) PathCommand.lineTo, x + w - rxBR, y + h, |
|
|
|
(float) PathCommand.bezierTo, x + w - rxBR * (1 - _KAPPA90), y + h, |
|
|
|
x + w, y + h - ryBR * (1 - _KAPPA90), x + w, y + h - ryBR, |
|
|
|
(float) PathCommand.lineTo, x + w, y + ryTR, |
|
|
|
(float) PathCommand.bezierTo, x + w, y + ryTR * (1 - _KAPPA90), |
|
|
|
x + w - rxTR * (1 - _KAPPA90), y, x + w - rxTR, y, |
|
|
|
(float) PathCommand.lineTo, x + rxTL, y, |
|
|
|
(float) PathCommand.bezierTo, x + rxTL * (1 - _KAPPA90), y, |
|
|
|
x, y + ryTL * (1 - _KAPPA90), x, y + ryTL, |
|
|
|
(float) PathCommand.close, |
|
|
|
}); |
|
|
|
this._appendMoveTo(x, y + ryTL); |
|
|
|
this._appendLineTo(x, y + h - ryBL); |
|
|
|
this._appendBezierTo(x, y + h - ryBL * (1 - _KAPPA90), |
|
|
|
x + rxBL * (1 - _KAPPA90), y + h, x + rxBL, y + h); |
|
|
|
this._appendLineTo(x + w - rxBR, y + h); |
|
|
|
this._appendBezierTo(x + w - rxBR * (1 - _KAPPA90), y + h, |
|
|
|
x + w, y + h - ryBR * (1 - _KAPPA90), x + w, y + h - ryBR); |
|
|
|
this._appendLineTo(x + w, y + ryTR); |
|
|
|
this._appendBezierTo(x + w, y + ryTR * (1 - _KAPPA90), |
|
|
|
x + w - rxTR * (1 - _KAPPA90), y, x + w - rxTR, y); |
|
|
|
this._appendLineTo(x + rxTL, y); |
|
|
|
this._appendBezierTo(x + rxTL * (1 - _KAPPA90), y, |
|
|
|
x, y + ryTL * (1 - _KAPPA90), x, y + ryTL); |
|
|
|
this._appendClose(); |
|
|
|
this._appendCommands(new[] { |
|
|
|
(float) PathCommand.moveTo, (cx - rx), cy, |
|
|
|
(float) PathCommand.bezierTo, (cx - rx), (cy + ry * _KAPPA90), |
|
|
|
(cx - rx * _KAPPA90), (cy + ry), cx, (cy + ry), |
|
|
|
(float) PathCommand.bezierTo, (cx + rx * _KAPPA90), (cy + ry), |
|
|
|
(cx + rx), (cy + ry * _KAPPA90), (cx + rx), cy, |
|
|
|
(float) PathCommand.bezierTo, (cx + rx), (cy - ry * _KAPPA90), |
|
|
|
(cx + rx * _KAPPA90), (cy - ry), cx, (cy - ry), |
|
|
|
(float) PathCommand.bezierTo, (cx - rx * _KAPPA90), (cy - ry), |
|
|
|
(cx - rx), (cy - ry * _KAPPA90), (cx - rx), cy, |
|
|
|
(float) PathCommand.close, |
|
|
|
}); |
|
|
|
this._appendMoveTo(cx - rx, cy); |
|
|
|
this._appendBezierTo(cx - rx, cy + ry * _KAPPA90, |
|
|
|
cx - rx * _KAPPA90, cy + ry, cx, cy + ry); |
|
|
|
this._appendBezierTo(cx + rx * _KAPPA90, cy + ry, |
|
|
|
cx + rx, cy + ry * _KAPPA90, cx + rx, cy); |
|
|
|
this._appendBezierTo(cx + rx, cy - ry * _KAPPA90, |
|
|
|
cx + rx * _KAPPA90, cy - ry, cx, cy - ry); |
|
|
|
this._appendBezierTo(cx - rx * _KAPPA90, cy - ry, |
|
|
|
cx - rx, cy - ry * _KAPPA90, cx - rx, cy); |
|
|
|
this._appendClose(); |
|
|
|
} |
|
|
|
|
|
|
|
public void addCircle(float cx, float cy, float r) { |
|
|
|
|
|
|
var center = rect.center; |
|
|
|
mat.postTranslate(center.dx, center.dy); |
|
|
|
|
|
|
|
var vals = this._getArcCommands(0, 0, 1, startAngle, startAngle + sweepAngle, |
|
|
|
sweepAngle >= 0 ? PathWinding.clockwise : PathWinding.counterClockwise, forceMoveTo); |
|
|
|
|
|
|
|
this._transformCommands(vals, mat); |
|
|
|
this._appendCommands(vals.ToArray()); |
|
|
|
this._addArcCommands(0, 0, 1, startAngle, startAngle + sweepAngle, |
|
|
|
sweepAngle >= 0 ? PathWinding.clockwise : PathWinding.counterClockwise, forceMoveTo, mat); |
|
|
|
} |
|
|
|
|
|
|
|
public void addArc(Rect rect, float startAngle, float sweepAngle) { |
|
|
|
|
|
|
public Path transform(Matrix3 mat) { |
|
|
|
Path ret = new Path(); |
|
|
|
|
|
|
|
var i = 0; |
|
|
|
while (i < this._commands.Count) { |
|
|
|
var cmd = (PathCommand) this._commands[i]; |
|
|
|
switch (cmd) { |
|
|
|
case PathCommand.moveTo: |
|
|
|
var res_move = mat.mapXY(this._commands[i + 1], this._commands[i + 2]); |
|
|
|
ret.moveTo(res_move.dx, res_move.dy); |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.lineTo: |
|
|
|
var res_lineto = mat.mapXY(this._commands[i + 1], this._commands[i + 2]); |
|
|
|
ret.lineTo(res_lineto.dx, res_lineto.dy); |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.bezierTo: |
|
|
|
var res1 = mat.mapXY(this._commands[i + 1], this._commands[i + 2]); |
|
|
|
var res2 = mat.mapXY(this._commands[i + 3], this._commands[i + 4]); |
|
|
|
var res3 = mat.mapXY(this._commands[i + 5], this._commands[i + 6]); |
|
|
|
ret.cubicTo(res1.dx, res1.dy, res2.dx, res2.dy, res3.dx, res3.dy); |
|
|
|
i += 7; |
|
|
|
break; |
|
|
|
case PathCommand.close: |
|
|
|
i++; |
|
|
|
break; |
|
|
|
case PathCommand.winding: |
|
|
|
i += 2; |
|
|
|
break; |
|
|
|
default: |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return ret; |
|
|
|
} |
|
|
|
|
|
|
|
void _transformCommands(List<float> commands, Matrix3 mat) { |
|
|
|
if (mat == null) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var i = 0; |
|
|
|
while (i < commands.Count) { |
|
|
|
var cmd = (PathCommand) commands[i]; |
|
|
|
switch (cmd) { |
|
|
|
case PathCommand.moveTo: |
|
|
|
case PathCommand.lineTo: |
|
|
|
var res = mat.mapXY(commands[i + 1], commands[i + 2]); |
|
|
|
commands[i + 1] = res.dx; |
|
|
|
commands[i + 2] = res.dy; |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.bezierTo: |
|
|
|
var res1 = mat.mapXY(commands[i + 1], commands[i + 2]); |
|
|
|
commands[i + 1] = res1.dx; |
|
|
|
commands[i + 2] = res1.dy; |
|
|
|
var res2 = mat.mapXY(commands[i + 3], commands[i + 4]); |
|
|
|
commands[i + 3] = res2.dx; |
|
|
|
commands[i + 4] = res2.dy; |
|
|
|
var res3 = mat.mapXY(commands[i + 5], commands[i + 6]); |
|
|
|
commands[i + 5] = res3.dx; |
|
|
|
commands[i + 6] = res3.dy; |
|
|
|
i += 7; |
|
|
|
break; |
|
|
|
case PathCommand.close: |
|
|
|
i++; |
|
|
|
break; |
|
|
|
case PathCommand.winding: |
|
|
|
i += 2; |
|
|
|
break; |
|
|
|
default: |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
List<float> _getArcCommands(float cx, float cy, float r, float a0, float a1, PathWinding dir, bool forceMoveTo) { |
|
|
|
void _addArcCommands( |
|
|
|
float cx, float cy, float r, float a0, float a1, |
|
|
|
PathWinding dir, bool forceMoveTo, Matrix3 transform = null) { |
|
|
|
// Clamp angles
|
|
|
|
float da = a1 - a0; |
|
|
|
if (dir == PathWinding.clockwise) { |
|
|
|
|
|
|
da += Mathf.PI * 2; |
|
|
|
} |
|
|
|
if (da <= 1e-5) { |
|
|
|
return new List<float>(); |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
} else { |
|
|
|
|
|
|
da -= Mathf.PI * 2; |
|
|
|
} |
|
|
|
if (da >= -1e-5) { |
|
|
|
return new List<float>(); |
|
|
|
return; |
|
|
|
|
|
|
|
// Split arc into max 90 degree segments.
|
|
|
|
int ndivs = Mathf.Max(1, Mathf.Min((int) (Mathf.Abs(da) / (Mathf.PI * 0.5f) + 0.5f), 5)); |
|
|
|
float hda = (da / ndivs) / 2.0f; |
|
|
|
|
|
|
PathCommand move = (forceMoveTo || this._commands.Count == 0) ? PathCommand.moveTo : PathCommand.lineTo; |
|
|
|
float px = 0, py = 0, ptanx = 0, ptany = 0; |
|
|
|
|
|
|
|
List<float> vals = new List<float>(); |
|
|
|
for (int i = 0; i <= ndivs; i++) { |
|
|
|
float a = a0 + da * (i / (float) ndivs); |
|
|
|
float dx = Mathf.Cos(a); |
|
|
|
|
|
|
float tany = dx * r * kappa; |
|
|
|
|
|
|
|
if (i == 0) { |
|
|
|
vals.Add((float) move); |
|
|
|
vals.Add(x); |
|
|
|
vals.Add(y); |
|
|
|
float x1 = x, y1 = y; |
|
|
|
if (transform != null) { |
|
|
|
transform.mapXY(x1, y1, out x1, out y1); |
|
|
|
} |
|
|
|
|
|
|
|
if (move == PathCommand.moveTo) { |
|
|
|
this._appendMoveTo(x1, y1); |
|
|
|
} else { |
|
|
|
this._appendLineTo(x1, y1); |
|
|
|
} |
|
|
|
vals.Add((float) PathCommand.bezierTo); |
|
|
|
vals.Add(px + ptanx); |
|
|
|
vals.Add(py + ptany); |
|
|
|
vals.Add(x - tanx); |
|
|
|
vals.Add(y - tany); |
|
|
|
vals.Add(x); |
|
|
|
vals.Add(y); |
|
|
|
float c1x = px + ptanx; |
|
|
|
float c1y = py + ptany; |
|
|
|
float c2x = x - tanx; |
|
|
|
float c2y = y - tany; |
|
|
|
float x1 = x; |
|
|
|
float y1 = y; |
|
|
|
if (transform != null) { |
|
|
|
transform.mapXY(c1x, c1y, out c1x, out c1y); |
|
|
|
transform.mapXY(c2x, c2y, out c2x, out c2y); |
|
|
|
transform.mapXY(x1, y1, out x1, out y1); |
|
|
|
} |
|
|
|
|
|
|
|
this._appendBezierTo(c1x, c1y, c2x, c2y, x1, y1); |
|
|
|
} |
|
|
|
px = x; |
|
|
|
py = y; |
|
|
|
|
|
|
} |
|
|
|
return vals; |
|
|
|
} |
|
|
|
|
|
|
|
var vals = this._getArcCommands(cx, cy, r, a0, a1, dir, forceMoveTo); |
|
|
|
this._appendCommands(vals.ToArray()); |
|
|
|
this._addArcCommands(cx, cy, r, a0, a1, dir, forceMoveTo); |
|
|
|
} |
|
|
|
|
|
|
|
public void addPolygon(IList<Offset> points, bool close) { |
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var commands = new List<float>(); |
|
|
|
commands.Add((float) PathCommand.moveTo); |
|
|
|
commands.Add(points[0].dx); |
|
|
|
commands.Add(points[0].dy); |
|
|
|
|
|
|
|
this._appendMoveTo(points[0].dx, points[0].dy); |
|
|
|
commands.Add((float) PathCommand.lineTo); |
|
|
|
commands.Add(point.dx); |
|
|
|
commands.Add(point.dy); |
|
|
|
this._appendLineTo(point.dx, point.dy); |
|
|
|
commands.Add((float) PathCommand.close); |
|
|
|
this._appendClose(); |
|
|
|
|
|
|
|
this._appendCommands(commands.ToArray()); |
|
|
|
} |
|
|
|
|
|
|
|
public Path shift(Offset offset) { |
|
|
|
|
|
|
return path; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
public Path transform(Matrix3 mat) { |
|
|
|
var path = new Path(); |
|
|
|
path.addPath(this, mat); |
|
|
|
return path; |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(path != null); |
|
|
|
D.assert(offset != null); |
|
|
|
if (offset == null) { |
|
|
|
this.addPath(path); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var transform = Matrix3.makeTrans(offset.dx, offset.dy); |
|
|
|
this.addPath(path, transform); |
|
|
|
} |
|
|
|
var commands = new List<float>(); |
|
|
|
public void addPath(Path path, Matrix3 transform = null) { |
|
|
|
D.assert(path != null); |
|
|
|
case PathCommand.moveTo: |
|
|
|
case PathCommand.lineTo: |
|
|
|
commands.Add(path._commands[i]); |
|
|
|
commands.Add(path._commands[i + 1] + offset.dx); |
|
|
|
commands.Add(path._commands[i + 2] + offset.dy); |
|
|
|
case PathCommand.moveTo: { |
|
|
|
float x = path._commands[i + 1]; |
|
|
|
float y = path._commands[i + 2]; |
|
|
|
if (transform != null) { |
|
|
|
transform.mapXY(x, y, out x, out y); |
|
|
|
} |
|
|
|
this._appendMoveTo(x, y); |
|
|
|
} |
|
|
|
case PathCommand.bezierTo: |
|
|
|
commands.Add(path._commands[i]); |
|
|
|
commands.Add(path._commands[i + 1] + offset.dx); |
|
|
|
commands.Add(path._commands[i + 2] + offset.dy); |
|
|
|
commands.Add(path._commands[i + 3] + offset.dx); |
|
|
|
commands.Add(path._commands[i + 4] + offset.dy); |
|
|
|
commands.Add(path._commands[i + 5] + offset.dx); |
|
|
|
commands.Add(path._commands[i + 6] + offset.dy); |
|
|
|
case PathCommand.lineTo: { |
|
|
|
float x = path._commands[i + 1]; |
|
|
|
float y = path._commands[i + 2]; |
|
|
|
if (transform != null) { |
|
|
|
transform.mapXY(x, y, out x, out y); |
|
|
|
} |
|
|
|
this._appendLineTo(x, y); |
|
|
|
} |
|
|
|
i += 3; |
|
|
|
break; |
|
|
|
case PathCommand.bezierTo: { |
|
|
|
float c1x = path._commands[i + 1]; |
|
|
|
float c1y = path._commands[i + 2]; |
|
|
|
float c2x = path._commands[i + 3]; |
|
|
|
float c2y = path._commands[i + 4]; |
|
|
|
float x1 = path._commands[i + 5]; |
|
|
|
float y1 = path._commands[i + 6]; |
|
|
|
if (transform != null) { |
|
|
|
transform.mapXY(c1x, c1y, out c1x, out c1y); |
|
|
|
transform.mapXY(c2x, c2y, out c2x, out c2y); |
|
|
|
transform.mapXY(x1, y1, out x1, out y1); |
|
|
|
} |
|
|
|
this._appendBezierTo(c1x, c1y, c2x, c2y, x1, y1); |
|
|
|
} |
|
|
|
commands.Add(path._commands[i]); |
|
|
|
this._appendClose(); |
|
|
|
commands.Add(path._commands[i]); |
|
|
|
commands.Add(path._commands[i + 1]); |
|
|
|
this._appendWinding(path._commands[i + 1]); |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
D.assert(false, () => "unknown cmd: " + cmd); |
|
|
|
|
|
|
|
this._appendCommands(commands.ToArray()); |
|
|
|
if (bounds == null) { |
|
|
|
if (bounds == null || bounds.isEmpty) { |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
i += 2; |
|
|
|
break; |
|
|
|
default: |
|
|
|
D.assert(false, "unknown cmd: " + cmd); |
|
|
|
D.assert(false, () => "unknown cmd: " + cmd); |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
return 0; |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(r >= 0 && r < 1, $"numer {numer}, denom {denom}, r {r}"); |
|
|
|
D.assert(r >= 0 && r < 1, () => $"numer {numer}, denom {denom}, r {r}"); |
|
|
|
if (r == 0) { |
|
|
|
// catch underflow if numer <<<< denom
|
|
|
|
return 0; |
|
|
|
|
|
|
clockwise = 2, // which just means the reversed order.
|
|
|
|
} |
|
|
|
|
|
|
|
class _Conic { |
|
|
|
public float x0; |
|
|
|
public float y0; |
|
|
|
public float x1; |
|
|
|
public float y1; |
|
|
|
public float x2; |
|
|
|
public float y2; |
|
|
|
public float w; |
|
|
|
|
|
|
|
public int chopIntoQuadsPOW2(float[] quadX, float[] quadY, int pow2) { |
|
|
|
quadX[0] = this.x0; |
|
|
|
quadY[0] = this.y0; |
|
|
|
|
|
|
|
var endIndex = this._subdivide(quadX, quadY, 1, pow2); |
|
|
|
var quadCount = 1 << pow2; |
|
|
|
var ptCount = 2 * quadCount + 1; |
|
|
|
D.assert(endIndex == ptCount); |
|
|
|
|
|
|
|
if (!(_areFinite(quadX, 0, ptCount) && _areFinite(quadY, 0, ptCount))) { |
|
|
|
for (int i = 1; i < ptCount - 1; i++) { |
|
|
|
quadX[i] = this.x1; |
|
|
|
quadY[i] = this.y1; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return quadCount; |
|
|
|
} |
|
|
|
|
|
|
|
static bool _areFinite(float[] array, int index, int count) { |
|
|
|
float prod = 0; |
|
|
|
|
|
|
|
count += index; |
|
|
|
for (int i = index; i < count; ++i) { |
|
|
|
prod *= array[i]; |
|
|
|
} |
|
|
|
|
|
|
|
// At this point, prod will either be NaN or 0
|
|
|
|
return prod == 0; // if prod is NaN, this check will return false
|
|
|
|
} |
|
|
|
|
|
|
|
int _subdivide(float[] quadX, float[] quadY, int index, int level) { |
|
|
|
D.assert(level >= 0); |
|
|
|
|
|
|
|
if (0 == level) { |
|
|
|
quadX[0 + index] = this.x1; |
|
|
|
quadY[0 + index] = this.y1; |
|
|
|
quadX[1 + index] = this.x2; |
|
|
|
quadY[1 + index] = this.y2; |
|
|
|
return 2 + index; |
|
|
|
} |
|
|
|
|
|
|
|
_Conic c1, c2; |
|
|
|
this._chop(out c1, out c2); |
|
|
|
|
|
|
|
var startY = this.y0; |
|
|
|
var endY = this.y2; |
|
|
|
|
|
|
|
if (_between(startY, this.y1, endY)) { |
|
|
|
// If the input is monotonic and the output is not, the scan converter hangs.
|
|
|
|
// Ensure that the chopped conics maintain their y-order.
|
|
|
|
var midY = c1.y2; |
|
|
|
if (!_between(startY, midY, endY)) { |
|
|
|
// If the computed midpoint is outside the ends, move it to the closer one.
|
|
|
|
var closerY = Mathf.Abs(midY - startY) < Mathf.Abs(midY - endY) ? startY : endY; |
|
|
|
c1.y2 = c2.y0 = closerY; |
|
|
|
} |
|
|
|
if (!_between(startY, c1.y1, c1.y2)) { |
|
|
|
// If the 1st control is not between the start and end, put it at the start.
|
|
|
|
// This also reduces the quad to a line.
|
|
|
|
c1.y1 = startY; |
|
|
|
} |
|
|
|
if (!_between(c2.y0, c2.y1, endY)) { |
|
|
|
// If the 2nd control is not between the start and end, put it at the end.
|
|
|
|
// This also reduces the quad to a line.
|
|
|
|
c2.y1 = endY; |
|
|
|
} |
|
|
|
// Verify that all five points are in order.
|
|
|
|
D.assert(_between(startY, c1.y1, c1.y2)); |
|
|
|
D.assert(_between(c1.y1, c1.y2, c2.y1)); |
|
|
|
D.assert(_between(c1.y2, c2.y1, endY)); |
|
|
|
} |
|
|
|
|
|
|
|
--level; |
|
|
|
index = c1._subdivide(quadX, quadY, index, level); |
|
|
|
return c2._subdivide(quadX, quadY, index, level); |
|
|
|
} |
|
|
|
|
|
|
|
static bool _between(float a, float b, float c) { |
|
|
|
return (a - b) * (c - b) <= 0; |
|
|
|
} |
|
|
|
|
|
|
|
void _chop(out _Conic c1, out _Conic c2) { |
|
|
|
var scale = 1.0f / (1.0f + this.w); |
|
|
|
var newW = Mathf.Sqrt(0.5f + this.w * 0.5f); |
|
|
|
|
|
|
|
var wp1X = this.w * this.x1; |
|
|
|
var wp1Y = this.w * this.y1; |
|
|
|
var mX = (this.x0 + (wp1X + wp1X) + this.x2) * scale * 0.5f; |
|
|
|
var mY = (this.y0 + (wp1Y + wp1Y) + this.y2) * scale * 0.5f; |
|
|
|
|
|
|
|
if (!(mX.isFinite() && mY.isFinite())) { |
|
|
|
double w_d = this.w; |
|
|
|
double w_2 = w_d * 2.0; |
|
|
|
double scale_half = 1.0 / (1.0 + w_d) * 0.5; |
|
|
|
mX = (float) ((this.x0 + w_2 * this.x1 + this.x2) * scale_half); |
|
|
|
mY = (float) ((this.y0 + w_2 * this.y1 + this.y2) * scale_half); |
|
|
|
} |
|
|
|
|
|
|
|
c1 = new _Conic { |
|
|
|
x0 = this.x0, |
|
|
|
y0 = this.y0, |
|
|
|
x1 = (this.x0 + wp1X) * scale, |
|
|
|
y1 = (this.y0 + wp1Y) * scale, |
|
|
|
x2 = mX, |
|
|
|
y2 = mY, |
|
|
|
w = newW, |
|
|
|
}; |
|
|
|
|
|
|
|
c2 = new _Conic { |
|
|
|
x0 = mX, |
|
|
|
y0 = mY, |
|
|
|
x1 = (wp1X + this.x2) * scale, |
|
|
|
y1 = (wp1Y + this.y2) * scale, |
|
|
|
x2 = this.x2, |
|
|
|
y2 = this.y2, |
|
|
|
w = newW, |
|
|
|
}; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
enum PathCommand { |
|
|
|
moveTo, |
|
|
|
lineTo, |
|
|
|
bezierTo, |
|
|
|
close, |
|
|
|
winding, |
|
|
|
} |
|
|
|
|
|
|
|
[Flags] |
|
|
|
enum PointFlags { |
|
|
|
corner = 0x01, |
|
|
|
|
|
|
public float len; |
|
|
|
public float dmx, dmy; |
|
|
|
public PointFlags flags; |
|
|
|
} |
|
|
|
|
|
|
|
enum PathCommand { |
|
|
|
moveTo, |
|
|
|
lineTo, |
|
|
|
bezierTo, |
|
|
|
close, |
|
|
|
winding, |
|
|
|
} |
|
|
|
|
|
|
|
class PathPath { |
|
|
|
|
|
|
y1 = pt.y; |
|
|
|
} |
|
|
|
|
|
|
|
if (x1 == x2 && x1 == x3 && x1 == x4 && |
|
|
|
y1 == y2 && y1 == y3 && y1 == y4) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
D.assert(points.Count > 0); |
|
|
|
points[points.Count - 1].flags = flags; |
|
|
|
foreach (var point in points) { |
|
|
|
this._addPoint(point); |
|
|
|
D.assert(points.Count > 0); |
|
|
|
for (int i = 0; i < points.Count; i++) { |
|
|
|
var point = points[i]; |
|
|
|
if (i == points.Count - 1) { |
|
|
|
this._addPoint(new PathPoint { |
|
|
|
x = point.x + x1, |
|
|
|
y = point.y + y1, |
|
|
|
flags = flags, |
|
|
|
}); |
|
|
|
} else { |
|
|
|
this._addPoint(new PathPoint { |
|
|
|
x = point.x + x1, |
|
|
|
y = point.y + y1, |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
var path = this._paths.Last(); |
|
|
|
var path = this._paths[this._paths.Count - 1]; |
|
|
|
path.closed = true; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
var mesh = new Mesh(); |
|
|
|
mesh.MarkDynamic(); |
|
|
|
mesh.hideFlags = HideFlags.HideAndDontSave; |
|
|
|
return mesh; |
|
|
|
} |
|
|
|