轨迹插件引入

This commit is contained in:
lishuo committed 2022-09-06 13:47:45 +08:00
1 parent 89b5275efd
commit 195893a1b0
1023 files changed
+378944 -18404

No files matched your search

@@ -0,0 +1,398 @@
# Change log
## [2.0.0-preview.17] - 2021-06-09
### Fixed
- Fixed missing stats over vector sprite asset preview
## [2.0.0-preview.16] - 2021-04-30
### Fixed
- Fixed null-reference exception occurring in IL2CPP builds
## [2.0.0-preview.15] - 2021-04-19
### Fixed
- Fixed prefab transform reset during asset rename
- Added missing dependencies in package.json
## [2.0.0-preview.14] - 2021-01-11
### Fixed
- Fixed path arcs when the sweep angle is very small
- Sanitized tessellation options to prevent infinite loops
## [2.0.0-preview.13] - 2020-09-08
### Changes
* Moved SVGImage/SVGImageEditor to the Unity.VectorImage namespace
* Renamed UIElements assets to UI Toolkit assets
* Added support for rgba() colors
### Fixes
* Fixed WebGL unrepresentable float error during segment length evaluation
* Fixed image fills not working when defined later in the file
## [2.0.0-preview.12] - 2020-02-13
### Changes
* Added a "Textured Sprite" option to use the SVG's pixels per unit instead of the texture
### Fixes
* Fixed gradients not showing properly on Metal
* Prevent hang while evaluating segment length with large coordinates
## [2.0.0-preview.11] - 2019-12-04
### Fixes
* Fixed CSS class used on root svg element
* Properly handling referenced stop data defined later in the file
* Fixed alpha blending for UI shaders
* Fixed path corners in polygon definitions
## [2.0.0-preview.10] - 2019-11-19
### Fixes
* Added support for RectMask2D
* Fixed sRGB support for UI shaders
* Fixed unmatched BeginSample/EndSample in GenerateAtlas()
## [2.0.0-preview.9] - 2019-11-15
### Fixes
* Fixed invalid VectorExpandEdges.shader path
## [2.0.0-preview.8] - 2019-11-06
### Changes
* Added "uGUI SVGImage" asset type, which sets the proper stencil state for uGUI's masking system
### Fixes
* Fixed VectorImage winding order
* Fixed LibTess preprocessor-dependent namespaces
## [2.0.0-preview.7] - 2019-10-24
### Changes
* Gradient support for strokes
## [2.0.0-preview.6] - 2019-09-23
### Changes
* Changed default gradient resolution from 128 -> 64 pixels to help batching with UIElements renderer
### Fixes
* Using serializable VectorImageVertex (only works on 2019.3b4 and later)
## [2.0.0-preview.5] - 2019-08-01
### Fixes
* Fixed pivot wrongly computed on textured sprites when SVG origin alignement is selected (case 1172332)
* Added preserve aspect ratio in SVGImage
## [2.0.0-preview.4] - 2019-07-08
### Fixes
* Fixed ArgumentOutOfRangeException when closing paths with not enough segments
* Fixed textured-sprite import issue after a full project reimport
## [2.0.0-preview.3] - 2019-06-26
### Changes
* Added a new asset import type (UIElements Vector Image) for upcoming UIElements support in Unity 2019.3.
### Fixes
* Fixed 'T' path instruction not computing the proper reflected control point
## [2.0.0-preview.2] - 2019-06-18
### Changes
There are some breaking changes when going from `1.0.0-preview` to `2.0.0-preview`:
* The deprecated API have been removed. Most notably, the removal of the `IDrawable` interface.
* The texture atlas layout for the gradient settings was changed. It now stores gradient settings in the first 3 columns of the atlas instead of the first row. This allows for a more efficient atlas packing. If you made a variant of the `Unlit/VectorGradient` shader, you may have to update it.
* The `SVGImporter` will now ignore the root `viewBox` attribute by default. Use the "Only Apply Root ViewBox" viewport option to bring back the old behavior.
### Fixes
* Fixed flipYAxis in FillMesh API
## [1.0.0-preview.26] - 2019-05-01
### Fixes
* Removed unused variable to fix warning
## [1.0.0-preview.25] - 2019-05-01
### Changes
* Importing SVG from editor code allows local Texture2D references
* URL scheme whitelisting on image tags
* Using next power-of-two texture size, fixes gradient issues on some Android devices
### Fixes
* Fixed pivot not being applied on textured sprite assets
* Fixed SVGParser rotation center in transforms
## [1.0.0-preview.24] - 2019-04-04
### Fixes
* Applying material's color in VectorGradient.shader
* Fixed duplicated points causing issues with polygons
* Added repository information to package.json
## [1.0.0-preview.23] - 2019-01-04
### Changes
* Deprecating Matrix2D.Rotate in favor of RotateRH and RotateLH
* Added support for gradient fills defined later in the file
### Fixes
* Fixed invalid SVG pen position when 'z' command occurs after 'm'
## [1.0.0-preview.22] - 2018-11-16
### Changes
* Proper support for styling in symbols
### Fixes
* Fixed precision issues with dashed strokes
* Fixed modifying node hierarchy while iterating through it
* Fixed CSS data parsing
* Clearing temp render texture when expanding edges
* Fixed instancing for gradient shader
## [1.0.0-preview.21] - 2018-10-23
### Changes
* Added support for borders (slices) for textured sprites
* SVGImageEditor is now fallback custom editor
### Fixes
* Fixed viewport clipping working when viewBox is applied
* Fixed dark outlines when rendering to texture
* Fixed alpha-blending in VectorGradient.shader
* Silenced obsolete warnings because of WWW usage
## [1.0.0-preview.20] - 2018-09-26
### Changes
* Removed "Per-Axis" texture wrap mode
### Fixes
* Fixed sprite value not being set in SVGImage's sprite property
## [1.0.0-preview.19] - 2018-09-24
### Changes
* Using viewBox for relative coordinates, when available
### Fixes
* Fixed issue with gradient user-units when no viewBox is specified
## [1.0.0-preview.18] - 2018-09-21
### Changes
* Improved texture import editor. Better basic tessellation defaults.
* Enabled GPU instancing, _RendererColor works out-of-the-box
* Made the auto-computed tessellation options less aggressive
* Allowing different width/height when importing to a texture
* Moved external libraries to their own namespaces
* Moved the sprite stats over the preview
* Filling atlas with opaque black to help with SVG sprite picking
* Support for sample count (for import-to-texture)
* Setting DtdProcessing to ignore
### Fixes
* Fixed flipped winding order when flipYAxis is false
* Fixed SVGImageEditor for 2018.1
## [1.0.0-preview.17] - 2018-09-13
### Changes
* Support for sprite mesh type on textured sprites
## [1.0.0-preview.16] - 2018-09-13
### Changes
* Node-by-id support
* Added SVGImage for Canvas UI
* Preserve viewport option
* Support for auto-generate physics outline
* Setting Closed=true closes the path connecting the last segment to the first instead of a straight line
* First iteration of "import to texture" feature
* Deprecated Rectangle, Path, Filled and IDrawable. Only Shape remains.
* Improved SVGOrigin and pivot support
* Added support for flipYAxis in FillMesh method
* Multiple object editing improvements
### Fixes
* Fixed relative positioning with viewBox
* Fixed elliptical-arc-to error with large sweep angles
* Fixed polygon winding after transform
* Fixed `<use>` always overriding fill/stroke even when not set
* Fixed "ProhibitDtd" obsolete warning on .NET 4.x backend
* Fixed issues with symbols and patterns usage
* Fixed support for empty 'd' elements
* Fixed issue when symbols are defined after `<use>`
* Fixed invalid SVG Origin when Y-axis is fipped
* Fixed sprite editor align/pivot to not interfere with SVG origin value
* Fixed missing Apply() after atlas generation
## [1.0.0-preview.15] - 2018-07-18
### Changes
* Updated CHANGELOG.md
## [1.0.0-preview.14] - 2018-07-17
### Changes
* Added QuadraticToCubic helper method
### Fixes
* Taking pixels-per-unit into account to compute tessellation settings
* Fixed rgb() color attributes not parsed properly
* Early exit when trying to tessellate paths without enough segments
* Fixed viewbox computation that were lost during tessellation
* Fixed namespace issues with 2018.3+
* Skip stroke tessellation if the width is 0
## [1.0.0-preview.13] - 2018-06-11
### Changes
* Showing imported sprites stats
### Fixes
* Elements with display:none are not displayed anymore
* Fixed parse issue when loading an unsupported texture from the image tag
## [1.0.0-preview.12] - 2018-06-07
### Fixes
* Using culture invariant float parsing
* Fixed import error when using percentage sizes in svg tag
## [1.0.0-preview.11] - 2018-06-05
### Fixes
* Fixed some precision issues
* More conservative processing of 'none' for 'stroke-dasharray'
* Revert "Fixed handling of 'none' styles"
## [1.0.0-preview.10] - 2018-05-23
### Fixes
* Adjusting the triangle's winding order after scene tessellation
## [1.0.0-preview.9] - 2018-05-15
### Changes
* Renamed Third-Party Notices
### Fixes
* Fixed handling of 'none' styles
## [1.0.0-preview.8] - 2018-05-05
### Changes
* Support for multiple SVG editing
* Updated documentation after docs team revision
## [1.0.0-preview.7] - 2018-04-26
### Changes
* Optimized path for convex shapes
### Fixes
* Fixed SVG StreamReader not being closed
* Fixed polyline corners
## [1.0.0-preview.6] - 2018-04-24
### Changes
* Physics outline fixes and using preview texture for Sprite Editor, when available
* Improved sampling step distance tooltip text
* Removed skin-based animation tools
## [1.0.0-preview.5] - 2018-04-18
### Changes
* Added Third-Party Notices
* Added MakeArc_MakesArcInClockwiseDirection test
## [1.0.0-preview.4] - 2018-04-13
### Changes
* MakeArc now returns a BezierPathSegment[] instead of BezierSegment[]. Added BezierSegmentsToPath API.
* Using the new code naming conventions (CamelCase for properties)
## [1.0.0-preview.3] - 2018-04-09
### Changes
* Exposed BuildRectangleContour API
## [1.0.0-preview.2] - 2018-04-05
### Changes
* Moved SVGParser to Unity.VectorGraphics namespace
## [1.0.0-preview.1] - 2018-04-04
* Initial release
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4169a547a111a07429dea777828b9c72
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,5 @@
com.unity.vectorgraphics copyright © 2020 Unity Technologies ApS
Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License).
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 89ca7bd98c06842498c9fbfe75a037ca
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,11 @@
## Vector Graphics Package
The Vector Graphics package provides an SVG file importer as well as generic vector graphics APIs.
The SVG importer follows a subset of the [SVG 1.1 specification](https://www.w3.org/TR/SVG11/). Once an SVG file is imported, the vector data is tesselated into triangles and a sprite is generated. This sprite can then be used by the 2D system. The source for the SVG importer can be found in the `Editor` folder.
The vector graphics APIs can be used to create and manipulate vector constructs directly in code. Read the [documentation](Documentation~/vectorgraphics.md) to get more information. The source for the vector graphics APIs can be found in the `VectorGraphics` namespace under the `Runtime` folder.
![Vector Graphics Screenshot](Documentation~/images/screenshot.png)
Unity version 2018.1 or later is required to use the Vector Graphics package.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 584bbb607ab62b648a28feeb52b44f0f
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fe090f91a3e5c5c4c8f0e8895d01cc2b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,359 @@
using System;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
/// <summary>A 2x3 transformation matrix used for 2D operations.</summary>
public struct Matrix2D
{
// memory layout:
//
// row no (=vertical)
// | 0 1 2
// ---+------------
// 0 | m00 m10 0
// column no 1 | m01 m11 0
// (=horiz) 2 | m02 m12 1
/// <summary>The matrix member at (0,0)</summary>
public float m00;
/// <summary>The matrix member at (1,0)</summary>
public float m10;
/// <summary>The matrix member at (0,1)</summary>
public float m01;
/// <summary>The matrix member at (1,1)</summary>
public float m11;
/// <summary>The matrix member at (0,2)</summary>
public float m02;
/// <summary>The matrix member at (1,2)</summary>
public float m12;
/// <summary>Initializes a Matrix2D with column vectors</summary>
/// <param name="column0">The first column</param>
/// <param name="column1">The second column</param>
/// <param name="column2">The third column</param>
public Matrix2D(Vector2 column0, Vector2 column1, Vector2 column2)
{
this.m00 = column0.x; this.m01 = column1.x; this.m02 = column2.x;
this.m10 = column0.y; this.m11 = column1.y; this.m12 = column2.y;
}
/// <summary>Access element at [row, column].</summary>
/// <returns>The value at [row, column]</returns>
public float this[int row, int column]
{
get
{
return this[row + column * 2];
}
set
{
this[row + column * 2] = value;
}
}
/// <summary>Access element at sequential index (0..5 inclusive).</summary>
/// <returns>The value at [index]</returns>
public float this[int index]
{
get
{
switch (index)
{
case 0: return m00;
case 1: return m10;
case 2: return m01;
case 3: return m11;
case 4: return m02;
case 5: return m12;
default:
throw new IndexOutOfRangeException("Invalid matrix index!");
}
}
set
{
switch (index)
{
case 0: m00 = value; break;
case 1: m10 = value; break;
case 2: m01 = value; break;
case 3: m11 = value; break;
case 4: m02 = value; break;
case 5: m12 = value; break;
default:
throw new IndexOutOfRangeException("Invalid matrix index!");
}
}
}
/// <summary>Gets a hashcode of the matrix.</summary>
/// <remarks>Used to allow Matrix3x3s to be used as keys in hash tables.</remarks>
/// <returns>The hashcode of the matrix</returns>
public override int GetHashCode()
{
return GetColumn(0).GetHashCode() ^ (GetColumn(1).GetHashCode() << 2) ^ (GetColumn(2).GetHashCode() >> 2);
}
/// <summary>Checks if two matrices are equal.</summary>
/// <param name="other">The other matrix to compare with</param>
/// <remarks>Used to allow Matrix3x3s to be used as keys in hash tables.</remarks>
/// <returns>True when the matrix is equal to "other"</returns>
public override bool Equals(object other)
{
if (!(other is Matrix2D)) return false;
Matrix2D rhs = (Matrix2D)other;
return GetColumn(0).Equals(rhs.GetColumn(0))
&& GetColumn(1).Equals(rhs.GetColumn(1))
&& GetColumn(2).Equals(rhs.GetColumn(2));
}
/// <summary>Multiplies two matrices.</summary>
/// <param name="lhs">The left hand side matrix of the operation</param>
/// <param name="rhs">The right hand side matrix of the operation</param>
/// <returns>The multiplied matrix</returns>
public static Matrix2D operator*(Matrix2D lhs, Matrix2D rhs)
{
Matrix2D res;
res.m00 = lhs.m00 * rhs.m00 + lhs.m01 * rhs.m10;
res.m01 = lhs.m00 * rhs.m01 + lhs.m01 * rhs.m11;
res.m02 = lhs.m00 * rhs.m02 + lhs.m01 * rhs.m12 + lhs.m02;
res.m10 = lhs.m10 * rhs.m00 + lhs.m11 * rhs.m10;
res.m11 = lhs.m10 * rhs.m01 + lhs.m11 * rhs.m11;
res.m12 = lhs.m10 * rhs.m02 + lhs.m11 * rhs.m12 + lhs.m12;
return res;
}
/// <summary>Transforms a Vector2 by a matrix.</summary>
/// <param name="lhs">The left hand side matrix of the operation</param>
/// <param name="vector">The vector the matrix will be multiplied with</param>
/// <returns>The transformed vector</returns>
public static Vector2 operator*(Matrix2D lhs, Vector2 vector)
{
Vector2 res;
res.x = lhs.m00 * vector.x + lhs.m01 * vector.y + lhs.m02;
res.y = lhs.m10 * vector.x + lhs.m11 * vector.y + lhs.m12;
return res;
}
/// <summary>Checks if two matrices are equal.</summary>
/// <param name="lhs">The left hand side matrix of the comparison</param>
/// <param name="rhs">The right hand side matrix of the comparison</param>
/// <returns>True if "lhs" and "rhs" are equal, or false otherwise.</returns>
public static bool operator==(Matrix2D lhs, Matrix2D rhs)
{
// Returns false in the presence of NaN values.
return lhs.GetColumn(0) == rhs.GetColumn(0)
&& lhs.GetColumn(1) == rhs.GetColumn(1)
&& lhs.GetColumn(2) == rhs.GetColumn(2);
}
/// <summary>Checks if two matrices are not equal.</summary>
/// <param name="lhs">The left hand side matrix of the comparison</param>
/// <param name="rhs">The right hand side matrix of the comparison</param>
/// <returns>True if "lhs" and "rhs" not are equal, or false otherwise.</returns>
public static bool operator!=(Matrix2D lhs, Matrix2D rhs)
{
// Returns true in the presence of NaN values.
return !(lhs == rhs);
}
/// <summary>Gets a column of the matrix.</summary>
/// <param name="index">The column index, between 0 and 2 inclusively</param>
/// <returns>The column at "index"</returns>
public Vector2 GetColumn(int index)
{
switch (index)
{
case 0: return new Vector2(m00, m10);
case 1: return new Vector2(m01, m11);
case 2: return new Vector2(m02, m12);
default:
throw new IndexOutOfRangeException("Invalid column index!");
}
}
/// <summary>Gets a row of the matrix.</summary>
/// <param name="index">The row index, between 0 and 1 inclusively</param>
/// <returns>The row at "index"</returns>
public Vector3 GetRow(int index)
{
switch (index)
{
case 0: return new Vector3(m00, m01, m02);
case 1: return new Vector3(m10, m11, m12);
default:
throw new IndexOutOfRangeException("Invalid row index!");
}
}
/// <summary>Sets a column of the matrix.</summary>
/// <param name="index">The column index, between 0 and 2 inclusively</param>
/// <param name="column">The column</param>
public void SetColumn(int index, Vector2 column)
{
this[0, index] = column.x;
this[1, index] = column.y;
}
/// <summary>Sets a row of the matrix.</summary>
/// <param name="index">The column index, between 0 and 1 inclusively</param>
/// <param name="row">The row</param>
public void SetRow(int index, Vector3 row)
{
this[index, 0] = row.x;
this[index, 1] = row.y;
this[index, 2] = row.z;
}
/// <summary>Transforms a position by this matrix (effectively by 2x3).</summary>
/// <param name="point">The point to multiply with this matrix</param>
/// <returns>The multiplied point</returns>
public Vector2 MultiplyPoint(Vector2 point)
{
Vector2 res;
res.x = this.m00 * point.x + this.m01 * point.y + this.m02;
res.y = this.m10 * point.x + this.m11 * point.y + this.m12;
return res;
}
/// <summary>Transforms a direction by this matrix.</summary>
/// <param name="vector">The direction to multiply with this matrix</param>
/// <returns>The multiplied direction</returns>
public Vector2 MultiplyVector(Vector2 vector)
{
Vector2 res;
res.x = this.m00 * vector.x + this.m01 * vector.y;
res.y = this.m10 * vector.x + this.m11 * vector.y;
return res;
}
/// <summary>Computes the inverse of the matrix.</summary>
/// <returns>The inverse matrix</returns>
public Matrix2D Inverse()
{
Matrix2D invMat = new Matrix2D();
float det = this[0, 0] * this[1, 1] - this[0, 1] * this[1, 0];
if (Mathf.Approximately(0.0f, det))
return zero;
float invDet = 1.0F / det;
invMat[0, 0] = this[1, 1] * invDet;
invMat[0, 1] = -this[0, 1] * invDet;
invMat[1, 0] = -this[1, 0] * invDet;
invMat[1, 1] = this[0, 0] * invDet;
// Do the translation part
invMat[0, 2] = -(this[0, 2] * invMat[0, 0] + this[1, 2] * invMat[0, 1]);
invMat[1, 2] = -(this[0, 2] * invMat[1, 0] + this[1, 2] * invMat[1, 1]);
return invMat;
}
/// <summary>Creates a scaling matrix.</summary>
/// <param name="vector">The scaling vector</param>
/// <returns>The scaling matrix</returns>
public static Matrix2D Scale(Vector2 vector)
{
Matrix2D m;
m.m00 = vector.x; m.m01 = 0F; m.m02 = 0F;
m.m10 = 0F; m.m11 = vector.y; m.m12 = 0F;
return m;
}
/// <summary>Creates a translation matrix.</summary>
/// <param name="vector">The translation vector</param>
/// <returns>The translation matrix</returns>
public static Matrix2D Translate(Vector2 vector)
{
Matrix2D m;
m.m00 = 1F; m.m01 = 0F; m.m02 = vector.x;
m.m10 = 0F; m.m11 = 1F; m.m12 = vector.y;
return m;
}
/// <summary>Creates a right-hand side rotation matrix.</summary>
/// <param name="angleRadians">The rotation angle, in radians</param>
/// <returns>The rotation matrix</returns>
public static Matrix2D RotateRH(float angleRadians)
{
return RotateLH(-angleRadians);
}
/// <summary>Creates a left-hand side rotation matrix.</summary>
/// <param name="angleRadians">The rotation angle, in radians</param>
/// <returns>The rotation matrix</returns>
public static Matrix2D RotateLH(float angleRadians)
{
// No SinCos? I hope the compiler optimizes this
float s = Mathf.Sin(angleRadians);
float c = Mathf.Cos(angleRadians);
Matrix2D m;
m.m00 = c; m.m10 = -s;
m.m01 = s; m.m11 = c;
m.m02 = 0.0F; m.m12 = 0.0F;
return m;
}
/// <summary>Creates a skew matrix on X.</summary>
/// <param name="angleRadians">The skew angle, in radians</param>
/// <returns>The skew matrix</returns>
public static Matrix2D SkewX(float angleRadians)
{
Matrix2D m;
m.m00 = 1.0f; m.m01 = Mathf.Tan(angleRadians); m.m02 = 0F;
m.m10 = 0F; m.m11 = 1.0f; m.m12 = 0F;
return m;
}
/// <summary>Creates a skew matrix on U.</summary>
/// <param name="angleRadians">The skew angle, in radians</param>
/// <returns>The skew matrix</returns>
public static Matrix2D SkewY(float angleRadians)
{
Matrix2D m;
m.m00 = 1.0f; m.m01 = 0F; m.m02 = 0F;
m.m10 = Mathf.Tan(angleRadians); m.m11 = 1.0f; m.m12 = 0F;
return m;
}
static readonly Matrix2D zeroMatrix = new Matrix2D(new Vector2(0, 0), new Vector2(0, 0), new Vector2(0, 0));
/// <summary>Returns a matrix with all elements set to zero (read-only).</summary>
/// <returns>The zero matrix</returns>
public static Matrix2D zero { get { return zeroMatrix; } }
static readonly Matrix2D identityMatrix = new Matrix2D(new Vector2(1, 0), new Vector2(0, 1), new Vector2(0, 0));
/// <summary>Returns the identity matrix (read-only).</summary>
/// <returns>The identity matrix</returns>
public static Matrix2D identity { get { return identityMatrix; } }
/// <summary>Returns a string representation of the matrix.</summary>
/// <returns>The matrix string representation</returns>
public override string ToString()
{
return string.Format("{0:F5}\t{1:F5}\t{2:F5}\n{3:F5}\t{4:F5}\t{5:F5}\n", m00, m01, m02, m10, m11, m12);
}
/// <summary>Returns a string representation of the matrix using a format.</summary>
/// <param name="format">The format to be used for the matrix components</param>
/// <returns>The matrix string representation</returns>
public string ToString(string format)
{
return string.Format("{0}\t{1}\t{2}\n{3}\t{4}\t{5}\n",
m00.ToString(format), m01.ToString(format), m02.ToString(format),
m10.ToString(format), m11.ToString(format), m12.ToString(format));
}
}
} //namespace
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 600ae451aa325bb479e48e4f91cf17e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aa2162f8539d856468fef95fc49e02e8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
internal class SVGPropertySheet : Dictionary<string, string> { }
internal class SVGStyleSheet
{
private List<KeyValuePair<string, SVGPropertySheet>> m_Selectors = new List<KeyValuePair<string, SVGPropertySheet>>();
public SVGPropertySheet this[string key]
{
get
{
int i = m_Selectors.FindIndex(x => x.Key == key);
if (i != -1)
return m_Selectors[i].Value;
return null;
}
set
{
var v = new KeyValuePair<string, SVGPropertySheet>(key, value);
int i = m_Selectors.FindIndex(x => x.Key == key);
if (i != -1)
m_Selectors[i] = v;
m_Selectors.Add(v);
}
}
public IEnumerable<string> selectors
{
get { return m_Selectors.Select(x => x.Key); }
}
public int Count
{
get { return m_Selectors.Count; }
}
public void Clear()
{
m_Selectors.Clear();
}
};
internal static class SVGStyleSheetUtils
{
public static SVGStyleSheet Parse(string cssText)
{
var result = new SVGStyleSheet();
var tokens = Tokenize(cssText);
var sheet = new SVGStyleSheet();
while (ParseSelector(tokens, sheet))
{
foreach (var sel in sheet.selectors)
{
if (result.selectors.Contains(sel))
CombineProperties(result[sel], sheet[sel]);
else
result[sel] = sheet[sel];
}
sheet.Clear();
}
return result;
}
public static SVGPropertySheet ParseInline(string cssText)
{
var tokens = Tokenize(cssText);
var props = new SVGPropertySheet();
ParseProperties(tokens, props);
return props;
}
private static bool ParseSelector(List<string> tokens, SVGStyleSheet sheet)
{
if (tokens.Count == 0)
return false;
var newSheet = new SVGStyleSheet();
while (true)
{
var selectorName = PopToken(tokens);
newSheet[selectorName] = new SVGPropertySheet();
while (PeekToken(tokens) == ",")
PopToken(tokens);
if (PeekToken(tokens) == "" || PeekToken(tokens) == "{")
break;
}
var sep = PopToken(tokens);
if (sep != "{")
{
Debug.LogError("Invalid CSS selector opening bracket: \"" + sep + "\"");
return false;
}
var props = new SVGPropertySheet();
ParseProperties(tokens, props);
// Transfer properties to the new selectors
foreach (var key in newSheet.selectors)
sheet[key] = CopyProperties(props);
sep = PopToken(tokens);
if (sep != "}")
{
Debug.LogError("Invalid CSS selector closing bracket: \"" + sep + "\"");
return false;
}
return true;
}
private static void CombineProperties(SVGPropertySheet first, SVGPropertySheet second)
{
foreach (var key in second.Keys)
first[key] = second[key];
}
private static SVGPropertySheet CopyProperties(SVGPropertySheet props)
{
var newProps = new SVGPropertySheet();
foreach (var v in props)
newProps[v.Key] = v.Value;
return newProps;
}
private static bool ParseProperties(List<string> tokens, SVGPropertySheet props)
{
string name;
string value;
while (ParseProperty(tokens, out name, out value))
{
props[name] = value;
while (PeekToken(tokens) == ";")
PopToken(tokens);
}
return true;
}
private static bool ParseProperty(List<string> tokens, out string name, out string value)
{
name = null;
value = null;
if (PeekToken(tokens) == "" || PeekToken(tokens) == "}")
return false;
name = PopToken(tokens);
var sep = PopToken(tokens);
if (sep != ":")
{
Debug.LogError("Invalid CSS property separator: \"" + sep + "\"");
return false;
}
value = "";
while (PeekToken(tokens) != "" && PeekToken(tokens) != ";" && PeekToken(tokens) != "}")
{
value = (value == "") ? PopToken(tokens) : value + " " + PopToken(tokens);
if (PeekToken(tokens) == "(")
value += ParseParenValue(tokens);
}
return true;
}
private static string ParseParenValue(List<string> tokens)
{
var opening = PopToken(tokens);
if (opening != "(")
{
Debug.LogError("Invaid CSS value opening");
return "";
}
var value = opening;
while (PeekToken(tokens) != "" && PeekToken(tokens) != ")")
value += PopToken(tokens);
if (PeekToken(tokens) != ")")
{
Debug.LogError("Invaid CSS value closing");
return "";
}
value += PopToken(tokens);
return value;
}
/// <summary>Breaks a CSS input into tokens</summary>
/// <param name="cssText">The CSS text</param>
/// <returns>A list of tokens</returns>
public static List<string> Tokenize(string cssText)
{
var tokens = new List<string>();
cssText = cssText.Replace(System.Environment.NewLine, ""); // Remove newlines
cssText = Regex.Replace(cssText, @"/\*.*?\*/", ""); // Remove CSS comments
cssText = Regex.Replace(cssText, @"<!--.*?-->", ""); // Remove XML comments
int from = 0;
int to = 0;
while (from < cssText.Length)
{
while (from < cssText.Length && IsWhitespace(cssText[from]))
++from;
to = from;
while (to < cssText.Length && !IsSeparator(cssText[to]))
++to;
if (from == to)
{
if (from < cssText.Length)
tokens.Add(cssText[from].ToString());
++to;
}
else
{
tokens.Add(cssText.Substring(from, to-from));
}
from = to;
}
return tokens;
}
private static string PeekToken(List<string> tokens)
{
if (tokens.Count == 0)
return "";
return tokens[0];
}
private static string PopToken(List<string> tokens)
{
if (tokens.Count == 0)
return "";
var tok = tokens[0];
tokens.RemoveAt(0);
return tok;
}
private static bool IsSeparator(char ch)
{
return IsWhitespace(ch) || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == '(' || ch == ')' || ch == ',';
}
private static bool IsWhitespace(char ch)
{
return ch == ' ' || ch == '\n' || ch == '\t';
}
}
} // namespace
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ad42887d8ca6e094093524d976e32104
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,100 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
public partial class VectorUtils
{
internal static BezierPathSegment[] BuildEllipsePath(Vector2 p0, Vector2 p1, float rotation, float rx, float ry, bool largeArc, bool sweep)
{
if ((p1-p0).magnitude < VectorUtils.Epsilon)
return new BezierPathSegment[0];
Vector2 c;
float theta1;
float sweepTheta;
float adjustedRx;
float adjustedRy;
ComputeEllipseParameters(p0, p1, rotation, rx, ry, largeArc, sweep, out c, out theta1, out sweepTheta, out adjustedRx, out adjustedRy);
BezierPathSegment[] path;
if (Mathf.Abs(sweepTheta) <= Mathf.Epsilon)
{
// Use a straight line if the sweep angle is tiny
path = VectorUtils.BezierSegmentToPath(VectorUtils.MakeLine(p0, p1));
}
else
{
path = VectorUtils.MakeArc(Vector2.zero, theta1, sweepTheta, 1.0f);
var scaling = new Vector2(adjustedRx, adjustedRy);
path = VectorUtils.TransformBezierPath(path, c, rotation, scaling);
}
return path;
}
private static void ComputeEllipseParameters(Vector2 p0, Vector2 p1, float phi, float rx, float ry, bool fa, bool fs, out Vector2 c, out float theta1, out float sweepTheta, out float adjustedRx, out float adjustedRy)
{
// See https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
var cosPhi = Mathf.Cos(phi);
var sinPhi = Mathf.Sin(phi);
var m0 = Matrix2D.identity;
m0.m00 = cosPhi;
m0.m01 = -sinPhi;
m0.m10 = sinPhi;
m0.m11 = cosPhi;
var m1 = m0;
m1.m01 = -m1.m01;
m1.m10 = -m1.m10;
var pp = m0 * new Vector2((p0.x - p1.x) / 2.0f, (p0.y - p1.y) / 2.0f);
rx = Mathf.Abs(rx);
ry = Mathf.Abs(ry);
EnsureRadiiAreLargeEnough(pp, ref rx, ref ry);
adjustedRx = rx;
adjustedRy = ry;
var ppx2 = pp.x * pp.x;
var ppy2 = pp.y * pp.y;
var rx2 = rx * rx;
var ry2 = ry * ry;
var cp = new Vector2((rx * pp.y) / ry, -(ry * pp.x) / rx);
cp *= Mathf.Sqrt(Mathf.Abs((rx2 * ry2 - rx2 * ppy2 - ry2 * ppx2) / (rx2 * ppy2 + ry2 * ppx2)));
if (fa == fs)
cp = -cp;
c = (m1 * cp) + new Vector2((p0.x + p1.x) / 2.0f, (p0.y + p1.y) / 2.0f);
theta1 = Vector2.SignedAngle(new Vector2(1, 0), new Vector2((pp.x - cp.x) / rx, (pp.y - cp.y) / ry)) % 360.0f;
sweepTheta = Vector2.SignedAngle(new Vector2((pp.x - cp.x) / rx, (pp.y - cp.y) / ry), new Vector2((-pp.x - cp.x) / rx, (-pp.y - cp.y) / ry));
if (!fs && sweepTheta > 0)
sweepTheta -= 360;
if (fs && sweepTheta < 0)
sweepTheta += 360;
theta1 *= Mathf.Deg2Rad;
sweepTheta *= Mathf.Deg2Rad;
}
private static void EnsureRadiiAreLargeEnough(Vector2 p, ref float rx, ref float ry)
{
var d = (p.x * p.x) / (rx * rx) + (p.y * p.y) / (ry * ry);
if (d > 1.0f)
{
var sqrtD = Mathf.Sqrt(d);
rx *= sqrtD;
ry *= sqrtD;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5c16ccd991535447911bd905325bbc0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,106 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
public partial class VectorUtils
{
/// <summary>Builds a BezierContour from a Rectangle.</summary>
/// <param name="rect">The rectangle to build the contour from</param>
/// <param name="radiusTL">The top-left radius of the rectangle</param>
/// <param name="radiusTR">The top-right radius of the rectangle</param>
/// <param name="radiusBR">The bottom-right radius of the rectangle</param>
/// <param name="radiusBL">The bottom-left radius of the rectangle</param>
/// <returns>A BezierContour that follows the rectangle contour</returns>
public static BezierContour BuildRectangleContour(Rect rect, Vector2 radiusTL, Vector2 radiusTR, Vector2 radiusBR, Vector2 radiusBL)
{
var width = rect.size.x;
var height = rect.size.y;
var halfSize = new Vector2(width / 2.0f, height / 2.0f);
radiusTL = Vector2.Max(Vector2.Min(radiusTL, halfSize), Vector2.zero);
radiusTR = Vector2.Max(Vector2.Min(radiusTR, halfSize), Vector2.zero);
radiusBR = Vector2.Max(Vector2.Min(radiusBR, halfSize), Vector2.zero);
radiusBL = Vector2.Max(Vector2.Min(radiusBL, halfSize), Vector2.zero);
var leftSegmentSize = height - (radiusBL.y + radiusTL.y);
var topSegmentSize = width - (radiusTL.x + radiusTR.x);
var rightSegmentSize = height - (radiusBR.y + radiusTR.y);
var bottomSegmentSize = width - (radiusBL.x + radiusBR.x);
var segments = new List<BezierPathSegment>(8);
BezierPathSegment seg;
if (leftSegmentSize > VectorUtils.Epsilon)
{
seg = MakePathLine(new Vector2(0.0f, radiusTL.y + leftSegmentSize), new Vector2(0.0f, radiusTL.y))[0];
segments.Add(seg);
}
if (radiusTL.magnitude > VectorUtils.Epsilon)
{
var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI, Mathf.PI / 2.0f, 1.0f);
circleArc = VectorUtils.TransformBezierPath(circleArc, radiusTL, 0.0f, radiusTL);
segments.Add(circleArc[0]);
}
if (topSegmentSize > VectorUtils.Epsilon)
{
seg = MakePathLine(new Vector2(radiusTL.x, 0.0f), new Vector2(radiusTL.x + topSegmentSize, 0.0f))[0];
segments.Add(seg);
}
if (radiusTR.magnitude > VectorUtils.Epsilon)
{
var topRight = new Vector2(width - radiusTR.x, radiusTR.y);
var circleArc = VectorUtils.MakeArc(Vector2.zero, -Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f);
circleArc = VectorUtils.TransformBezierPath(circleArc, topRight, 0.0f, radiusTR);
segments.Add(circleArc[0]);
}
if (rightSegmentSize > VectorUtils.Epsilon)
{
seg = MakePathLine(new Vector2(width, radiusTR.y), new Vector2(width, radiusTR.y + rightSegmentSize))[0];
segments.Add(seg);
}
if (radiusBR.magnitude > VectorUtils.Epsilon)
{
var bottomRight = new Vector2(width - radiusBR.x, height - radiusBR.y);
var circleArc = VectorUtils.MakeArc(Vector2.zero, 0.0f, Mathf.PI / 2.0f, 1.0f);
circleArc = VectorUtils.TransformBezierPath(circleArc, bottomRight, 0.0f, radiusBR);
segments.Add(circleArc[0]);
}
if (bottomSegmentSize > VectorUtils.Epsilon)
{
seg = MakePathLine(new Vector2(width - radiusBR.x, height), new Vector2(width - (radiusBR.x + bottomSegmentSize), height))[0];
segments.Add(seg);
}
if (radiusBL.magnitude > VectorUtils.Epsilon)
{
var bottomLeft = new Vector2(radiusBL.x, height - radiusBL.y);
var circleArc = VectorUtils.MakeArc(Vector2.zero, Mathf.PI / 2.0f, Mathf.PI / 2.0f, 1.0f);
circleArc = VectorUtils.TransformBezierPath(circleArc, bottomLeft, 0.0f, radiusBL);
segments.Add(circleArc[0]);
}
// Offset segments to position
for (int i = 0; i < segments.Count; ++i)
{
var s = segments[i];
s.P0 += rect.position;
s.P1 += rect.position;
s.P2 += rect.position;
segments[i] = s;
}
return new BezierContour() { Segments = segments.ToArray(), Closed = true };
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ab9bcde9af121f45a0d120f28f80d7c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
/// <summary>The gradient fill types.</summary>
public enum GradientFillType
{
/// <summary>A linear gradient.</summary>
Linear,
/// <summary>A radial gradient, centered at the radial focus of the gradient fill.</summary>
Radial
}
/// <summary>The path corner types, for joining path segments together.</summary>
public enum PathCorner
{
/// <summary>A tipped corner with a sharp edge.</summary>
Tipped,
/// <summary>A rounded corner.</summary>
Round,
/// <summary>A beveled corner.</summary>
Beveled
}
/// <summary>The path ending types.</summary>
public enum PathEnding
{
/// <summary>A square path ending.</summary>
Chop,
/// <summary>A square path ending with a small extrusion.</summary>
Square,
/// <summary>A rounded path ending.</summary>
Round
}
/// <summary>The fill mode types.</summary>
public enum FillMode
{
/// <summary>Determines the "insideness" of the shape by evaluating the direction of the edges crossed.</summary>
NonZero,
/// <summary>Determines the "insideness" of the shape by counting the number of edges crossed.</summary>
OddEven
}
/// <summary>The addressing mode, defining how textures or gradients behave when being addressed outside their unit range.</summary>
public enum AddressMode
{
/// <summary>Textures/gradients are wrapping around with a repeating pattern.</summary>
Wrap,
/// <summary>Textures/gradients are clamped on the borders.</summary>
Clamp,
/// <summary>Textures/gradients are repeated with a mirroring pattern.</summary>
Mirror
}
/// <summary>The gradient stops used for gradient fills.</summary>
public struct GradientStop
{
/// <summary>The color of the stop.</summary>
public Color Color { get; set; }
/// <summary>At which percentage this stop applies. Should be between 0 and 1, inclusively.</summary>
public float StopPercentage { get; set; }
}
/// <summary>A bezier segment.</summary>
/// <remarks>
/// Cubic Bezier segment starts from P0, flies in tangent to direction from P0 to P1,
/// then lands in direction from P2 to P3, to finally end exactly at P3.
/// </remarks>
public struct BezierSegment
{
/// <summary>Origin point of the segment.</summary>
public Vector2 P0;
/// <summary>First control point of the segment.</summary>
public Vector2 P1;
/// <summary>Second control point of the segment.</summary>
public Vector2 P2;
/// <summary>Ending point of the segment.</summary>
public Vector2 P3;
}
/// <summary>A bezier path segment.</summary>
/// <remarks>
/// Like BezierSegment but implies connectivity of segments, where segments[0].P3 is actually segments[1].P0
/// </remarks>
public struct BezierPathSegment
{
/// <summary>Origin point of the segment.</summary>
public Vector2 P0;
/// <summary>First control point of the segment.</summary>
public Vector2 P1;
/// <summary>Second control point of the segment.</summary>
public Vector2 P2;
}
/// <summary>A chain of bezier paths, optionnally closed.</summary>
public struct BezierContour
{
/// <summary>An array of every path segments on the contour.</summary>
/// <remarks>Closed paths should not add a dedicated closing segment. It is implied by the 'closed' property.</remarks>
public BezierPathSegment[] Segments { get; set; }
/// <summary>A boolean indicating if the contour should be closed.</summary>
/// <remarks>
/// When set to true, closed path will connect the last path segment to the first path segment, by using the
/// last path segment's P1 and P2 as control points.
/// </remarks>
public bool Closed { get; set; }
}
/// <summary>The IFill interface is implemented by filling techniques (solid, texture or gradient).</summary>
public interface IFill
{
/// <summary>The filling method (non-zero or even-odd) of the fill.</summary>
FillMode Mode { get; set; }
/// <summary>The opacity of the fill.</summary>
float Opacity { get; set; }
}
/// <summary>Fills a shape with a single color.</summary>
public class SolidFill : IFill
{
/// <summary>The color of the fill.</summary>
public Color Color { get; set; }
/// <summary>The opacity of the fill.</summary>
public float Opacity { get { return m_Opacity; } set { m_Opacity = value; } }
private float m_Opacity = 1.0f;
/// <summary>The filling method (non-zero or even-odd) of the fill.</summary>
public FillMode Mode { get; set; }
}
/// <summary>Fills a shape with a gradient.</summary>
/// <remarks>
/// Size of the fill is always assumed to cover the entire element's bounding box.
/// Radial fills are centered in the element's bounding box. Its radii are half the bounding box dimensions in each direction.
/// Linear fills start from the left edge to the right edge of the element's bounding box.
/// </remarks>
public class GradientFill : IFill
{
/// <summary>The fill type (linear or gradient).</summary>
public GradientFillType Type { get; set; }
/// <summary>An array of stops defining the gradient colors.</summary>
public GradientStop[] Stops { get; set; }
/// <summary>The filling method (non-zero or even-odd) of the fill.</summary>
public FillMode Mode { get; set; }
/// <summary>The opacity of the fill.</summary>
public float Opacity { get { return m_Opacity; } set { m_Opacity = value; } }
private float m_Opacity = 1.0f;
/// <summary>The adressing mode (wrap, clamp or mirror) of this fill.</summary>
public AddressMode Addressing { get; set; }
/// <summary>A position within the unit circle (-1,1) where 0 falls in the middle of the fill.</summary>
public Vector2 RadialFocus { get; set; }
}
/// <summary>Fills a shape with a texture.</summary>
public class TextureFill : IFill
{
/// <summary>The texture to fill the shape with.</summary>
public Texture2D Texture { get; set; }
/// <summary>The filling method (non-zero or even-odd) of the fill.</summary>
public FillMode Mode { get; set; }
/// <summary>The opacity of the fill.</summary>
public float Opacity { get { return m_Opacity; } set { m_Opacity = value; } }
private float m_Opacity = 1.0f;
/// <summary>The adressing mode (wrap, clamp or mirror) of this fill.</summary>
public AddressMode Addressing { get; set; }
}
/// <summary>Fills a shape with a pattern.</summary>
public class PatternFill : IFill
{
/// <summary>The filling method (non-zero or even-odd) of the fill.</summary>
public FillMode Mode { get; set; }
/// <summary>The opacity of the fill.</summary>
public float Opacity { get { return m_Opacity; } set { m_Opacity = value; } }
private float m_Opacity = 1.0f;
/// <summary>The root node of the pattern</summary>
public SceneNode Pattern { get; set; }
/// <summary>The rectangle that is repeated</summary>
public Rect Rect { get; set; }
}
/// <summary>Defines how strokes should be rendered.</summary>
public class Stroke
{
/// <summary>The stroke color.</summary>
public Color Color {
get {
var solidFill = Fill as SolidFill;
if (solidFill == null) return new Color();
return solidFill.Color;
}
set {
Fill = new SolidFill() { Color = value };
}
}
/// <summary>The stroke fill.</summary>
public IFill Fill { get; set; }
/// <summary>A transformation specific to the fill.</summary>
public Matrix2D FillTransform { get { return m_FillTransform; } set { m_FillTransform = value; } }
private Matrix2D m_FillTransform = Matrix2D.identity;
/// <summary>The stroke half-thickness.</summary>
public float HalfThickness { get; set; }
/// <summary>The stroke pattern (dashes).</summary>
/// <remarks>Even entries mark a fill and odd entries mark void</remarks>
public float[] Pattern { get; set; }
/// <summary>An offset to where the pattern should start.</summary>
public float PatternOffset { get; set; }
/// <summary>How far the tipped corners may extrude.</summary>
public float TippedCornerLimit { get; set; }
}
/// <summary>Defines properties of paths.</summary>
public struct PathProperties
{
/// <summary>The stroke used to render the path.</summary>
public Stroke Stroke { get; set; }
/// <summary>How the beginning of the path should be displayed.</summary>
public PathEnding Head { get; set; }
/// <summary>How the end of the path should be displayed.</summary>
public PathEnding Tail { get; set; }
/// <summary>How the corners of the path should be displayed.</summary>
public PathCorner Corners { get; set; }
}
/// <summary>A generic filled shape.</summary>
public class Shape
{
/// <summary>All the contours defining the shape.</summary>
/// <remarks>
/// Some of these coutours may be holes in the shape, depending on the fill mode used <see cref="FillMode"/>.
/// </remarks>
public BezierContour[] Contours { get; set; }
/// <summary>The fill used on the shape.</summary>
public IFill Fill { get; set; }
/// <summary>A transformation specific to the fill.</summary>
public Matrix2D FillTransform { get { return m_FillTransform; } set { m_FillTransform = value; } }
private Matrix2D m_FillTransform = Matrix2D.identity;
/// <summary>The path properties.</summary>
public PathProperties PathProps { get; set; }
/// <summary>Whether the specified contours are convex or not</summary>
/// <remarks>
/// Set this to true when you know the shape contours are convex.
/// This will allow for a faster tessellation process in some circumstances.
/// </remarks>
public bool IsConvex { get; set; }
}
/// <summary>A node inside a hierarchy.</summary>
public class SceneNode
{
/// <summary>The list of children nodes.</summary>
public List<SceneNode> Children { get; set; }
/// <summary>The list of shapes inside this node.</summary>
public List<Shape> Shapes { get; set; }
/// <summary>The transform of the node.</summary>
public Matrix2D Transform { get { return m_Transform; } set { m_Transform = value; } }
private Matrix2D m_Transform = Matrix2D.identity;
/// <summary>A clipper hierarchy that will clip this node.</summary>
public SceneNode Clipper { get; set; }
}
/// <summary>A scene contains the whole node hierarchy.</summary>
public class Scene
{
/// <summary>The root of the node hierarchy.</summary>
public SceneNode Root { get; set; }
}
} // namespace
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6317336283684d540b187222a9055e78
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,317 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.U2D;
#if !(UNITY_2019_3_OR_NEWER)
using UnityEngine.Experimental.U2D;
#endif
using UnityEngine.Experimental.Rendering;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
public static partial class VectorUtils
{
/// <summary>The alignement of the sprite, to determine the location of the pivot.</summary>
public enum Alignment
{
/// <summary>Center alignment.</summary>
Center = 0,
/// <summary>Top-left alignment.</summary>
TopLeft = 1,
/// <summary>Top-center alignment.</summary>
TopCenter = 2,
/// <summary>Top-right alignment.</summary>
TopRight = 3,
/// <summary>Left-center alignment.</summary>
LeftCenter = 4,
/// <summary>Right-center alignment.</summary>
RightCenter = 5,
/// <summary>Bottom-left alignment.</summary>
BottomLeft = 6,
/// <summary>Bottom-center alignment.</summary>
BottomCenter = 7,
/// <summary>Bottom-right alignment.</summary>
BottomRight = 8,
/// <summary>Custom alignment.</summary>
/// <remarks>
/// Uses a custom alignment that will be used when building the sprite using the <see cref="BuildSprite"/> method.
/// </remarks>
Custom = 9,
/// <summary>SVG origin alignment.</summary>
/// <remarks>
/// This will use the origin of the SVG document as the origin of the sprite.
/// </remarks>
SVGOrigin = 10
}
private static void FlipYAxis(IList<Vector2> vertices)
{
var bbox = Bounds(vertices);
var h = bbox.height;
for (int i = 0; i < vertices.Count; ++i)
{
var v = vertices[i];
v.y -= bbox.position.y;
v.y = h - v.y;
v.y += bbox.position.y;
vertices[i] = v;
}
}
internal enum WindingDir
{
CW,
CCW
}
internal static void AdjustWinding(Vector2[] vertices, UInt16[] indices, WindingDir dir)
{
bool shouldFlip = false;
var length = indices.Length;
for (int i = 0; i < (length - 2); i += 3)
{
var v0 = (Vector3)vertices[indices[i]];
var v1 = (Vector3)vertices[indices[i+1]];
var v2 = (Vector3)vertices[indices[i+2]];
var s = (v1 - v0).normalized;
var t = (v2 - v0).normalized;
float dot = Vector3.Dot(s, t);
if (s == Vector3.zero || t == Vector3.zero || dot > 0.9999f || dot < -0.9999f)
continue;
var n = Vector3.Cross(s, t);
if (n.sqrMagnitude < 0.0001f)
continue;
shouldFlip = dir == WindingDir.CCW ? n.z < 0.0f : n.z > 0.0f;
break;
}
if (shouldFlip)
{
for (int i = 0; i < (length - 2); i += 3)
{
var tmp = indices[i];
indices[i] = indices[i+1];
indices[i+1] = tmp;
}
}
}
private static void FlipRangeIfNecessary(List<Vector2> vertices, List<UInt16> indices, int indexStart, int indexEnd, bool flipYAxis)
{
// For the range, find the first valid triangle and check its winding order. If that triangle needs flipping, then flip the whole range.
bool shouldFlip = false;
for (int i = indexStart; i < (indexEnd - 2); i += 3)
{
var v0 = (Vector3)vertices[indices[i]];
var v1 = (Vector3)vertices[indices[i + 1]];
var v2 = (Vector3)vertices[indices[i + 2]];
var s = (v1 - v0).normalized;
var t = (v2 - v0).normalized;
float dot = Vector3.Dot(s, t);
if (s == Vector3.zero || t == Vector3.zero || dot > 0.99f || dot < -0.99f)
continue;
var n = Vector3.Cross(s, t);
if (n.sqrMagnitude < 0.001f)
continue;
shouldFlip = flipYAxis ? n.z < 0.0f : n.z > 0.0f;
break;
}
if (shouldFlip)
{
for (int i = indexStart; i < (indexEnd - 2); i += 3)
{
var tmp = indices[i + 1];
indices[i + 1] = indices[i + 2];
indices[i + 2] = tmp;
}
}
}
internal static void RenderFromArrays(Vector2[] vertices, UInt16[] indices, Vector2[] uvs, Color[] colors, Vector2[] settings, Texture2D texture, Material mat, bool clear = true)
{
mat.SetTexture("_MainTex", texture);
mat.SetPass(0);
if (clear)
GL.Clear(true, true, Color.clear);
GL.PushMatrix();
GL.LoadOrtho();
GL.Color(new Color(1, 1, 1, 1));
GL.Begin(GL.TRIANGLES);
for (int i = 0; i < indices.Length; ++i)
{
ushort index = indices[i];
Vector2 vertex = vertices[index];
Vector2 uv = uvs[index];
GL.TexCoord2(uv.x, uv.y);
if (settings != null)
{
var setting = settings[index];
GL.MultiTexCoord2(2, setting.x, setting.y);
}
if (colors != null)
GL.Color(colors[index]);
GL.Vertex3(vertex.x, vertex.y, 0);
}
GL.End();
GL.PopMatrix();
mat.SetTexture("_MainTex", null);
}
/// <summary>Draws a vector sprite using the provided material.</summary>
/// <param name="sprite">The sprite to render</param>
/// <param name="mat">The material used for rendering</param>
/// <param name="clear">If true, clear the render target before rendering</param>
public static void RenderSprite(Sprite sprite, Material mat, bool clear = true)
{
float spriteWidth = sprite.rect.width;
float spriteHeight = sprite.rect.height;
float pixelsToUnits = sprite.rect.width / sprite.bounds.size.x;
var uvs = sprite.uv;
var triangles = sprite.triangles;
var pivot = sprite.pivot;
var vertices = sprite.vertices.Select(v =>
new Vector2((v.x * pixelsToUnits + pivot.x)/spriteWidth,
(v.y * pixelsToUnits + pivot.y)/spriteHeight)
).ToArray();
Color[] colors = null;
if (sprite.HasVertexAttribute(VertexAttribute.Color))
colors = sprite.GetVertexAttribute<Color32>(VertexAttribute.Color).Select(c => (Color)c).ToArray();
Vector2[] settings = null;
if (sprite.HasVertexAttribute(VertexAttribute.TexCoord2))
settings = sprite.GetVertexAttribute<Vector2>(VertexAttribute.TexCoord2).ToArray();
RenderFromArrays(vertices, sprite.triangles, sprite.uv, colors, settings, sprite.texture, mat, clear);
}
private static Material s_ExpandEdgesMat;
/// <summary>Renders a vector sprite to Texture2D.</summary>
/// <param name="sprite">The sprite to render</param>
/// <param name="width">The desired width of the resulting texture</param>
/// <param name="height">The desired height of the resulting texture</param>
/// <param name="mat">The material used to render the sprite</param>
/// <param name="antiAliasing">The number of samples per pixel for anti-aliasing</param>
/// <param name="expandEdges">When true, expand the edges to avoid a dark banding effect caused by filtering. This is slower to render and uses more graphics memory.</param>
/// <returns>A Texture2D object containing the rendered vector sprite</returns>
public static Texture2D RenderSpriteToTexture2D(Sprite sprite, int width, int height, Material mat, int antiAliasing = 1, bool expandEdges = false)
{
if (width <= 0 || height <= 0)
return null;
RenderTexture tex = null;
var oldActive = RenderTexture.active;
var desc = new RenderTextureDescriptor(width, height, RenderTextureFormat.ARGB32, 0) {
msaaSamples = 1,
sRGB = QualitySettings.activeColorSpace == ColorSpace.Linear
};
if (expandEdges)
{
// Draw the sprite normally to be used as a background, no-antialiasing
var normalTex = RenderTexture.GetTemporary(desc);
RenderTexture.active = normalTex;
RenderSprite(sprite, mat);
// Expand the edges and make completely transparent
if (s_ExpandEdgesMat == null)
{
var shader = Shader.Find("Hidden/VectorExpandEdges");
if (shader == null)
{
#if UNITY_EDITOR
// Workaround for case 1167309.
// Shader.Find() seems to fail on the package shader when doing a fresh import with a clean Library folder,
// but AssetDatabase.LoadAssetAtPath() works fine though.
shader = UnityEditor.AssetDatabase.LoadAssetAtPath<Shader>("Packages/com.unity.vectorgraphics/Runtime/Shaders/VectorExpandEdges.shader");
#else
return null;
#endif
}
s_ExpandEdgesMat = new Material(shader);
}
var expandTex = RenderTexture.GetTemporary(desc);
RenderTexture.active = expandTex;
GL.Clear(false, true, Color.clear);
Graphics.Blit(normalTex, expandTex, s_ExpandEdgesMat, 0);
RenderTexture.ReleaseTemporary(normalTex);
// Draw the sprite again, but clear with the texture rendered in the previous step,
// this will make the bilinear filter to interpolate the colors with values different
// than "transparent black", which causes black-ish outlines around the shape.
desc.msaaSamples = antiAliasing;
tex = RenderTexture.GetTemporary(desc);
RenderTexture.active = tex;
Graphics.Blit(expandTex, tex);
RenderTexture.ReleaseTemporary(expandTex); // Use the expanded texture to clear the buffer
RenderTexture.active = tex;
RenderSprite(sprite, mat, false);
}
else
{
desc.msaaSamples = antiAliasing;
tex = RenderTexture.GetTemporary(desc);
RenderTexture.active = tex;
RenderSprite(sprite, mat);
}
Texture2D copy = new Texture2D(width, height, TextureFormat.RGBA32, false);
copy.hideFlags = HideFlags.HideAndDontSave;
copy.ReadPixels(new Rect(0, 0, width, height), 0, 0);
copy.Apply();
RenderTexture.active = oldActive;
RenderTexture.ReleaseTemporary(tex);
return copy;
}
internal static Vector2 GetPivot(Alignment alignment, Vector2 customPivot, Rect bbox, bool flipYAxis)
{
switch (alignment)
{
case Alignment.Center: return new Vector2(0.5f, 0.5f);
case Alignment.TopLeft: return new Vector2(0.0f, 1.0f);
case Alignment.TopCenter: return new Vector2(0.5f, 1.0f);
case Alignment.TopRight: return new Vector2(1.0f, 1.0f);
case Alignment.LeftCenter: return new Vector2(0.0f, 0.5f);
case Alignment.RightCenter: return new Vector2(1.0f, 0.5f);
case Alignment.BottomLeft: return new Vector2(0.0f, 0.0f);
case Alignment.BottomCenter: return new Vector2(0.5f, 0.0f);
case Alignment.BottomRight: return new Vector2(1.0f, 0.0f);
case Alignment.SVGOrigin:
{
var p = -bbox.position / bbox.size;
if (flipYAxis)
p.y = 1.0f - p.y;
return p;
}
case Alignment.Custom: return customPivot;
}
return Vector2.zero;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c138b02f3c416c146aa000916ae2f816
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,861 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace ToolBuddy.ThirdParty.VectorGraphics
{
public static partial class VectorUtils
{
/// <summary>
/// Structure to store the tessellation options.
/// </summary>
public struct TessellationOptions
{
private float m_MaxCordDev, m_MaxCordDevSq, m_MaxTanAngleDev, m_MaxTanAngleDevCosine, m_StepSize;
/// <summary>
/// The uniform tessellation step distance.
/// </summary>
public float StepDistance { get; set; } // A split to happen uniformly at fixed distances
/// <summary>
/// The maximum distance on the cord to a straight line between to points after which more tessellation will be generated.
/// To disable, specify float.MaxValue.
/// </summary>
public float MaxCordDeviation // Maximum distance allowed between a cord and its line projection
{
get { return m_MaxCordDev; }
set
{
m_MaxCordDev = Mathf.Max(value, 0.0f);
m_MaxCordDevSq = (m_MaxCordDev == float.MaxValue) ? float.MaxValue : m_MaxCordDev * m_MaxCordDev;
}
}
internal float MaxCordDeviationSquared { get { return m_MaxCordDevSq; } }
/// <summary>
/// The maximum angle (in degrees) between the curve tangent and the next point after which more tessellation will be generated.
/// To disable, specify float.MaxValue.
/// </summary>
public float MaxTanAngleDeviation // The maximum angle allowed (in radians) between tangents before a split happens
{
get { return m_MaxTanAngleDev; }
set
{
m_MaxTanAngleDev = Mathf.Clamp(value, VectorUtils.Epsilon, Mathf.PI * 0.5f);
m_MaxTanAngleDevCosine = Mathf.Cos(m_MaxTanAngleDev);
}
}
internal float MaxTanAngleDeviationCosine { get { return m_MaxTanAngleDevCosine; } } // Cosine of the maximum angle allowed between tangents before a split happens
/// <summary>
/// The number of samples used internally to evaluate the curves. More samples = higher quality.
/// Should be between 0 and 1 (inclusive).
/// </summary>
public float SamplingStepSize
{
get { return m_StepSize; }
set { m_StepSize = Mathf.Clamp(value, Epsilon, 1.0f); }
}
}
/// <summary>
/// Tessellates a path.
/// </summary>
/// <param name="contour">The path to tessellate</param>
/// <param name="pathProps">The path properties</param>
/// <param name="tessellateOptions">The tessellation options</param>
/// <param name="vertices">The resulting vertices</param>
/// <param name="indices">The resulting triangles</param>
/// <remarks>
/// The individual line segments generated during tessellation are made out of a set of ordered vertices. It is important
/// to honor this ordering so joining and and capping connect properly with the existing vertices without generating dupes.
/// The ordering assumed is as follows:
/// The last two vertices of a piece must be such that the first is generated at the end with a positive half-thickness
/// while the second vertex is at the end too but at a negative half-thickness.
/// No assumptions are enforced for other vertices before the two last vertices.
/// </remarks>
public static void TessellatePath(BezierContour contour, PathProperties pathProps, TessellationOptions tessellateOptions, out Vector2[] vertices, out UInt16[] indices)
{
if (tessellateOptions.StepDistance < Epsilon)
throw new Exception("stepDistance too small");
if (contour.Segments.Length < 2)
{
vertices = new Vector2[0];
indices = new UInt16[0];
return;
}
tessellateOptions.MaxCordDeviation = Mathf.Max(0.0001f, tessellateOptions.MaxCordDeviation);
tessellateOptions.MaxTanAngleDeviation = Mathf.Max(0.0001f, tessellateOptions.MaxTanAngleDeviation);
UnityEngine.Profiling.Profiler.BeginSample("TessellatePath");
float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed);
// Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work
float approxTotalLength = 0.0f;
foreach (var s in segmentLengths)
approxTotalLength += s;
int approxStepCount = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2);
if (pathProps.Stroke.Pattern != null)
approxStepCount += pathProps.Stroke.Pattern.Length * 2;
List<Vector2> verts = new List<Vector2>(approxStepCount * 2 + 32); // A little bit possibly for the endings
List<UInt16> inds = new List<UInt16>((int)(verts.Capacity * 1.5f)); // Usually every 4 verts represent a quad that uses 6 indices
var patternIt = new PathPatternIterator(pathProps.Stroke.Pattern, pathProps.Stroke.PatternOffset);
var pathIt = new PathDistanceForwardIterator(contour.Segments, contour.Closed, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize);
JoiningInfo[] joiningInfo = new JoiningInfo[2];
HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths);
int rangeIndex = 0;
while (!pathIt.Ended)
{
if (patternIt.IsSolid)
TessellateRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, tessellateOptions, joiningInfo, segmentLengths, approxTotalLength, rangeIndex++, verts, inds);
else
SkipRange(patternIt.SegmentLength, pathIt, patternIt, pathProps, joiningInfo, segmentLengths);
patternIt.Advance();
}
vertices = verts.ToArray();
indices = inds.ToArray();
UnityEngine.Profiling.Profiler.EndSample();
}
static Vector2[] TraceShape(BezierContour contour, Stroke stroke, TessellationOptions tessellateOptions)
{
if (tessellateOptions.StepDistance < Epsilon)
throw new Exception("stepDistance too small");
if (contour.Segments.Length < 2)
return new Vector2[0];
float[] segmentLengths = VectorUtils.SegmentsLengths(contour.Segments, contour.Closed);
// Approximate the number of vertices/indices we need to store the results so we reduce memory reallocations during work
float approxTotalLength = 0.0f;
foreach (var s in segmentLengths)
approxTotalLength += s;
int approxStepCount = Math.Max((int)(approxTotalLength / tessellateOptions.StepDistance + 0.5f), 2);
var strokePattern = stroke != null ? stroke.Pattern : null;
var strokePatternOffset = stroke != null ? stroke.PatternOffset : 0.0f;
if (strokePattern != null)
approxStepCount += strokePattern.Length * 2;
List<Vector2> verts = new List<Vector2>(approxStepCount); // A little bit possibly for the endings
var patternIt = new PathPatternIterator(strokePattern, strokePatternOffset);
var pathIt = new PathDistanceForwardIterator(contour.Segments, true, tessellateOptions.MaxCordDeviationSquared, tessellateOptions.MaxTanAngleDeviationCosine, tessellateOptions.SamplingStepSize);
verts.Add(pathIt.EvalCurrent());
while (!pathIt.Ended)
{
float distance = patternIt.SegmentLength;
float startingLength = pathIt.LengthSoFar;
float unitsRemaining = Mathf.Min(tessellateOptions.StepDistance, distance);
bool endedEntirePath = false;
for (;;)
{
var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining);
if (result == PathDistanceForwardIterator.Result.Ended)
{
endedEntirePath = true;
break;
}
else if (result == PathDistanceForwardIterator.Result.NewSegment)
verts.Add(pathIt.EvalCurrent());
if ((unitsRemaining <= Epsilon) &&
!TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance))
{
break;
}
if (result == PathDistanceForwardIterator.Result.Stepped)
verts.Add(pathIt.EvalCurrent());
}
// Ending
if (endedEntirePath)
break;
else verts.Add(pathIt.EvalCurrent());
patternIt.Advance();
}
if ((verts[0] - verts[verts.Count - 1]).sqrMagnitude < Epsilon)
verts.RemoveAt(verts.Count - 1);
return verts.ToArray(); // Why not return verts itself?
}
static bool TryGetMoreRemainingUnits(ref float unitsRemaining, PathDistanceForwardIterator pathIt, float startingLength, float distance, float stepDistance)
{
float distanceCrossedSoFar = pathIt.LengthSoFar - startingLength;
float epsilon = Math.Max(Epsilon, distance * Epsilon * 100.0f);
if ((distance - distanceCrossedSoFar) <= epsilon)
return false;
if (distanceCrossedSoFar + stepDistance > distance)
unitsRemaining = distance - distanceCrossedSoFar;
else unitsRemaining = stepDistance;
return true;
}
static void HandleNewSegmentJoining(PathDistanceForwardIterator pathIt, PathPatternIterator patternIt, JoiningInfo[] joiningInfo, float halfThickness, float[] segmentLengths)
{
joiningInfo[0] = joiningInfo[1];
joiningInfo[1] = null;
if (!patternIt.IsSolidAt(pathIt.LengthSoFar + segmentLengths[pathIt.CurrentSegment]))
return; // The joining center falls outside the pattern, so don't join... period
if (pathIt.Closed && pathIt.Segments.Count <= 2)
return; // Not enough segments to do proper closing
if (pathIt.Closed)
{
JoiningInfo closing;
if ((pathIt.CurrentSegment == 0) || (pathIt.CurrentSegment == pathIt.Segments.Count - 2))
{
closing = ForeseeJoining(
VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.Segments.Count - 2),
VectorUtils.PathSegmentAtIndex(pathIt.Segments, 0),
halfThickness, segmentLengths[pathIt.Segments.Count - 2]);
if (pathIt.CurrentSegment == 0)
joiningInfo[0] = closing;
else
{
joiningInfo[1] = closing;
return;
}
}
else if (pathIt.CurrentSegment > pathIt.Segments.Count - 2)
return;
}
else if (pathIt.CurrentSegment >= pathIt.Segments.Count - 2)
return;
joiningInfo[1] = ForeseeJoining(
VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment),
VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment + 1),
halfThickness, segmentLengths[pathIt.CurrentSegment]);
}
static void SkipRange(
float distance, PathDistanceForwardIterator pathIt, PathPatternIterator patternIt,
PathProperties pathProps, JoiningInfo[] joiningInfo, float[] segmentLengths)
{
float unitsRemaining = distance;
while (unitsRemaining > Epsilon)
{
var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining);
switch (result)
{
case PathDistanceForwardIterator.Result.Ended:
return;
case PathDistanceForwardIterator.Result.Stepped:
if (unitsRemaining < Epsilon)
return;
break;
case PathDistanceForwardIterator.Result.NewSegment:
HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths);
break;
}
}
}
static void TessellateRange(
float distance, PathDistanceForwardIterator pathIt, PathPatternIterator patternIt, PathProperties pathProps,
TessellationOptions tessellateOptions, JoiningInfo[] joiningInfo, float[] segmentLengths, float totalLength, int rangeIndex, List<Vector2> verts, List<UInt16> inds)
{
bool startOfLoop = pathIt.Closed && (pathIt.CurrentSegment == 0) && (pathIt.CurrentT == 0.0f);
if (startOfLoop && (joiningInfo[0] != null))
{
GenerateJoining(joiningInfo[0], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds);
}
else
{
var pathEnding = pathProps.Head;
// If pattern at the end will overlap with beginning, use a chopped ending to allow merging
if (pathIt.Closed && rangeIndex == 0 && patternIt.IsSolidAt(pathIt.CurrentT) && patternIt.IsSolidAt(totalLength))
pathEnding = PathEnding.Chop;
GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), true, pathIt.CurrentT, pathEnding, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds);
}
float startingLength = pathIt.LengthSoFar;
float unitsRemaining = Mathf.Min(tessellateOptions.StepDistance, distance);
bool endedEntirePath = false;
for (;;)
{
var result = pathIt.AdvanceBy(unitsRemaining, out unitsRemaining);
if (result == PathDistanceForwardIterator.Result.Ended)
{
endedEntirePath = true;
break;
}
else if (result == PathDistanceForwardIterator.Result.NewSegment)
{
if (joiningInfo[1] != null)
GenerateJoining(joiningInfo[1], pathProps.Corners, pathProps.Stroke.HalfThickness, pathProps.Stroke.TippedCornerLimit, tessellateOptions, verts, inds);
else AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, null, pathIt.SegmentLengthSoFar, verts, inds);
HandleNewSegmentJoining(pathIt, patternIt, joiningInfo, pathProps.Stroke.HalfThickness, segmentLengths);
}
if ((unitsRemaining <= Epsilon) &&
!TryGetMoreRemainingUnits(ref unitsRemaining, pathIt, startingLength, distance, tessellateOptions.StepDistance))
{
break;
}
if (result == PathDistanceForwardIterator.Result.Stepped)
AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds);
}
// Ending
if (endedEntirePath && pathIt.Closed)
{
// No joining needed, the start and end of the path should just connect
inds.Add(0);
inds.Add(1);
inds.Add((UInt16)(verts.Count - 2));
inds.Add((UInt16)(verts.Count - 1));
inds.Add((UInt16)(verts.Count - 2));
inds.Add(1);
}
else
{
AddSegment(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), pathIt.CurrentT, pathProps.Stroke.HalfThickness, joiningInfo, pathIt.SegmentLengthSoFar, verts, inds);
GenerateTip(VectorUtils.PathSegmentAtIndex(pathIt.Segments, pathIt.CurrentSegment), false, pathIt.CurrentT, pathProps.Tail, pathProps.Stroke.HalfThickness, tessellateOptions, verts, inds);
}
}
static void AddSegment(BezierSegment segment, float toT, float halfThickness, JoiningInfo[] joinInfo, float segmentLengthSoFar, List<Vector2> verts, List<UInt16> inds)
{
Vector2 tanTo, normTo;
Vector2 posTo = VectorUtils.EvalFull(segment, toT, out tanTo, out normTo);
Vector2 posThickness = posTo + normTo * halfThickness;
Vector2 negThickness = posTo + normTo * -halfThickness;
if (joinInfo != null)
{
if ((joinInfo[0] != null) && (segmentLengthSoFar < joinInfo[0].InnerCornerDistFromStart))
{
if (joinInfo[0].RoundPosThickness)
negThickness = joinInfo[0].InnerCornerVertex;
else posThickness = joinInfo[0].InnerCornerVertex;
}
if ((joinInfo[1] != null) && (segmentLengthSoFar > joinInfo[1].InnerCornerDistToEnd))
{
if (joinInfo[1].RoundPosThickness)
negThickness = joinInfo[1].InnerCornerVertex;
else posThickness = joinInfo[1].InnerCornerVertex;
}
}
System.Diagnostics.Debug.Assert(verts.Count >= 2);
int indexStart = verts.Count - 2;
verts.Add(posThickness);
verts.Add(negThickness);
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + 1));
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 3));
}
class JoiningInfo
{
public Vector2 JoinPos;
public Vector2 TanAtEnd, TanAtStart;
public Vector2 NormAtEnd, NormAtStart;
public Vector2 PosThicknessStart, NegThicknessStart;
public Vector2 PosThicknessEnd, NegThicknessEnd;
public Vector2 PosThicknessClosingPoint, NegThicknessClosingPoint;
public bool RoundPosThickness;
public bool SimpleJoin;
public Vector2 InnerCornerVertex;
public float InnerCornerDistToEnd, InnerCornerDistFromStart;
}
static JoiningInfo ForeseeJoining(BezierSegment end, BezierSegment start, float halfThickness, float endSegmentLength)
{
JoiningInfo joinInfo = new JoiningInfo();
// The joining generates the vertices at both ends as well as the joining itself
joinInfo.JoinPos = end.P3;
joinInfo.TanAtEnd = VectorUtils.EvalTangent(end, 1.0f);
joinInfo.NormAtEnd = Vector2.Perpendicular(joinInfo.TanAtEnd);
joinInfo.TanAtStart = VectorUtils.EvalTangent(start, 0.0f);
joinInfo.NormAtStart = Vector2.Perpendicular(joinInfo.TanAtStart);
// If the tangents are continuous at the join location, we don't have
// to generate a corner, we do a "simple" join by just connecting the vertices
// from the two segments directly
float cosAngleBetweenTans = Vector2.Dot(joinInfo.TanAtEnd, joinInfo.TanAtStart);
joinInfo.SimpleJoin = Mathf.Approximately(Mathf.Abs(cosAngleBetweenTans), 1.0f);
if (joinInfo.SimpleJoin)
return null;
joinInfo.PosThicknessEnd = joinInfo.JoinPos + joinInfo.NormAtEnd * halfThickness;
joinInfo.NegThicknessEnd = joinInfo.JoinPos - joinInfo.NormAtEnd * halfThickness;
joinInfo.PosThicknessStart = joinInfo.JoinPos + joinInfo.NormAtStart * halfThickness;
joinInfo.NegThicknessStart = joinInfo.JoinPos - joinInfo.NormAtStart * halfThickness;
if (joinInfo.SimpleJoin)
{
joinInfo.PosThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.PosThicknessEnd, joinInfo.PosThicknessStart, 0.5f);
joinInfo.NegThicknessClosingPoint = Vector2.LerpUnclamped(joinInfo.NegThicknessEnd, joinInfo.NegThicknessStart, 0.5f);
}
else
{
joinInfo.PosThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.PosThicknessEnd, joinInfo.PosThicknessEnd + joinInfo.TanAtEnd, joinInfo.PosThicknessStart, joinInfo.PosThicknessStart + joinInfo.TanAtStart);
joinInfo.NegThicknessClosingPoint = VectorUtils.IntersectLines(joinInfo.NegThicknessEnd, joinInfo.NegThicknessEnd + joinInfo.TanAtEnd, joinInfo.NegThicknessStart, joinInfo.NegThicknessStart + joinInfo.TanAtStart);
if (float.IsInfinity(joinInfo.PosThicknessClosingPoint.x) || float.IsInfinity(joinInfo.PosThicknessClosingPoint.y))
joinInfo.PosThicknessClosingPoint = joinInfo.JoinPos;
if (float.IsInfinity(joinInfo.NegThicknessClosingPoint.x) || float.IsInfinity(joinInfo.NegThicknessClosingPoint.y))
joinInfo.NegThicknessClosingPoint = joinInfo.JoinPos;
}
// Should we round the positive thickness side or the negative thickness side?
joinInfo.RoundPosThickness = PointOnTheLeftOfLine(Vector2.zero, joinInfo.TanAtEnd, joinInfo.TanAtStart);
// Inner corner vertex should be calculated by intersection of the inner segments
Vector2[] startTrail = null, endTrail = null;
Vector2 intersectionOnStart = Vector2.zero, intersectionOnEnd = Vector2.zero;
if (!joinInfo.SimpleJoin)
{
BezierSegment endFlipped = VectorUtils.FlipSegment(end);
Vector2 thicknessClosingPoint = joinInfo.RoundPosThickness ? joinInfo.PosThicknessClosingPoint : joinInfo.NegThicknessClosingPoint;
Vector2 meetingPoint = end.P3;
Vector2 thicknessDiagonalEnd = meetingPoint + (thicknessClosingPoint - meetingPoint) * 10.0f;
startTrail = LineBezierThicknessIntersect(
start, joinInfo.RoundPosThickness ? -halfThickness : halfThickness, meetingPoint, thicknessDiagonalEnd,
out joinInfo.InnerCornerDistFromStart, out intersectionOnStart);
endTrail = LineBezierThicknessIntersect(
endFlipped, joinInfo.RoundPosThickness ? halfThickness : -halfThickness, meetingPoint, thicknessDiagonalEnd,
out joinInfo.InnerCornerDistToEnd, out intersectionOnEnd);
}
bool intersectionFound = false;
if ((startTrail != null) && (endTrail != null))
{
var intersect = VectorUtils.IntersectLines(startTrail[0], startTrail[1], endTrail[0], endTrail[1]);
var isOnStartTrail = PointOnLineIsWithinSegment(startTrail[0], startTrail[1], intersect);
var isOnEndTrail = PointOnLineIsWithinSegment(endTrail[0], endTrail[1], intersect);
if (!float.IsInfinity(intersect.x) && isOnStartTrail && isOnEndTrail)
{
var vStart = intersectionOnStart - intersect;
var vEnd = intersectionOnEnd - intersect;
joinInfo.InnerCornerDistFromStart += (vStart == Vector2.zero) ? 0.0f : vStart.magnitude;
joinInfo.InnerCornerDistToEnd += (vEnd == Vector2.zero) ? 0.0f : vEnd.magnitude;
joinInfo.InnerCornerDistToEnd = endSegmentLength - joinInfo.InnerCornerDistToEnd;
joinInfo.InnerCornerVertex = intersect; // Found it!
intersectionFound = true;
}
}
if (!intersectionFound)
{
joinInfo.InnerCornerVertex = joinInfo.JoinPos + ((joinInfo.TanAtStart - joinInfo.TanAtEnd) / 2.0f).normalized * halfThickness;
joinInfo.InnerCornerDistFromStart = 0;
joinInfo.InnerCornerDistToEnd = endSegmentLength;
}
return joinInfo;
}
static Vector2[] LineBezierThicknessIntersect(BezierSegment seg, float thickness, Vector2 lineFrom, Vector2 lineTo, out float distanceToIntersection, out Vector2 intersection)
{
Vector2 tan = VectorUtils.EvalTangent(seg, 0.0f);
Vector2 nrm = Vector2.Perpendicular(tan);
Vector2 lastPoint = seg.P0 + nrm * thickness;
distanceToIntersection = 0.0f;
intersection = new Vector2(float.PositiveInfinity, float.PositiveInfinity);
float stepT = 0.01f;
float t = 0;
while (t < 1.0f)
{
t += stepT;
var point = VectorUtils.EvalFull(seg, t, out tan, out nrm) + nrm * thickness;
intersection = VectorUtils.IntersectLines(lineFrom, lineTo, lastPoint, point);
if (PointOnLineIsWithinSegment(lastPoint, point, intersection))
{
distanceToIntersection += (lastPoint - intersection).magnitude;
return new Vector2[] { lastPoint, point };
}
distanceToIntersection += (lastPoint - point).magnitude;
lastPoint = point;
}
return null;
}
static bool PointOnLineIsWithinSegment(Vector2 lineFrom, Vector2 lineTo, Vector2 point)
{
// Point is assumed to be already on the line, but we would like to know if it is within the segment specified
var v = (lineTo - lineFrom).normalized;
if (Vector2.Dot(point - lineFrom, v) < -Epsilon)
return false;
if (Vector2.Dot(point - lineTo, v) > Epsilon)
return false;
return true;
}
static void GenerateJoining(JoiningInfo joinInfo, PathCorner corner, float halfThickness, float tippedCornerLimit, TessellationOptions tessellateOptions, List<Vector2> verts, List<UInt16> inds)
{
// The joining generates the vertices at both ends as well as the joining itself
if (verts.Count == 0)
{
// Starting a path with a joining (meaning a loop)
verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessEnd : joinInfo.InnerCornerVertex);
verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessEnd);
}
System.Diagnostics.Debug.Assert(verts.Count >= 2);
int indexStart = verts.Count - 2; // Using the last two vertices
// Convert a tipped corner to a beveled one if tippedCornerLimit ratio is reached
if (corner == PathCorner.Tipped && tippedCornerLimit >= 1.0f)
{
var theta = Vector2.Angle(-joinInfo.TanAtEnd, joinInfo.TanAtStart) * Mathf.Deg2Rad;
var ratio = 1.0f / Mathf.Sin(theta / 2.0f);
if (ratio > tippedCornerLimit)
corner = PathCorner.Beveled;
}
if (joinInfo.SimpleJoin)
{
// TODO
}
else if (corner == PathCorner.Tipped)
{
verts.Add(joinInfo.PosThicknessClosingPoint);
verts.Add(joinInfo.NegThicknessClosingPoint);
verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessStart : joinInfo.InnerCornerVertex);
verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessStart);
// Ending to tip
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + 1));
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 3));
// Tip to starting
inds.Add((UInt16)(indexStart + 4));
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 4));
inds.Add((UInt16)(indexStart + 5));
inds.Add((UInt16)(indexStart + 3));
return;
}
else if (corner == PathCorner.Beveled)
{
verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessEnd : joinInfo.InnerCornerVertex); // 2
verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessEnd); // 3
verts.Add(joinInfo.RoundPosThickness ? joinInfo.PosThicknessStart : joinInfo.InnerCornerVertex); // 4
verts.Add(joinInfo.RoundPosThickness ? joinInfo.InnerCornerVertex : joinInfo.NegThicknessStart); // 5
// Ending to tip
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 1));
inds.Add((UInt16)(indexStart + 1));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 3));
// Bevel
if (joinInfo.RoundPosThickness)
{
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 4));
inds.Add((UInt16)(indexStart + 3));
}
else
{
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 5));
}
return;
}
if (corner == PathCorner.Round)
{
float sweepAngle = Mathf.Acos(Vector2.Dot(joinInfo.NormAtEnd, joinInfo.NormAtStart));
bool flipArc = false;
if (!PointOnTheLeftOfLine(Vector2.zero, joinInfo.NormAtEnd, joinInfo.NormAtStart))
{
sweepAngle = -sweepAngle;
flipArc = true;
}
UInt16 innerCornerVertexIndex = (UInt16)verts.Count;
verts.Add(joinInfo.InnerCornerVertex);
int arcSegments = CalculateArcSteps(halfThickness, 0, sweepAngle, tessellateOptions);
for (int i = 0; i <= arcSegments; i++)
{
float angle = sweepAngle * (i / (float)arcSegments);
Vector2 nrm = Matrix2D.RotateLH(angle) * joinInfo.NormAtEnd;
if (flipArc) nrm = -nrm;
verts.Add(nrm * halfThickness + joinInfo.JoinPos);
if (i == 0)
{
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + (joinInfo.RoundPosThickness ? 2 : 1)));
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + (joinInfo.RoundPosThickness ? 1 : 3)));
}
else
{
if (joinInfo.RoundPosThickness)
{
inds.Add((UInt16)(indexStart + i + (flipArc ? 3 : 2)));
inds.Add((UInt16)(indexStart + i + (flipArc ? 2 : 3)));
inds.Add(innerCornerVertexIndex);
}
else
{
inds.Add((UInt16)(indexStart + i + (flipArc ? 3 : 2)));
inds.Add((UInt16)(indexStart + i + (flipArc ? 2 : 3)));
inds.Add(innerCornerVertexIndex);
}
}
}
// Manually add the last segment, maintain the expected vertex positioning
int endingVerticesIndex = verts.Count;
if (joinInfo.RoundPosThickness)
{
verts.Add(joinInfo.PosThicknessStart);
verts.Add(joinInfo.InnerCornerVertex);
}
else
{
verts.Add(joinInfo.InnerCornerVertex);
verts.Add(joinInfo.NegThicknessStart);
}
inds.Add((UInt16)(endingVerticesIndex - 1));
inds.Add((UInt16)(endingVerticesIndex + 0));
inds.Add(innerCornerVertexIndex);
}
}
static void GenerateTip(BezierSegment segment, bool atStart, float t, PathEnding ending, float halfThickness, TessellationOptions tessellateOptions, List<Vector2> verts, List<UInt16> inds)
{
// The tip includes the vertices at the end itself
Vector2 tan, nrm;
var pos = VectorUtils.EvalFull(segment, t, out tan, out nrm);
int indexStart = verts.Count;
switch (ending)
{
case PathEnding.Chop:
if (atStart)
{
verts.Add(pos + nrm * halfThickness);
verts.Add(pos - nrm * halfThickness);
}
else
{
// Not much, path segments are always expected to be generated perpendicular to the path
// at the segment point location, so we don't have to do anything for the ending
}
break;
case PathEnding.Square:
if (atStart)
{
verts.Add(pos + nrm * halfThickness - tan * halfThickness);
verts.Add(pos - nrm * halfThickness - tan * halfThickness);
verts.Add(pos + nrm * halfThickness);
verts.Add(pos - nrm * halfThickness);
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 3));
inds.Add((UInt16)(indexStart + 1));
inds.Add((UInt16)(indexStart + 0));
inds.Add((UInt16)(indexStart + 2));
inds.Add((UInt16)(indexStart + 3));
}
else
{
// Relying on the last two vertices, and just adding two of our own here
verts.Add(pos + nrm * halfThickness + tan * halfThickness);
verts.Add(pos - nrm * halfThickness + tan * halfThickness);
inds.Add((UInt16)(indexStart + 0 - 2));
inds.Add((UInt16)(indexStart + 3 - 2));
inds.Add((UInt16)(indexStart + 1 - 2));
inds.Add((UInt16)(indexStart + 0 - 2));
inds.Add((UInt16)(indexStart + 2 - 2));
inds.Add((UInt16)(indexStart + 3 - 2));
}
break;
case PathEnding.Round:
float arcSign = atStart ? -1 : 1;
int arcSegments = CalculateArcSteps(halfThickness, 0, Mathf.PI, tessellateOptions);
for (int i = 1; i < arcSegments; i++)
{
float angle = Mathf.PI * (i / (float)arcSegments);
verts.Add(pos + Matrix2D.RotateLH(angle) * nrm * halfThickness * arcSign);
}
if (atStart)
{
// Note how we maintain the last two vertices being setup for connection by the rest of the path vertices
int indexTipStart = verts.Count;
verts.Add(pos + nrm * halfThickness);
verts.Add(pos - nrm * halfThickness);
for (int i = 1; i < arcSegments; i++)
{
inds.Add((UInt16)(indexTipStart + 1));
inds.Add((UInt16)(indexStart + i - 1));
inds.Add((UInt16)(indexStart + i));
}
}
else
{
inds.Add((UInt16)(indexStart - 1));
inds.Add((UInt16)(indexStart - 2));
inds.Add((UInt16)(indexStart + 0));
for (int i = 1; i < arcSegments - 1; i++)
{
inds.Add((UInt16)(indexStart - 1));
inds.Add((UInt16)(indexStart + i - 1));
inds.Add((UInt16)(indexStart + i));
}
}
break;
default:
System.Diagnostics.Debug.Assert(false); // Joining has its own function
break;
}
}
static int CalculateArcSteps(float radius, float fromAngle, float toAngle, TessellationOptions tessellateOptions)
{
float stepDivisor = float.MaxValue;
if (tessellateOptions.StepDistance != float.MaxValue)
stepDivisor = tessellateOptions.StepDistance / radius;
if (tessellateOptions.MaxCordDeviation != float.MaxValue)
{
float y = radius - tessellateOptions.MaxCordDeviation;
float cordHalfLength = Mathf.Sqrt(radius * radius - y * y);
float div = Mathf.Min(stepDivisor, Mathf.Asin(cordHalfLength / radius));
if (div > VectorUtils.Epsilon)
stepDivisor = div;
}
if (tessellateOptions.MaxTanAngleDeviation < Mathf.PI * 0.5f)
stepDivisor = Mathf.Min(stepDivisor, tessellateOptions.MaxTanAngleDeviation * 2.0f);
float stepsInFullCircle = (Mathf.PI * 2.0f) / stepDivisor;
float arcPercentage = Mathf.Abs(fromAngle - toAngle) / (Mathf.PI * 2.0f);
return (int)Mathf.Max(stepsInFullCircle * arcPercentage + 0.5f, 3); // Never less than 3 segments
}
/// <summary>Tessellates a rectangle.</summary>
/// <param name="rect">Rectangle to tessellate</param>
/// <param name="vertices">The output vertices</param>
/// <param name="indices">The output triangles</param>
public static void TessellateRect(Rect rect, out Vector2[] vertices, out UInt16[] indices)
{
vertices = new Vector2[] {
new Vector2(rect.xMin, rect.yMin),
new Vector2(rect.xMax, rect.yMin),
new Vector2(rect.xMax, rect.yMax),
new Vector2(rect.xMin, rect.yMax)
};
indices = new UInt16[] {
1, 0, 2, 2, 0, 3
};
}
/// <summary>Tessellates a rectangle border.</summary>
/// <param name="rect">Rectangle to tessellate</param>
/// <param name="halfThickness">The half-thickness of the border</param>
/// <param name="vertices">The output vertices</param>
/// <param name="indices">The output triangles</param>
public static void TessellateRectBorder(Rect rect, float halfThickness, out Vector2[] vertices, out UInt16[] indices)
{
var verts = new List<Vector2>(16);
var inds = new List<UInt16>(24);
// Left edge
var p0 = new Vector2(rect.x, rect.y + rect.height);
var p1 = new Vector2(rect.x, rect.y);
var q0 = p0 + new Vector2(-halfThickness, halfThickness);
var q1 = p1 + new Vector2(-halfThickness, -halfThickness);
var q2 = p1 + new Vector2(halfThickness, halfThickness);
var q3 = p0 + new Vector2(halfThickness, -halfThickness);
verts.Add(q0); verts.Add(q1); verts.Add(q2); verts.Add(q3);
inds.Add(0); inds.Add(3); inds.Add(2); inds.Add(2); inds.Add(1); inds.Add(0);
// Top edge
p0 = new Vector2(rect.x, rect.y);
p1 = new Vector2(rect.x + rect.width, rect.y);
q0 = p0 + new Vector2(-halfThickness, -halfThickness);
q1 = p1 + new Vector2(halfThickness, -halfThickness);
q2 = p1 + new Vector2(-halfThickness, halfThickness);
q3 = p0 + new Vector2(halfThickness, halfThickness);
verts.Add(q0); verts.Add(q1); verts.Add(q2); verts.Add(q3);
inds.Add(4); inds.Add(7); inds.Add(6); inds.Add(6); inds.Add(5); inds.Add(4);
// Right edge
p0 = new Vector2(rect.x + rect.width, rect.y);
p1 = new Vector2(rect.x + rect.width, rect.y + rect.height);
q0 = p0 + new Vector2(halfThickness, -halfThickness);
q1 = p1 + new Vector2(halfThickness, halfThickness);
q2 = p1 + new Vector2(-halfThickness, -halfThickness);
q3 = p0 + new Vector2(-halfThickness, halfThickness);
verts.Add(q0); verts.Add(q1); verts.Add(q2); verts.Add(q3);
inds.Add(8); inds.Add(11); inds.Add(10); inds.Add(10); inds.Add(9); inds.Add(8);
// Bottom edge
p0 = new Vector2(rect.x + rect.width, rect.y + rect.height);
p1 = new Vector2(rect.x, rect.y + rect.height);
q0 = p0 + new Vector2(halfThickness, halfThickness);
q1 = p1 + new Vector2(-halfThickness, halfThickness);
q2 = p1 + new Vector2(halfThickness, -halfThickness);
q3 = p0 + new Vector2(-halfThickness, -halfThickness);
verts.Add(q0); verts.Add(q1); verts.Add(q2); verts.Add(q3);
inds.Add(12); inds.Add(15); inds.Add(14); inds.Add(14); inds.Add(13); inds.Add(12);
vertices = verts.ToArray();
indices = inds.ToArray();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7946c79a48c418944bb4ac48df97c4be
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bc6ce72ebfec4b24faaee0fa9df6a984
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
{
"name":"ToolBuddy.Dependencies.VectorGraphics"
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 1b380eea165f7b4478fcf2f7ee32b4d7
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: