diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3f1580cb..39d4dafc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## [9.0.0] - 2022-08-03
+### Added
+- Added bone weight index validation in SpriteSkin's validate method, to ensure valid data before continuing with deformation.
+
+### Fixed
+- Fixed a case where moving vertices forcefully in the Skinning editor could cause a quad reset of the mesh. (case DANB-7)
+- Fixed a case where multi selecting Sprite Skins would cause a null reference exception to be thrown. (case DANB-126)
+
+### Changed
+- Refactored internal triangulation and tessellation APIs.
+
## [9.0.0-pre.3] - 2022-05-31
### Changed
- Update dependency package version.
@@ -14,6 +25,7 @@
### Changed
- Added ability to create Sprite Library Asset variant from the create menu.
+- Added dialog box to the Skinning Editor when entering Play Mode to allow saving unsaved changes.
## [9.0.0-pre.1] - 2022-03-21
### Added
diff --git a/Documentation~/Animating-actor.md b/Documentation~/Animating-actor.md
index 1293a719..7239bc2f 100644
--- a/Documentation~/Animating-actor.md
+++ b/Documentation~/Animating-actor.md
@@ -1,4 +1,4 @@
-#Animating an actor
+# Animating an actor
After [importing](PreparingArtwork.md) and [rigging](CharacterRig.md) an actor, you can begin animating by simply dragging the rigged actor into the Scene view. By repositioning the different bones of the actor on the Animation timeline with [Unity's animation workflow and tools](https://docs.unity3d.com/Manual/AnimationSection.html). The mesh of the actor [deforms](SpriteSkin.md) with the positioning of the rigged bones, creating smooth animation transitions.
Aside from this method, there are other ways that you can animate with the 2D Animation package. The following are a few examples based on the Sample scenes available for you to import to use with the package.
@@ -7,4 +7,4 @@ Aside from this method, there are other ways that you can animate with the 2D An
The 2D Animation package allows you to use the [Sprite Swap](SpriteSwapIntro.md) feature to swap to different Sprites at runtime, from [swapping only a single part](CharacterParts.md) of an actor to [swapping the entire Sprite Library Asset](SLASwap.md) it refers to.
### Other Sample projects
-[Sample projects ](Examples.md) are distributed with the 2D Animation package and available for import. These projects include examples of the different ways you can animate with the package features, such as the [Flipbook Animation Swap](ex-sprite-swap.md#flipbook-animation-swap) and [Animated Swap](ex-sprite-swap.md#animated-swap) and so on. Refer to the respective Sample documentation pages for more information.
+[Sample projects ](Examples.md) are distributed with the 2D Animation package and available for import. These projects include examples of the different ways you can animate with the package features such as an [Animated Swap](ex-sprite-swap.md#animated-swap). Refer to the respective Sample documentation pages for more information.
diff --git a/Documentation~/AssetUpgrader.md b/Documentation~/AssetUpgrader.md
index f3a64f40..382566f4 100644
--- a/Documentation~/AssetUpgrader.md
+++ b/Documentation~/AssetUpgrader.md
@@ -3,7 +3,7 @@ The 2D Animation package and its assets are often updated with major and minor t
The 2D Animation Asset Upgrader tool eases the transition and upgrade of older assets to newer ones. This tool has the following features:
-- Upgrades [Sprite Library Asset](SLAsset.md) files ending in `.asset` to Sprite Library Source Asset files ending in `.spriteLib`.
+- Upgrades [Sprite Library Asset](SL-Asset.md) files ending in `.asset` to Sprite Library Source Asset files ending in `.spriteLib`.
- Moves Sprite Library Assets baked into `.psb` files created in Unity 2019 and Unity 2020 out into their own separate Sprite Library Source Asset files ending in `.spriteLib`.
- Upgrades [Animation Clips](https://docs.unity3d.com/Manual/AnimationClips.html) that animate Sprites based on the [Sprite Resolver component](https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html%23sprite-resolver-component)'s [Category and Label](https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SLAsset.html%23category) hash in Unity 2019 and Unity 2020, to Sprite Resolver's new **Sprite Hash** property from Unity 2022 onwards.
- Upgrades Animation Clips animating the Sprite Resolver component's **Sprite Key** property in Unity 2021, to Sprite Resolver's new Sprite Hash property from Unity 2022 onwards.
diff --git a/Documentation~/CharacterParts.md b/Documentation~/CharacterParts.md
index 54abf26f..91fbeb74 100644
--- a/Documentation~/CharacterParts.md
+++ b/Documentation~/CharacterParts.md
@@ -5,7 +5,7 @@ In the following example, there are two Sprites that are variations of the actor
![](images/bothscarves.PNG)
__Left:__ The original `green scarf` Sprite. __Right:__ An alternate `blue scarf` Sprite.
-1. Place the Sprites for both scarves into the same [Sprite Library Asset](SLAsset.md), and add them both to the same **Category** (named `Scarf`).
+1. Place the Sprites for both scarves into the same [Sprite Library Asset](SL-Asset.md), and add them both to the same **Category** (named `Scarf`).
2. Give each of the Sprites a unique __Label__ name (in this case `green scarf` and `blue scarf` respectively).
This and the previous step can be automated by dragging and dropping sprites into the Categories tab empty space.
@@ -13,7 +13,7 @@ This and the previous step can be automated by dragging and dropping sprites int
3. In the Scene, select the [Instantiated Prefab](https://docs.unity3d.com/Manual/InstantiatingPrefabs.html) and then select the `Scarf` GameObject in the Hierarchy window.
-4. Go to the [Sprite Resolver component](SLAsset.md#sprite-resolver-component) of the `Scarf` GameObject. The Sprite Resolver‘s visual selector displays the two Sprites available in the `Scarf` Category.
![](images/2d-anim-change-parts-select-green.png)
+4. Go to the [Sprite Resolver component](SL-Resolver.md) of the `Scarf` GameObject. The Sprite Resolver‘s visual selector displays the two Sprites available in the `Scarf` Category.
![](images/2d-anim-change-parts-select-green.png)
5. Select the `blue scarf` to switch the Sprite rendered by the `Scarf` GameObject to it instead.
![](images/2d-anim-change-parts-select-blue.png)
The Sprite Resolver's Label is set to `blue scarf`.
diff --git a/Documentation~/Examples.md b/Documentation~/Examples.md
index 9fedb661..fee507d1 100644
--- a/Documentation~/Examples.md
+++ b/Documentation~/Examples.md
@@ -1,19 +1,18 @@
# Importing Samples
-Sample scenes are available for import from the Package Manger under **Samples**, which demonstrate the different ways you can use the features in the 2D Animation package to achieve a variety of effects and outcomes.
+Sample scenes are available for import from the Package Manager under **Samples**, which demonstrate the different ways you can use the features in the 2D Animation package to achieve a variety of effects and outcomes.
![](images/sample-import-button.png)
Select **Import** to download and install the Sample projects and Assets.
Each Sample project contains specific examples with ready-made Assets, demonstrating how to use the 2D Animation package's features and the results and outcomes you can achieve with them.
-When the import is complete, Unity installs the Sample projects to `Assets/Samples/2D Animation/[X.Y.Z]/Samples`; where `[X.Y.Z]` is the version of the currently installed 2D Animation package.
+When the import is complete, Unity installs the Sample projects to `Assets/Samples/2D Animation/[X.Y.Z]/Samples`; where `[X.Y.Z]` is the version of the installed 2D Animation package.
The following is the list of Sample projects and their respective documentation. Note that some of these Samples require and refer to the [PSD Importer](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest/) package:
- [Simple](ex-simple.md) - a single Sprite rig with simple bone hierarchy and rigging.
- [Single Skinned Sprite](ex-single-skinned-sprite.md) - a more advance single Sprite actor.
- [Character](ex-psd-importer.md) - Imported with the PSD Importer
-- [Sprite Swap](ex-sprite-swap.md) - - Contains examples of the many different ways that Sprite Swap can be used.
- - [Flipbook Animation Swap](ex-sprite-swap.md#flipbook-animation-swap)
+- [Sprite Swap](ex-sprite-swap.md) - - Contains examples of the different ways to use Sprite Swap.
- [Animated Swap](ex-sprite-swap.md#animated-swap)
- [Part Swap](ex-sprite-swap.md#part-swap)
- [Full Skin Swap](ex-sprite-swap.md#full-skin-swap)
diff --git a/Documentation~/SL-Asset.md b/Documentation~/SL-Asset.md
new file mode 100644
index 00000000..4e4d2b6b
--- /dev/null
+++ b/Documentation~/SL-Asset.md
@@ -0,0 +1,44 @@
+# Sprite Library Asset in Unity
+
+The **Sprite Library Asset** is an Unity asset that contains the Sprites that you want to use for [Sprite Swapping](SpriteSwapIntro.md). This page explains what are the [Sprite Library Asset properties](#sprite-library-asset-properties) and how to [create a Sprite Library Asset](#create-a-sprite-library-asset) or a [Sprite Library Asset variant](#convert-a-sprite-library-asset-into-a-variant).
+
+A Sprite Library Asset groups the Sprites it contains into [Categories](SL-Editor.md#categories), and you can give these Sprites unique names called [Labels](SL-Editor.md#labels) to differentiate them. You can edit the Sprite Library Asset's content in the [Sprite Library Editor window](SL-Editor.md) (refer to its documentation for more details).
+
+In the [Sprite Swap workflow](SpriteSwapSetup.md), after creating a Sprite Library Asset or several assets, you can select the Sprite Library Asset you want to use with the [Sprite Library](SL-component.md) component, and the [Sprite Resolver](SL-Resolver.md) component will pull information from the asset selected.
+
+## Create a Sprite Library Asset
+
+To create a Sprite Library Asset, go to **Assets** > **Create** > **2D** > **Sprite Library Asset**.
+
+![](images/2D-animation-SLAsset-dropdown.png)
+
+## Sprite Library Asset properties
+Select the Sprite Library Asset and go to its Inspector window to view the following properties.
+
+![](images/2D-animation-SLAsset-properties.png)
+
+Property |Description
+--|--
+**Open in Sprite Library Editor** | Select this to open the [Sprite Library Editor window](SL-Editor.md) to edit the content of this asset.
+**Main Library** | Leave this property empty to have this Sprite Library Asset refer to its own Categories and Labels. Assign a different Sprite Library Asset to have it become the [Main Library](SL-Editor-UI.md#main-library) of the selected Sprite Library Asset, which will now refer to the second asset's Categories and Labels instead. Doing so also [converts](#convert-a-sprite-library-asset-into-a-variant) the selected Sprite Library Asset into a Variant asset of the Sprite Library Asset set as the **Main Library**.
+**Revert** | Select this to reset property changes back to the last saved state. Selecting this removes all unsaved changes.
+**Apply** | Select this to save the current property settings.
+
+## Create a Sprite Library Asset Variant
+
+A Sprite Library Asset Variant inherits **Categories** and **Labels** from a selected Sprite Library Asset, instead of referring to its own. There are two ways to create a Variant.
+
+### Create through the menu
+
+After creating a Sprite Library Asset, select it in the Project window, then go to **Assets** > **Create** > **2D** > **Sprite Library Asset Variant** to create a Variant asset that references it.
+
+![](images/2D-animation-SLAssetVariant-dropdown.png)
+
+### Convert a Sprite Library Asset into a Variant
+
+You can convert an existing Sprite Library Asset into a Variant of another by setting another Sprite Library Asset as its [Main Library](SL-Main-Library.md).
+
+## Additional resources
+- [Sprite Library Editor](SL-Editor.md)
+- [Setting up for Sprite Swap](SpriteSwapSetup.md)
+- [Overrides to the Main Library](SL-Main-Library.md)
diff --git a/Documentation~/SL-Drag.md b/Documentation~/SL-Drag.md
new file mode 100644
index 00000000..5aba62a2
--- /dev/null
+++ b/Documentation~/SL-Drag.md
@@ -0,0 +1,77 @@
+# Drag sprites to create or edit Categories and Labels
+
+This page shows the different ways you can create or edit Categories and Labels in a Sprite Library Asset by dragging sprites directly into the Sprite Library Editor window.
+
+You can automatically create new Categories and Labels by dragging sprites or [PSD Importer supported file types](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest) directly into the [Sprite Library Editor](SL-Editor.md) window.
+
+## Prerequisites
+
+- [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest) is required for the Sprite Library Editor to recognize imported .psb files.
+
+## Create a new Category
+
+1. In the open Sprite Library Editor window, drag a sprite directly onto the Categories column to create a new Category. You can't create Labels without selecting an existing Category first.
+
+ ![](images/sl-editor-drag-sprite-category.png)
+
+## Create a new Label for each sprite
+
+1. Select a Category, then drag a or a selection of sprites into an empty space in the Labels column.
+
+ ![](images/2D-animation-SLAsset-drag-n-drop-04.png)
_Dragging the `head` sprite into the Labels column creates a new Label named `head` in the selected Category._
+
+2. Unity creates a new Label for each sprite in the selection and gives it the same name as the sprite it references. **Note:** If an existing Label with the same name already exists at the destination, then the editor appends `_X` to the new Label's name, where `X` is the next number in sequence, starting from zero.
+
+ ![](images/2D-animation-SLAsset-drag-n-drop-04-finished.png)
_Dragging in a selection of sprites all named `head` results in additional Labels created with the `_X` suffix for each sprite._
+
+## Replace a Label's sprite reference
+
+1. Drag a sprite onto an existing Label.
+ ![](images/2D-animation-SLAsset-drag-n-drop-05.png)
+
+2. The editor replaces the sprite reference to the new sprite. The Label's name remains unchanged.
+ ![](images/2D-animation-SLAsset-drag-n-drop-05-finished.png)
+
+## Create a single Category with multiple Labels
+
+1. Select multiple sprites from the Project window.
+
+ ![](images/sl-editor-drag-select-sprites.png)
+2. Drag the selected sprites into the Categories column to create a new Category. The Category is automatically named after the first sprite in the selection.
+
+ ![](images/sl-editor-drag-select-sprites-drop.png)
+
+**Note:** If an existing Category with the same name already exists at the destination, then the editor appends `_X` to the new Category's name, where `X` is the next number in sequence, starting from zero.
+
+## Create Categories for each Layer and Layer Group
+
+After you [prepare the .psb](PreparingArtwork.md) of your character, import it into Unity with the PSD Importer package. **Note:** The following [requires](#prerequisites) the PSD Importer package to be installed.
+
+1. Enable **Use Layer Group** in the imported .psb's properties.
+
+ ![](images/sl-editor-drag-wolf-layer-groups.png)
+
+2. Drag the imported .psb file into the Sprite Library Editor's Categories column.
+
+3. The editor creates a Category for each Layer and Layer Group, and creates Labels for each sprite. Sprites which belonged to the same Layer Group are automatically grouped into the same Category.
+
+ ![](images/sl-editor-drag-wolf-labels.png)
+
+
+## Replace each Labels' sprite references
+
+The following steps how to replace each Label's sprite reference with sprites from a different imported .psb. **Note:** This method only works if the imported .psb has Layers and Layer Groups with the same exact names as the original .psb used to [create the Categories and Labels](#create-categories-for-each-layer-and-layer-group).
+
+This method is useful when you have multiple characters with the same Layers and Layer Groups and want to replace their respective sprites without creating a new Sprite Library Asset.
+
+1. Enable **Use Layer Groups** in the replacement .psb file's properties.
+
+ ![](images/sl-editor-drag-knight-drag.png)
+
+2. Drag the replacement .psb onto an empty space in the Categories column and release. All sprite references of the same name and in the same Categories are automatically replaced with their respective counterparts from the replacement .psb.
+
+ ![](images/sl-editor-drag-knight-drop.png)
+
+## Additional resources
+- [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest)
+- [Preparing and importing artwork](PreparingArtwork.md)
\ No newline at end of file
diff --git a/Documentation~/SL-Editor-UI.md b/Documentation~/SL-Editor-UI.md
new file mode 100644
index 00000000..7d2bb095
--- /dev/null
+++ b/Documentation~/SL-Editor-UI.md
@@ -0,0 +1,106 @@
+# Sprite Library Editor reference
+
+The **Sprite Library Editor** window displays the content of the selected [Sprite Library Asset](SL-Asset.md). Use the editor to view, create, or edit the [Categories](SL-Editor.md#categories) and the [Labels](SL-Editor.md#labels) in the selected Sprite Library Asset.
+
+![](images/sl-editor-blank.png)
+
+A: The name of the Sprite Library Asset opened in this editor.
+B: This displays this asset's [Main Library](SL-Main-Library.md) if it's a [Variant asset](SL-Asset.md#convert-a-sprite-library-asset-into-a-variant). Refer to [Variant asset-specific properties ](#variant-asset-specific-properties) for more information.
+C: The [saving options](#saving-options) for changes made in this editor.
+D: Enter a text string here to filter the Categories or Labels by name. Select the magnifying glass icon to the left of the filter to bring up its [context menu](#filter-context-menu).
+
+## Saving options
+
+![](images/sl-editor-save-options.png)
+
+Property | Description
+--|--
+**Revert** | Select this to discard all unsaved changes and to return to the last saved state of the asset.
+**Save** | Select this to keep all unsaved changes and include them in the saved state of the asset.
+**Auto Save** | Enable this option to have the editor automatically save when you make any changes to the asset.
+
+## Main Library
+
+![](images/sl-editor-main-library.png)
+
+Property | Description |
+-----|------|
+**Main Library**| Assign a Sprite Library Asset to this to set it as the opened Sprite Library Asset's Main Library, and it will inherit the Main Library's Categories and Labels. Doing so also converts the opened Sprite Library Asset into a [Sprite Library Asset Variant](SL-Asset.md#convert-a-sprite-library-asset-into-a-variant).
+
+## Filter context menu
+
+![](images/sl-editor-filter-context-menu.png)
+
+Property | Description
+--|--
+**Category and Label** | Search for the entered search string in both Category and Label names.
+**Category** | Search for the entered search string in only Category names.
+**Label** | Search for the entered search string in only Label names.
+
+## Categories and Labels columns
+
+![](images/sl-editor-category-contents.png)
+
+
+
+
+ Property |
+ Description |
+
+
+
+
+ Categories |
+Displays all Categories the opened Sprite Library Asset contains. Select Add (+) at the top left of this column to add a new empty Category to this asset. |
+
+
+ |
+Local foldout group |
+Groups all Categories created in this Sprite Library Asset. |
+
+
+ |
+Inherited foldout group |
+Groups all Categories inherited from this Sprite Library Asset Variant's Main Library. Note: This is only available if the opened Sprite Library Asset is a Variant asset. |
+
+
+Labels |
+Displays all Labels a selected Category contains, when you select one from the Categories column. Select Add (+) at the top left of this column to add a new empty Label to this asset. |
+
+
+ |
+(Sprite) object field |
+Displays the sprite that this Label refers to. This is blank by default. Select a sprite by opening the object picker, or drag a sprite directly onto this field. |
+
+
+
+
+### Variant asset-specific properties
+The following properties and UI features are visible only when you open a [Sprite Library Variant Asset](SL-Asset.md#convert-a-sprite-library-asset-into-a-variant) in the Sprite Library Editor.
+
+![](images/sl-editor-variant-annotated.png)
+
+E: The breadcrumb trail showing the names of the opened Variant asset and Library assets it inherits from.
+F: The Main Library the Variant asset inherits from.
+G: The Inherited group type that displays all Categories inherited from the Main Library.
+H: A vertical white line which shows that an [override](SL-Main-Library.md#create-overrides) is present.
+
+## Label context menu
+
+Right-click over a Label in the Labels column to open this context menu.
+
+![](images/sl-editor-label-context-menu.png)
+
+
+Property | Description
+---------|----------
+ **Create a new Label** | Create a new local Label in the selected Category.
+ **Rename Label** | Rename the selected Label. This is unavailable if it's an inherited Label.
+ **Delete Selected Labels** | Deletes all selected Labels. This is unavailable if they're inherited Labels.
+ **Revert Selected Overrides** | Removes all overrides made in the selected Labels, and returns them back to their inherited state.
+ **Revert All Overrides**| Removes all overrides in the selected Category.
+ **Show Label Location** | This is only available if the selected Label has a set sprite reference. Select this to open the sprite that the Label references.
+
+## Additional resources
+- [Sprite Library Editor fundamentals](SL-Editor.md)
+- [Setting up for Sprite Swap](SpriteSwapSetup.md)
\ No newline at end of file
diff --git a/Documentation~/SL-Editor.md b/Documentation~/SL-Editor.md
new file mode 100644
index 00000000..8a2b8b2b
--- /dev/null
+++ b/Documentation~/SL-Editor.md
@@ -0,0 +1,65 @@
+# Sprite Library Editor fundamentals
+
+The Sprite Library Editor window is where you edit the content of a selected [Sprite Library Asset](SL-Asset.md). Select a Sprite Library Asset and then select **Open in Sprite Library Editor** in its Inspector window to open this editor. You can also open the Sprite Library Editor window directly by going to **Window** > **2D** > **Sprite Library Editor**.
+
+A Sprite Library Asset groups the sprites it contains into [Categories](#categories) and [Labels](#labels), and you edit their contents in the Sprite Library Editor window. This page shows you the [basic features](#useful-editor-features) of the Sprite Library Editor and how to begin editing a Sprite Library Asset.
+
+## Categories
+
+![](images/2D-animation-SLAsset-add-category.png)
_Creating a new Category in the Categories column._
+
+Use **Categories** to contain and group **Labels** together for a common purpose to make it easier to organize your sprites. For example, you can create a Category named 'Hat' for Labels which refer to sprites of hats for your character.
+
+To create a new Category, select **Add (+)** in the Categories column, or [drag](SL-Drag.md) sprites directly into the Sprite Library Editor window. Give each Category a unique name to ensure that the editor correctly identifies each individual Category.
+
+### Local and inherited Categories
+
+![](images/2D-animation-SLAsset-category-local-inherited.png)
_**Inherited** and **Local** foldout groups in the **Categories** column._
+
+There are two types of Categories:
+
+- **Local**: A Local Category is a Category created in the open Sprite Library Asset in the editor window.
+- **Inherited**: An Inherited Category is a Category retrieved from the Sprite Library Asset set as the [Main Library](SL-Editor-UI.md#main-library).
+
+**Note**: You can't rename inherited Categories, to ensure that the Category names in the [Sprite Library Asset Variant](SL-Asset.md#create-a-sprite-library-asset-variant) matches the originals in the Main Library. This ensures that the Variant asset can inherit all Categories and Labels from the Main Library.
+
+To make changes to an inherited Category's content, you can create [overrides](SL-Main-Library.md#create-overrides) to an inherited Category or Label such as adding new Labels or changing the Sprite an inherited Label references instead.
+
+## Labels
+
+A Category contains multiple Labels, with each Label referencing a single sprite in the project. When you are [setting up for Sprite Swap](SpriteSwapSetup.md), Labels with similar functions are commonly placed in the same Category. For example, a Category named 'Hats' may contain Labels which each reference a different hat sprite.
+
+To create a new Label, select **Add (+)** in the Labels column, or [drag](SL-Drag.md) Sprite directly into the Sprite Library Editor window.
+
+![](images/2D-animation-SLAsset-add-label.png)_Create a new Label by selecting **Add (+)**._
+
+**Note:** If a Label is inherited from a Main Library and exists in an [inherited Category](#local-and-inherited-categories), you can't rename the inherited Label to ensure that it matches the original's name in the Main Library. This ensures that the Variant asset can inherit all Categories and Labels from the Main Library.
+
+You can create new Labels or edit the sprite reference of an inherited Label as [overrides](SL-Main-Library.md#create-overrides) to an inherited Category or Label. Refer to [Overrides in the Main Library](SL-Main-Library.md) for more information.
+
+## Useful editor features
+
+The following editor features make it more convenient to edit the contents of a Sprite Library Asset. For more information about all available editor features, refer to the [Sprite Library Editor reference](SL-Editor-UI.md).
+
+### Navigate between different assets
+
+![](images/2D-animation-SLAsset-breadcrumbs.png)
+
+When you open a Sprite Library Asset Variant in the Sprite Library Editor, you can use the Sprite Library Editor breadcrumb trail to navigate between different Sprite Library Assets that the opened asset inherits from. Select an asset in the breadcrumb trail to select it in the Project window.
+
+### Toggle between list or grid view
+
+You can view the sprite content of Labels in a list or in a grid. To toggle between these two views, select the respective icon at the lower right of the editor window, and use the slider to adjust the size of the visual preview.
+
+![](images/2D-animation-SLAsset-labels-view-type.png)
+
+### Filter Categories and Labels by name
+
+Filter the Categories and Labels by entering a text string into the filter bar in the upper right of the window. You can adjust the parameters of the filter by using the [filter context menu](SL-Editor-UI.md#filter-context-menu).
+
+![](images/sl-editor-filter-box.png)
+
+## Additional resources
+- [Sprite Library Editor reference](SL-Editor-UI.md)
+- [Drag sprites to create or edit Categories and Labels](SL-Drag.md)
+- [Overrides to the Main Library](SL-Main-Library.md)
\ No newline at end of file
diff --git a/Documentation~/SL-Main-Library.md b/Documentation~/SL-Main-Library.md
new file mode 100644
index 00000000..47382379
--- /dev/null
+++ b/Documentation~/SL-Main-Library.md
@@ -0,0 +1,31 @@
+# Overrides to the Main Library
+
+When you create a [Sprite Library Asset](SL-Asset.md), you have the option of [converting it into a Sprite Library Asset Variant](SL-Asset.md#convert-a-sprite-library-asset-into-a-variant), or [creating a Variant asset](SL-Asset.md#create-a-sprite-library-asset-variant) of the selected Sprite Library Asset. A Variant asset inherits all the Categories and Labels from the Sprite Library Asset as its **Main Library**. You can't directly change the Categories and Labels inherited from the Main Library, but you can add to the inherited content by adding new Categories and Labels in the form of [overrides](#create-overrides).
+
+## Inherited Categories and Labels limitations
+
+Assigning another existing Sprite Library Asset to the **Main Library** property of the current Sprite Library Asset allows the current Asset to access all Categories and Labels contained in the assigned Sprite Library Asset. Inherited Categories are visible in the [Sprite Library Editor window](SL-Editor-UI.md) in the [Inherited foldout group](SL-Editor-UI.md#categories-and-labels-columns), while the **Local** foldout group contains all Categories that exist solely in the current asset.
+
+You can't rename or remove the Labels of inherited Categories, but you can add new Labels to an inherited Category as an [override](#create-overrides).
+
+## Create overrides
+
+An override is any change you make to the contents of an inherited Category. While you can't rename or remove inherited Labels, you can do the following in an inherited Category:
+
+- [Create a new Label](SL-Editor.md#create-a-label).
+- [Change the sprite](#change-the-sprite-reference) that the Label references.
+- Revert overrides in selected Labels or for all inherited Categories and Labels.
+
+The Sprite Library Editor window [displays a vertical white line](SL-Editor-UI.md#variant-asset-specific-properties) next to inherited Categories and inherited Labels when an override is present.
+
+### Change the sprite reference
+
+You can change a Label's sprite reference by selecting a different sprite from the object picker next to the [Sprite object field](SL-Editor-UI.md#categories-and-labels-columns). You can also change the sprite reference by [dragging](SL-Drag.md) the desired sprite directly onto to a Label.
+
+To revert sprite reference changes made to selected Labels, right-click the Label(s) and select **Revert Selected Overrides** from the [context menu](SL-Editor-UI.md#label-context-menu) to restore all sprite references back to their original inherited state from the Main Library.
+
+![](images/2D-animation-SLAsset-label-revert.png)
_Revert changes to Labels in inherited Categories by selecting **Revert Selected Overrides**._
+
+To revert all overrides in the selected inherited Category, select **Revert All Overrides** from the context menu.
+
+**Caution:** Overrides aren't included in the [save state](SL-Editor-UI.md#saving-options) of the Sprite Library Editor, and reverting overrides will remove all overrides regardless of the previous save state. To undo the last action, press Ctrl+Z (macOS: Cmd+Z).
\ No newline at end of file
diff --git a/Documentation~/SL-Resolver.md b/Documentation~/SL-Resolver.md
new file mode 100644
index 00000000..5f16ecab
--- /dev/null
+++ b/Documentation~/SL-Resolver.md
@@ -0,0 +1,20 @@
+# Sprite Resolver component in Unity
+
+The Sprite Resolver component pulls information from the [Sprite Library Asset](SL-Asset.md) assigned to the [Sprite Library component](SL-component.md) at the root of the actor Prefab, and displays the Sprites available for selection. This component is part of the [Sprite Swap setup and workflow](SpriteSwapSetup.md), where attaching the Sprite Resolver component to a GameObject that's part of an actor Prefab allows you to change the Sprite rendered by that GameObject's [Sprite Renderer component](https://docs.unity3d.com/Manual/class-SpriteRenderer.html).
+
+## Property settings
+
+The component contains two properties - [Category](SL-Editor.md#categories) and [Label](SL-Editor.md#labels) - and the **Visual variant selector** interface which displays thumbnails of the Sprites within the Sprite Library Asset.
+
+![](images/2D-animation-SResolver-properties.png)
_Inspector window properties of Sprite Resolver component._
+
+| Property | Function |
+| ------------ | ------------------------------------------------------------ |
+| **Category** | Select the **Category** that contains the Sprite you want to use for this GameObject. |
+| **Label** | Select the **Label** name of the Sprite you want to use for this GameObject. |
+|**Visual variant selector** | Select the thumbnail of the Sprite you want to use. This selector displays all Sprites contained in the selected Category above. |
+
+## Additional resources
+* [Setting up for Sprite Swap](SpriteSwapSetup.md)
+* [Sprite Library Asset in Unity](SL-Asset.md)
+* [Sprite Library component in Unity](SL-component.md)
\ No newline at end of file
diff --git a/Documentation~/SL-component.md b/Documentation~/SL-component.md
new file mode 100644
index 00000000..508fb90c
--- /dev/null
+++ b/Documentation~/SL-component.md
@@ -0,0 +1,39 @@
+# Sprite Library component in Unity
+
+The **Sprite Library** component defines which [Sprite Library Asset](SL-Asset.md) a GameObject refers to at runtime. When you attach this component to a GameObject, the [Sprite Resolver component](SL-Resolver.md) attached to the same GameObject or child GameObject will refer to the Sprite Library Asset set by the Sprite Library component. This allows you to change the Sprite referenced by a [Sprite Renderer](https://docs.unity3d.com/Manual/class-SpriteRenderer) with the Sprite Resolver component.
+
+## Property settings
+
+In the Sprite Library component’s Inspector window, assign the desired Sprite Library Asset to the **Sprite Library Asset** property.
+
+![](images/2D-animation-SLComp-properties.png)
+
+After assigning a Sprite Library Asset, the Inspector window shows a visual preview of the content in the selected Sprite Library Asset.
+
+![](images/2D-animation-SLComp-preview.png)
+
+## Component functions
+
+Within the Sprite Library component Inspector window, you can [commit the same overrides](SL-Main-Library.md) to the assigned Sprite Library Asset as you would to the **Main Library** in the [Sprite Library Editor](SL-Editor.md) window. You add or remove new Categories, add or remove new Labels in a Category, and change the sprite a Label refers to.
+
+## Modified Sprites
+![](images/2D-animation-SLAsset-category-entry-icon.png)
_Example: A modified sprite retrieved from the Sprite Library Asset._
+
+The **+** icon appears at the upper left of a Label entry when:
+
+- You add a new Label to a Category from the retrieved Sprite Library Asset.
+- You change the sprite reference of a Label from the original sprite reference retrieved from the Sprite Library Asset.
+
+## Category and Label name conflict behavior
+
+The following are the ways Unity resolves any name conflicts that may occur when you replace the assigned Sprite Library Asset in the **Sprite Library Asset** property with another Sprite Library Asset.
+
+- If the same Category name already exists in the current set Sprite Library Asset, then Unity merges the Labels from Categories with the same name in both Sprite Library Assets into a single Category with that name.
+
+- If there are Labels with the same name within the same Category when you assign the Sprite Library Asset, then Unity merges the Labels into a single Label. The merged Label uses the sprite reference from the replacement Sprite Library Asset instead.
+
+**Note:** When you remove a Sprite Library Asset from the **Sprite Library Asset** property, overrides aren't saved to that Sprite Library Asset. All changes remain in the Sprite Library component.
+
+## Additional resources
+- [Swapping Sprite Library Assets](SLASwap.md)
+- [Overrides to the Main Library](SL-Main-Library.md)
\ No newline at end of file
diff --git a/Documentation~/SLASwap.md b/Documentation~/SLASwap.md
index 64c1315c..47f7ff15 100644
--- a/Documentation~/SLASwap.md
+++ b/Documentation~/SLASwap.md
@@ -9,13 +9,13 @@ The following example shows how to switch from a Sprite Library Asset of color S
2. [Import](PreparingArtwork.md) both .psb files into the Unity Editor. Both become separate Model Prefabs in the Asset window.
-3. [Create a Sprite Library Asset](SpriteSwapSetup.md) and assign each Sprite of the actor to a unique [Category](SLAsset.md#Categories). For convenience, name each Category and Label after the respective body part of the actor. You can also drag and drop your Sprites into the empty space in the Categories tab to populate them automatically. Save the changes once complete.
![](images/2d-anim-slasset-swap-category-color.png)
The Category and Label names for the parts of the color actor.
+3. [Create a Sprite Library Asset](SpriteSwapSetup.md) and assign each Sprite of the actor to a unique [Category](SL-Asset.md#Categories). For convenience, name each Category and Label after the respective body part of the actor. You can also drag and drop your Sprites into the empty space in the Categories tab to populate them automatically. Save the changes once complete.
![](images/2d-anim-slasset-swap-category-color.png)
The Category and Label names for the parts of the color actor.
4. Repeat step 3 for the grayscale actor. Use the same Category and Label names for the corresponding gray Sprites. Remember that you can drag and drop your Sprites to empty space in the Categories and their corresponding Labels should now have a new Sprite reference.
![](images/2d-anim-slasset-swap-category-gray.png)
The grayscale Sprites with the same corresponding Category and Label names.
-5. Drag the color Model Prefab into the Scene view, and go to the root GameObject. Add a [Sprite Library component](SLAsset.md#sprite-library-component) to the root GameObject and assign the color Sprite Library Asset created in step 3 to the **Sprite Library Asset** property.
![](images/2d-anim-slasset-swap-step-5.png)
+5. Drag the color Model Prefab into the Scene view, and go to the root GameObject. Add a [Sprite Library component](SL-component.md) to the root GameObject and assign the color Sprite Library Asset created in step 3 to the **Sprite Library Asset** property.
![](images/2d-anim-slasset-swap-step-5.png)
-6. For every Sprite Renderer in the Instantiated Prefab, add a [Sprite Resolver component](SLAsset.md#sprite-resolver-component) and ensure that the Sprite Resolver component has the same Sprite selected as the Sprite Renderer.
+6. For every Sprite Renderer in the Instantiated Prefab, add a [Sprite Resolver component](SL-Resolver.md) and ensure that the Sprite Resolver component has the same Sprite selected as the Sprite Renderer.
7. With the Inspector window of the color Prefab root GameObject remaining open, go to the Asset window and assign the Sprite Library Asset created in step 4 to the **Sprite Library Asset** property of the Sprite Library component.
diff --git a/Documentation~/SpriteSwapIntro.md b/Documentation~/SpriteSwapIntro.md
index 3ba00bee..93db6a07 100644
--- a/Documentation~/SpriteSwapIntro.md
+++ b/Documentation~/SpriteSwapIntro.md
@@ -1,23 +1,41 @@
-# Sprite Swapping
-__Sprite Swap__ is a feature that enables you to change a GameObject’s rendered Sprite at runtime. This has a number of uses, such as easily creating multiple characters which [share a skeleton](ex-skeleton-sharing.md) (requires the [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest)) or [reuse existing bone and Mesh data](SkinEdToolsShortcuts.md#copy-and-paste-behavior) while looking visually different.
+# Introduction to Sprite Swap
+This page introduces what's Sprite Swap, its different uses and its limitations. **Sprite Swap** refers to changing the rendered Sprite of a GameObject at runtime, which is useful when animating the Sprites that make up a 2D actor or other GameObjects.
-The 2D Animation package comes with several Sample projects of different ways you can use Sprite Swap to achieve different effects and features, refer to the [Sample documentation here](ex-sprite-swap.md) for more information about these examples.
+For example, you can [swap the individual Sprites](ex-sprite-swap.md#part-swap) that make up an animated actor to create multiple actors that [share the same skeleton](ex-skeleton-sharing.md) (requires the [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest); or [create animation clips](ex-sprite-swap.md#animated-swap) by swapping the Sprites at runtime.
-## Sprite Swap Assets and components
-Sprite Swap requires the following Assets and components, which are all included with the 2D Animation package:
+You can import [sample projects](Examples.md) for the 2D Animation package by selecting the option in the 2D Animation package window. Refer to the individual [Sprite Swap examples](ex-sprite-swap.md) pages for more information about these samples.
-- The [Sprite Library Asset](SLAsset.md) that contains a set of selected Sprites which are assigned to different [Categories](SLAsset.md#category) and [Entries or Labels](SLAsset.md#entry).
+## Required assets and components
-- Attach the [Sprite Library component](SLAsset.html#sprite-library-component) to a GameObject to assign or change which __Sprite Library Asset__ the GameObject refers to.
+Sprite Swap requires the following Assets and component, which are available with the 2D Animation package:
-- The [Sprite Resolver component](SLAsset.html#sprite-resolver-component) is used to request a Sprite registered to the Sprite Library Asset by referring to the __Category__ and __Label__ value of the desired Sprite.
+* [Sprite Library Asset](SL-Asset.md): The Sprite Library Asset contains a set of selected Sprites which are assigned to different [Categories](SL-Editor.md#categories) and [Labels](SL-Editor.md#labels).
+
-## Skeletal animation limitations
-To ensure Sprite Swap works correctly with skeletal animation, the skeleton must be identical between the Sprites being swapped. Use the [Copy and Paste tools](SkinEdToolsShortcuts.md#copy-and-paste-behavior) to duplicate the bone and skeleton data from one Sprite to another to ensure they can be swapped correctly.
+* [Sprite Library component](SL-component.md): The Sprite Library component determines which Sprite Library Asset a GameObject refers to.
+
-## Animator limitations
-In a single [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html), you cannot have one [Animation Clip](https://docs.unity3d.com/Manual/AnimationClips.html) animating the [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) assigned Sprite while another [Animation Clip](https://docs.unity3d.com/Manual/AnimationClips.html) animates the [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) Sprite Hash. If these two clips are in the same [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html), they will conflict with each other causing unwanted playback results.
+* [Sprite Resolver component](SL-Resolver.md): The Sprite Resolver component requests a Sprite registered to the Sprite Library Asset by referring to the **Category** and **Label** value of the desired Sprite.
-To resolve this issue, we advise the following solutions. The first solution is to separate the [Animation Clips](https://docs.unity3d.com/Manual/AnimationClips.html) into separate [Animator Controllers](https://docs.unity3d.com/Manual/AnimatorControllers.html) that contain only clips that animate either a [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) Sprite or the [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) Sprite Hash; but not both types in the same [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html).
+## Technical limitations
-The second solution is to update all [Animation Clips](https://docs.unity3d.com/Manual/AnimationClips.html) to the same type so that they can all remain in a single [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html), by converting all clips animating a [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) Sprite to animating a [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) Sprite Hash, or vice versa.
+The following are technical limitations which you should keep in mind when using Sprite Swap.
+
+### Skeletal animation limitations
+
+If you want to [animate your actor](Animating-actor.md) and use Sprite Swap with skeletal animation, both sprites that are swapped must have an identical skeleton. Use the [Copy and Paste tools of the Skinning Editor](SkinEdToolsShortcuts.md#copy-and-paste-behavior) to duplicate the bone and skeleton data from one sprite to another to ensure they will swap correctly.
+
+### Animator limitations
+In a single [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html), you can't have one [Animation Clip](https://docs.unity3d.com/Manual/AnimationClips.html) animating the [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) assigned sprite while another [Animation Clip](https://docs.unity3d.com/Manual/AnimationClips.html) animates the [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) sprite hash. If these two clips are in the same [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html), they will conflict with each other and cause unwanted playback results.
+
+Use the following recommended methods to resolve this issue.
+
+1. The first method is to separate the [Animation Clips](https://docs.unity3d.com/Manual/AnimationClips.html) into separate [Animator Controllers](https://docs.unity3d.com/Manual/AnimatorControllers.html) that contain only clips that animate either a [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) sprite or the [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) sprite hash but not both types in the same [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html).
+
+
+2. The second method is to update all [Animation Clips](https://docs.unity3d.com/Manual/AnimationClips.html) to the same type so that they can all remain in a single [Animator Controller](https://docs.unity3d.com/Manual/AnimatorControllers.html). To do so, convert all clips animating a [Sprite Renderer’s](https://docs.unity3d.com/Manual/class-SpriteRenderer.html) sprite to animating a [Sprite Resolver’s](SLAsset.html#sprite-resolver-component) sprite hash, or vice versa.
+
+## Additional resources
+- [Animation](https://docs.unity3d.com/Manual/AnimationSection.html)
+- [Skinning Editor](SkinningEditor.md)
+- [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest)
diff --git a/Documentation~/SpriteSwapLanding.md b/Documentation~/SpriteSwapLanding.md
new file mode 100644
index 00000000..87975b0f
--- /dev/null
+++ b/Documentation~/SpriteSwapLanding.md
@@ -0,0 +1,18 @@
+# Sprite Swap
+Use **Sprite Swap** to change a GameObject's rendered Sprite at runtime. You can swap the entire set of Sprites that make up a character (referred to as an 'actor') at once, or swap specific Sprites and 'parts' of an actor to create animation loops or other game-related features.
+
+For various examples of how you can use this feature in a Project, [import sample Projects](Examples.md) for the 2D Animation package and refer to the [Sprite Swap examples](ex-sprite-swap.md) for examples of the different ways you can use Sprite Swap in your Projects.
+
+**Topic** | **Description**
+:----------|:----------------
+[Introduction to Sprite Swap](SpriteSwapIntro.md) | Understand Sprite Swap, its requirements and limitations.
+[Sprite Library Asset in Unity](SL-Asset.md) | Understand what the Sprite Library Asset is and how to use its features.
+[Sprite Library Editor fundamentals](SL-Editor.md) | Understand how to use the Sprite Library Editor's main features.
+[Overrides to the Main Library](SL-Main-Library.md) | Create overrides are and understand how to use them to make changes.
+[Drag sprites to create or edit Categories and Labels](SL-Drag.md) | Drag sprites to perform certain functions in the Sprite Library Editor automatically.
+[Sprite Library component in Unity](SL-component.md) | Understand what the Sprite Library component is and how to use its features.
+[Sprite Resolver component in Unity](SL-Resolver.md) | Understand what the Sprite Resolver component is and how to use it.
+[Setting up Sprite Swap](SpriteSwapSetup.md) | Understand how to set up the different components and assets needed to use Sprite Swap.
+
+## Additional resources
+* [PSD Importer package](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest)
\ No newline at end of file
diff --git a/Documentation~/SpriteSwapSetup.md b/Documentation~/SpriteSwapSetup.md
index 0b2ea8f8..c4bfe3fb 100644
--- a/Documentation~/SpriteSwapSetup.md
+++ b/Documentation~/SpriteSwapSetup.md
@@ -1,19 +1,20 @@
-# Sprite Swap setup
-Follow the steps below to create a [Sprite Library Asset](SLAsset.md), and choose which GameObjects refer to the Asset:
+# Setting up for Sprite Swap
+The following steps
+Follow the steps below to create a [Sprite Library Asset](SL-Asset.md), and choose which GameObjects refer to the Asset:
1. Select the Sprite Library Asset from the Asset creation menu by going to **Asset > Create > 2D > Sprite Library Asset**
![](images/2D-animation-SLAsset-dropdown.png)
-2. Select the new Sprite Library Asset and open it in the Sprite Library Editor. The editor displays the list of [Categories](SLAsset.md#Categories) and [Labels](SLAsset.md#Labels) available in the Asset.
+2. Select the new Sprite Library Asset and open it in the Sprite Library Editor. The editor displays the list of [Categories](SL-Asset.md#Categories) and [Labels](SL-Asset.md#Labels) available in the Asset.
3. Select **+** at the lower right of the List to add a new Category. Enter a name into **Category** (the default name is 'New Category'). Each Category in the same Sprite Library Asset must have a unique name.
![](images/2D-animation-SLAsset-add-category.png)
-4. Add new Labels into the Category by either selecting **+** and then selecting a Sprite from the Object Picker window; or by [dragging](SLAsset.md#drag-and-drop) a Sprite, Texture or [PSD Importer supported file type](#PreparingArtwork.md) onto an empty space within the Categories tab
![](images/2D-animation-SLAsset-category-entry2.png)
+4. Add new Labels into the Category by either selecting **+** and then selecting a Sprite from the Object Picker window; or by [dragging](SL-Asset.md#drag-and-drop) a Sprite, Texture or [PSD Importer supported file type](#PreparingArtwork.md) onto an empty space within the Categories tab
![](images/2D-animation-SLAsset-category-entry2.png)
5. Create an empty GameObject (menu: Right-click on the **Hierarchy window > Create Empty**). Select it and then add the Sprite Renderer component.
![](images/2d-anim-add-sprite-renderer.png)
-6. Add the [Sprite Library](SLAsset.md#sprite-library-component) component to the same GameObject. Assign the Sprite Library Asset created in step 3 to **Sprite Library Asset**.
![](images/2D-animation-SLComp-properties.png)
+6. Add the [Sprite Library](SL-component.md) component to the same GameObject. Assign the Sprite Library Asset created in step 3 to **Sprite Library Asset**.
![](images/2D-animation-SLComp-properties.png)
-7. Add the [Sprite Resolver](SLAsset.md#sprite-resolver-component) component to the same GameObject.
![](images/2d-anim-add-sprite-resolver-comp-step.png)
+7. Add the [Sprite Resolver](SL-Resolver.md) component to the same GameObject.
![](images/2d-anim-add-sprite-resolver-comp-step.png)
8. Open the **Category** drop-down menu, and select a Category you created in step 3. The **Label** drop-down menu will become available and display thumbnails of the Sprites contained in the Category.
![](images/2D-animation-SResolver-properties.png)
diff --git a/Documentation~/TableOfContents.md b/Documentation~/TableOfContents.md
index 571329b2..df8bc0e1 100644
--- a/Documentation~/TableOfContents.md
+++ b/Documentation~/TableOfContents.md
@@ -1,24 +1,31 @@
* [Introduction to 2D Animation](index)
- * [What's new](whats-new.md)
- * [2D Animation Asset Upgrader](AssetUpgrader)
+ * [What's new](whats-new)
+ * [2D Animation Asset Upgrader](AssetUpgrader)
* [Preparing and importing artwork](PreparingArtwork)
- * [Sprite Skin component](SpriteSkin)
+ * [Sprite Skin component](SpriteSkin)
* [Skinning editor](SkinningEditor)
- * [Tool Preferences](ToolPref)
- * [Editor tools and shortcuts](SkinEdToolsShortcuts)
- * [Sprite Visibility panel](SpriteVis)
+ * [Tool Preferences](ToolPref)
+ * [Editor tools and shortcuts](SkinEdToolsShortcuts)
+ * [Sprite Visibility panel](SpriteVis)
* [Actor rigging and weighing workflow](CharacterRig)
* [Animating an actor](Animating-actor)
- * [2D Inverse Kinematics](2DIK)
-* [Sprite Swap](SpriteSwapIntro)
- * [Sprite Library Asset and other components](SLAsset)
- * [Setting up Sprite Swap](SpriteSwapSetup)
- * [Swapping individual Sprites](CharacterParts)
- * [Swapping Sprite Library Assets](SLASwap)
+ * [2D Inverse Kinematics](2DIK)
+* [Sprite Swap](SpriteSwapLanding)
+ * [Introduction to Sprite Swap](SpriteSwapIntro)
+ * [Sprite Library Asset in Unity](SL-Asset)
+ * [Sprite Library Editor fundamentals](SL-Editor)
+ * [Sprite Library Editor reference](SL-Editor-UI)
+ * [Overrides to the Main Library](SL-Main-Library)
+ * [Drag sprites to create or edit Categories and Labels](SL-Drag)
+ * [Sprite Library component in Unity](SL-component)
+ * [Sprite Resolver component in Unity](SL-Resolver)
+ * [Setting up for Sprite Swap](SpriteSwapSetup)
+ * [How to swap individual Sprites](CharacterParts)
+ * [Swapping Sprite Library Assets](SLASwap)
* [Importing Samples](Examples)
- * [Simple](ex-simple)
- * [Single Skinned Sprite](ex-single-skinned-sprite)
- * [Character](ex-psd-importer)
- * [Sprite Swap examples](ex-sprite-swap)
- * [Skeleton Sharing](ex-skeleton-sharing)
- * [Runtime Swap](ex-runtime-swap)
+ * [Simple](ex-simple)
+ * [Single Skinned Sprite](ex-single-skinned-sprite)
+ * [Character](ex-psd-importer)
+* [Sprite Swap sample projects](ex-sprite-swap)
+ * [Skeleton Sharing](ex-skeleton-sharing)
+ * [Runtime Swap](ex-runtime-swap)
\ No newline at end of file
diff --git a/Documentation~/ex-sprite-swap.md b/Documentation~/ex-sprite-swap.md
index 8f133638..d22e1ecb 100644
--- a/Documentation~/ex-sprite-swap.md
+++ b/Documentation~/ex-sprite-swap.md
@@ -13,13 +13,13 @@ The Scenes for the following samples can be all found in `Assets/Samples/2D Anim
- [Runtime Swap](ex-runtime-swap.md)
## Animated Swap
-This sample demonstrates how to use Sprite Swap to create a reusable Animation Clip for animations that include both Sprite swapping and [deformation](SpriteSkin.md) of the Sprites. Note that the following example requires the [PSD Importer](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest) to be installed.
+This sample demonstrates how to use Sprite Swap to create a reusable Animation Clip for animations that include both Sprite swapping and [deformation](SpriteSkin.md) of the Sprites. **Note:** Install the [PSD Importer](https://docs.unity3d.com/Packages/com.unity.2d.psdimporter@latest) package to use this sample.
Open the Scene file `1 Animated Swap.unity` to see the sample in action.
![](images/2D-animation-samples-spriteswap-animated1.png)
Initial frame with the hands in thumbs-up position.
-This sample builds on the reusable Animation Clip example from the [Flipbook Animation Swap](#flipbook-animation-swap) sample. This sample uses two different source files located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 SpriteSwap/Sprites`. The Assets used are:
+This sample uses two different source files located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 SpriteSwap/Sprites`. The Assets used are:
- `dialog.psb`
- `dialog gray.psb`
@@ -28,7 +28,7 @@ These Assets are imported with the PSD Importer with its **Character Rig** prope
![](images/2D-animation-samples-spriteswap-animated2.png)
Swapped to a frame with the hands open.
-Two Sprite Library Assets are created using the same steps demonstrated in the [Flipbook Animation Swap](#flipbook-animation-swap) sample. They are located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 Sprite Swap/Sprite Library` and are:
+They are located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 Sprite Swap/Sprite Library` and are:
- `dialog.spriteLib`
![](images/2D-animation-samples-spriteswap-animated-spritelib1.png)
@@ -40,13 +40,13 @@ Follow the steps below to reconstruct the sample Scene:
1. Drag both `dialog.psb` and `dialog gray.psb` Prefabs from the Project window into the Scene.
-2. Add the [Sprite Library component](SLAsset.md#sprite-library-component) to `dialog` GameObject, then assign the `dialog.spriteLib` Asset to its **Sprite Library Asset** property.
+2. Add the [Sprite Library component](SL-component.md) to `dialog` GameObject, then assign the `dialog.spriteLib` Asset to its **Sprite Library Asset** property.
-3. Add the [Sprite Library component](SLAsset.md#sprite-library-component) to `dialog gray` GameObject, then assign the `dialog gray.spriteLib` Asset to its **Sprite Library Asset** property.
+3. Add the [Sprite Library component](SL-component.md) to `dialog gray` GameObject, then assign the `dialog gray.spriteLib` Asset to its **Sprite Library Asset** property.
4. Expand the `dialog` GameObject's hierarchy and disable the `R_arm_2` child GameObject. This Asset is not required as it is swapped in during the animation.
-5. Go to the `R_arm_1` GameObject, and add the [Sprite Resolver component](SLAsset.md#sprite-resolver-component). Select the `R_arm_2` graphic from the **Label** drop-down menu or from its thumbnail.
![](images/2D-animation-samples-spriteswap-animated-spritelib3.png)
+5. Go to the `R_arm_1` GameObject, and add the [Sprite Resolver component](SL-Resolver.md). Select the `R_arm_2` graphic from the **Label** drop-down menu or from its thumbnail.
![](images/2D-animation-samples-spriteswap-animated-spritelib3.png)
6. Repeat steps 4 to 5 with the `dialog gray` GameObject.
@@ -65,7 +65,7 @@ In the Scene, each part has three different visual options that can be swapped.
- `Skeleton.psb`
- `Witch.psb`
-A [Sprite Library Asset](SLAsset.md) containing Sprites made from all three graphic Assets above is created. A Category is created for each body part of the actor, with three Entries derived from the three different versions of the character. The Asset is located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 SpriteSwap/Sprite Library/Part Swap.spriteLib`.
+A [Sprite Library Asset](SL-Asset.md) containing Sprites made from all three graphic Assets above is created. A Category is created for each body part of the actor, with three Entries derived from the three different versions of the character. The Asset is located in `Assets/Samples/2D Animation/[X.Y.Z]/Samples/5 SpriteSwap/Sprite Library/Part Swap.spriteLib`.
![](images/2D-animation-samples-partswap-SLasset.png)Corresponding parts from each of the three versions of the actor, and named accordingly.
@@ -103,7 +103,7 @@ swapOption.spriteResolver.SetCategoryAndLabel(swapOption.category, swapOption.dr
```
## Full Skin Swap
-This sample demonstrates how to swap Sprite visuals using the provided API by changing the [Sprite Library Asset](SLAsset.md) referenced by the Sprite Library component. Open the `3 Full Swap.unity` Scene to see the sample in action.
+This sample demonstrates how to swap Sprite visuals using the provided API by changing the [Sprite Library Asset](SL-Asset.md) referenced by the Sprite Library component. Open the `3 Full Swap.unity` Scene to see the sample in action.
![](images/2D-animation-samples-fullswap-scene.png)
diff --git a/Documentation~/images/sl-editor-blank.png b/Documentation~/images/sl-editor-blank.png
new file mode 100644
index 00000000..beb28698
Binary files /dev/null and b/Documentation~/images/sl-editor-blank.png differ
diff --git a/Documentation~/images/sl-editor-category-contents.png b/Documentation~/images/sl-editor-category-contents.png
new file mode 100644
index 00000000..6fc1c65c
Binary files /dev/null and b/Documentation~/images/sl-editor-category-contents.png differ
diff --git a/Documentation~/images/sl-editor-drag-knight-drag.png b/Documentation~/images/sl-editor-drag-knight-drag.png
new file mode 100644
index 00000000..7b93f1b4
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-knight-drag.png differ
diff --git a/Documentation~/images/sl-editor-drag-knight-drop.png b/Documentation~/images/sl-editor-drag-knight-drop.png
new file mode 100644
index 00000000..6d5b2d7f
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-knight-drop.png differ
diff --git a/Documentation~/images/sl-editor-drag-select-sprites-drop.png b/Documentation~/images/sl-editor-drag-select-sprites-drop.png
new file mode 100644
index 00000000..50d80e19
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-select-sprites-drop.png differ
diff --git a/Documentation~/images/sl-editor-drag-select-sprites.png b/Documentation~/images/sl-editor-drag-select-sprites.png
new file mode 100644
index 00000000..3e398a71
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-select-sprites.png differ
diff --git a/Documentation~/images/sl-editor-drag-sprite-category.png b/Documentation~/images/sl-editor-drag-sprite-category.png
new file mode 100644
index 00000000..2a377734
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-sprite-category.png differ
diff --git a/Documentation~/images/sl-editor-drag-wolf-labels.png b/Documentation~/images/sl-editor-drag-wolf-labels.png
new file mode 100644
index 00000000..61a603bb
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-wolf-labels.png differ
diff --git a/Documentation~/images/sl-editor-drag-wolf-layer-groups.png b/Documentation~/images/sl-editor-drag-wolf-layer-groups.png
new file mode 100644
index 00000000..f722ec2c
Binary files /dev/null and b/Documentation~/images/sl-editor-drag-wolf-layer-groups.png differ
diff --git a/Documentation~/images/sl-editor-filter-box.png b/Documentation~/images/sl-editor-filter-box.png
new file mode 100644
index 00000000..90f24205
Binary files /dev/null and b/Documentation~/images/sl-editor-filter-box.png differ
diff --git a/Documentation~/images/sl-editor-filter-context-menu.png b/Documentation~/images/sl-editor-filter-context-menu.png
new file mode 100644
index 00000000..76681948
Binary files /dev/null and b/Documentation~/images/sl-editor-filter-context-menu.png differ
diff --git a/Documentation~/images/sl-editor-label-context-menu.png b/Documentation~/images/sl-editor-label-context-menu.png
new file mode 100644
index 00000000..361f4e06
Binary files /dev/null and b/Documentation~/images/sl-editor-label-context-menu.png differ
diff --git a/Documentation~/images/sl-editor-main-library.png b/Documentation~/images/sl-editor-main-library.png
new file mode 100644
index 00000000..7f821f82
Binary files /dev/null and b/Documentation~/images/sl-editor-main-library.png differ
diff --git a/Documentation~/images/sl-editor-save-options.png b/Documentation~/images/sl-editor-save-options.png
new file mode 100644
index 00000000..587cccd3
Binary files /dev/null and b/Documentation~/images/sl-editor-save-options.png differ
diff --git a/Documentation~/images/sl-editor-variant-annotated.png b/Documentation~/images/sl-editor-variant-annotated.png
new file mode 100644
index 00000000..d464522e
Binary files /dev/null and b/Documentation~/images/sl-editor-variant-annotated.png differ
diff --git a/Documentation~/images/sl-editor-variant-overrides.png b/Documentation~/images/sl-editor-variant-overrides.png
new file mode 100644
index 00000000..4d6ef182
Binary files /dev/null and b/Documentation~/images/sl-editor-variant-overrides.png differ
diff --git a/Editor/EditorUtilities.cs b/Editor/EditorUtilities.cs
index 49f83ac3..74495837 100644
--- a/Editor/EditorUtilities.cs
+++ b/Editor/EditorUtilities.cs
@@ -1,4 +1,5 @@
using System;
+using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.U2D.Animation
@@ -8,11 +9,23 @@ internal static class EditorUtilities
///
/// Checks if element exists in array independent of the order of X & Y.
///
- public static bool ContainsAny(this Vector2Int[] array, Vector2Int element)
+ public static bool ContainsAny(this int2[] array, int2 element)
{
return Array.FindIndex(array, e =>
(e.x == element.x && e.y == element.y) ||
(e.y == element.x && e.x == element.y)) != -1;
- }
+ }
+
+ public static int2[] ToInt2(Vector2Int[] source) => Array.ConvertAll(source, e => new int2(e.x, e.y));
+ public static Vector2Int[] ToVector2Int(int2[] source) => Array.ConvertAll(source, e => new Vector2Int(e.x, e.y));
+ public static float2[] ToFloat2(Vector2[] source) => Array.ConvertAll(source, e => (float2)e);
+ public static Vector2[] ToVector2(float2[] source) => Array.ConvertAll(source, e => (Vector2)e);
+
+ public static T[] CreateCopy(T[] source) where T : struct
+ {
+ var copy = new T[source.Length];
+ Array.Copy(source, copy, source.Length);
+ return copy;
+ }
}
}
\ No newline at end of file
diff --git a/Editor/MeshUtilities.cs b/Editor/MeshUtilities.cs
new file mode 100644
index 00000000..f4d76053
--- /dev/null
+++ b/Editor/MeshUtilities.cs
@@ -0,0 +1,61 @@
+using Unity.Collections;
+using Unity.Mathematics;
+
+namespace UnityEngine.U2D.Animation
+{
+ internal static class MeshUtilities
+ {
+ ///
+ /// Get the outline edges from a set of indices.
+ /// This method expects the index array to be laid out with one triangle for every 3 indices.
+ /// E.g. triangle 0: index 0 - 2, triangle 1: index 3 - 5, etc.
+ ///
+ public static int2[] GetOutlineEdges(in int[] indices)
+ {
+ var edges = new NativeHashMap(indices.Length / 3, Allocator.Persistent);
+
+ for (var i = 0; i < indices.Length; i += 3)
+ {
+ var i0 = indices[i];
+ var i1 = indices[i + 1];
+ var i2 = indices[i + 2];
+
+ var edge0 = new int2(i0, i1);
+ var edge1 = new int2(i1, i2);
+ var edge2 = new int2(i2, i0);
+
+ AddToEdgeMap(edge0, ref edges);
+ AddToEdgeMap(edge1, ref edges);
+ AddToEdgeMap(edge2, ref edges);
+ }
+
+ var outlineEdges = new NativeList(edges.Count(), Allocator.Temp);
+ foreach(var edgePair in edges)
+ {
+ // If an edge is only used in one triangle, it is an outline edge.
+ if (edgePair.Value.z == 1)
+ outlineEdges.Add(edgePair.Value.xy);
+ }
+ edges.Dispose();
+
+ return outlineEdges.ToArray();
+ }
+
+ static void AddToEdgeMap(int2 edge, ref NativeHashMap edgeMap)
+ {
+ var tmpEdge = math.min(edge.x, edge.y) == edge.x ? edge.xy : edge.yx;
+ var hashCode = tmpEdge.GetHashCode();
+
+ // We store the hashCode as key, so that we can do less GetHashCode-calls.
+ // Then we store the count the int3s z-value.
+ if (!edgeMap.ContainsKey(hashCode))
+ edgeMap.Add(hashCode, new int3(edge, 1));
+ else
+ {
+ var val = edgeMap[hashCode];
+ val.z++;
+ edgeMap[hashCode] = val;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Editor/MeshUtilities.cs.meta b/Editor/MeshUtilities.cs.meta
new file mode 100644
index 00000000..b6552c98
--- /dev/null
+++ b/Editor/MeshUtilities.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 297af0e30a094d06ad9239ddb2c7e207
+timeCreated: 1654843866
\ No newline at end of file
diff --git a/Editor/SkinningModule/Cache/Cache.cs b/Editor/SkinningModule/Cache/Cache.cs
index d1f000e2..2adbd2e5 100644
--- a/Editor/SkinningModule/Cache/Cache.cs
+++ b/Editor/SkinningModule/Cache/Cache.cs
@@ -11,6 +11,7 @@ public static T Create() where T : Cache
{
var cache = CreateInstance();
cache.hideFlags = HideFlags.DontSave;
+ cache.name = cache.GetType().ToString();
return cache;
}
diff --git a/Editor/SkinningModule/Cache/CacheObject.cs b/Editor/SkinningModule/Cache/CacheObject.cs
index 5c004a2c..37475878 100644
--- a/Editor/SkinningModule/Cache/CacheObject.cs
+++ b/Editor/SkinningModule/Cache/CacheObject.cs
@@ -1,4 +1,3 @@
-using System;
using UnityEngine;
namespace UnityEditor.U2D.Animation
@@ -10,11 +9,12 @@ public static T Create(Cache owner) where T : CacheObject
var cacheObject = CreateInstance();
cacheObject.hideFlags = HideFlags.HideAndDontSave;
cacheObject.owner = owner;
+ cacheObject.name = cacheObject.GetType().ToString();
return cacheObject;
}
[SerializeField]
- private Cache m_Owner;
+ Cache m_Owner;
public Cache owner
{
diff --git a/Editor/SkinningModule/CopyTool.cs b/Editor/SkinningModule/CopyTool.cs
index 7704a21d..17e53516 100644
--- a/Editor/SkinningModule/CopyTool.cs
+++ b/Editor/SkinningModule/CopyTool.cs
@@ -524,8 +524,9 @@ void PasteMeshInSprite(SpriteCache sprite, SkinningCopySpriteData copySpriteData
meshTool.mesh.vertices[i] = position;
}
}
- meshTool.mesh.indices = copySpriteData.indices;
- meshTool.mesh.edges = copySpriteData.edges;
+
+ meshTool.mesh.SetIndices(copySpriteData.indices);
+ meshTool.mesh.SetEdges(copySpriteData.edges);
var boneIndices = new int[copySpriteData.boneWeightGuids.Count];
BoneCache[] newBones = null;
diff --git a/Editor/SkinningModule/IMGUI/SpriteMeshController.cs b/Editor/SkinningModule/IMGUI/SpriteMeshController.cs
index 61797091..abd814eb 100644
--- a/Editor/SkinningModule/IMGUI/SpriteMeshController.cs
+++ b/Editor/SkinningModule/IMGUI/SpriteMeshController.cs
@@ -1,4 +1,5 @@
using System;
+using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.U2D.Animation
@@ -19,7 +20,7 @@ struct EdgeIntersectionResult
EdgeIntersectionResult m_EdgeIntersectionResult;
public ISpriteMeshView spriteMeshView { get; set; }
- public ISpriteMeshData spriteMeshData { get; set; }
+ public BaseSpriteMeshData spriteMeshData { get; set; }
public ISelection selection { get; set; }
public ICacheUndo cacheUndo { get; set; }
public ITriangulator triangulator { get; set; }
@@ -27,10 +28,6 @@ struct EdgeIntersectionResult
public bool disable { get; set; }
public Rect frame { get; set; }
- bool m_Moved;
-
- Vector2[] m_MovedVerticesCache;
-
public void OnGUI()
{
m_SpriteMeshDataController.spriteMeshData = spriteMeshData;
@@ -79,18 +76,16 @@ public void OnGUI()
EditorGUI.EndDisabledGroup();
HandleSelectVertex();
+ HandleSelectEdge();
EditorGUI.BeginDisabledGroup(disable);
- HandleMoveVertex();
+ HandleMoveVertexAndEdge();
EditorGUI.EndDisabledGroup();
- HandleSelectEdge();
-
EditorGUI.BeginDisabledGroup(disable);
- HandleMoveEdge();
HandleRemoveVertices();
spriteMeshView.DoRepaint();
@@ -266,26 +261,25 @@ void HandleSelectEdge()
SelectEdge(spriteMeshView.hoveredEdge, additive);
}
- void HandleMoveVertex()
+ void HandleMoveVertexAndEdge()
{
- if (spriteMeshView.IsActionTriggered(MeshEditorAction.MoveVertex))
- m_Moved = false;
-
- if (spriteMeshView.DoMoveVertex(out var deltaPosition))
+ if (selection.Count == 0)
+ return;
+
+ if (spriteMeshView.DoMoveVertex(out var finalDeltaPos) || spriteMeshView.DoMoveEdge(out finalDeltaPos))
{
- deltaPosition = MathUtility.MoveRectInsideFrame(CalculateRectFromSelection(), frame, deltaPosition);
- CacheMovedVertices(deltaPosition);
+ var selectionArray = selection.elements;
- if (IsMovedSelectionIntersectingWithEdges())
- return;
+ finalDeltaPos = MathUtility.MoveRectInsideFrame(CalculateRectFromSelection(), frame, finalDeltaPos);
+ var movedVertexSelection = GetMovedVertexSelection(in selectionArray, spriteMeshData.vertices, finalDeltaPos);
- if (!m_Moved)
- {
- cacheUndo.BeginUndoOperation(TextContent.moveVertices);
- m_Moved = true;
- }
+ if (IsMovedEdgeIntersectingWithOtherEdge(in selectionArray, in movedVertexSelection, spriteMeshData.edges, spriteMeshData.vertices))
+ return;
+ if (IsMovedVertexIntersectingWithOutline(in selectionArray, in movedVertexSelection, spriteMeshData.outlineEdges, spriteMeshData.vertices))
+ return;
- MoveSelectedVertices();
+ cacheUndo.BeginUndoOperation(TextContent.moveVertices);
+ MoveSelectedVertices(in movedVertexSelection);
}
}
@@ -335,29 +329,6 @@ void HandleCreateEdge()
}
}
- void HandleMoveEdge()
- {
- if (spriteMeshView.IsActionTriggered(MeshEditorAction.MoveEdge))
- m_Moved = false;
-
- if (spriteMeshView.DoMoveEdge(out var deltaPosition))
- {
- deltaPosition = MathUtility.MoveRectInsideFrame(CalculateRectFromSelection(), frame, deltaPosition);
- CacheMovedVertices(deltaPosition);
-
- if (IsMovedSelectionIntersectingWithEdges())
- return;
-
- if (!m_Moved)
- {
- cacheUndo.BeginUndoOperation(TextContent.moveVertices);
- m_Moved = true;
- }
-
- MoveSelectedVertices();
- }
- }
-
void HandleRemoveVertices()
{
if (spriteMeshView.DoRemove())
@@ -498,11 +469,14 @@ void ClearSelection()
selection.Clear();
}
- void MoveSelectedVertices()
+ void MoveSelectedVertices(in Vector2[] movedVertices)
{
- foreach (var index in selection.elements)
- spriteMeshData.vertices[index] = m_MovedVerticesCache[index];
-
+ for (var i = 0; i < selection.Count; ++i)
+ {
+ var index = selection.elements[i];
+ spriteMeshData.vertices[index] = movedVertices[i];
+ }
+
Triangulate();
}
@@ -537,7 +511,7 @@ bool IsEdgeSelected()
var index1 = indices[0];
var index2 = indices[1];
- var edge = new Vector2Int(index1, index2);
+ var edge = new int2(index1, index2);
return spriteMeshData.edges.ContainsAny(edge);
}
@@ -707,52 +681,91 @@ bool SegmentIntersectsEdge(Vector2 p1, Vector2 p2, int ignoreIndex, ref Vector2
}
- void CacheMovedVertices(Vector2 deltaPosition)
+ static Vector2[] GetMovedVertexSelection(in int[] selection, in Vector2[] vertices, Vector2 deltaPosition)
{
- var vertexCount = spriteMeshData.vertexCount;
- if (m_MovedVerticesCache == null || m_MovedVerticesCache.Length != vertexCount)
- m_MovedVerticesCache = new Vector2[vertexCount];
-
- for (var v = 0; v < vertexCount; v++)
+ var movedVertices = new Vector2[selection.Length];
+ for (var i = 0; i < selection.Length; i++)
{
- var vPos = spriteMeshData.vertices[v];
- if (selection.Contains(v))
- vPos += deltaPosition;
- m_MovedVerticesCache[v] = vPos;
+ var index = selection[i];
+ movedVertices[i] = vertices[index] + deltaPosition;
}
+ return movedVertices;
}
- bool IsMovedSelectionIntersectingWithEdges()
+ static bool IsMovedEdgeIntersectingWithOtherEdge(in int[] selection, in Vector2[] movedVertices, in int2[] meshEdges, in Vector2[] meshVertices)
{
- var edges = spriteMeshData.edges;
- var edgeCount = edges.Length;
+ var edgeCount = meshEdges.Length;
var edgeIntersectionPoint = Vector2.zero;
- for (var e = 0; e < edges.Length; e++)
+ for (var i = 0; i < edgeCount; i++)
{
- var edgeInSelection = edges[e];
- var edgeIndex1 = edgeInSelection.x;
- var edgeIndex2 = edgeInSelection.y;
- if (!(selection.Contains(edgeIndex1) || selection.Contains(edgeIndex2)))
+ var selectionIndex = FindSelectionIndexFromEdge(selection, meshEdges[i]);
+ if (selectionIndex.x == -1 && selectionIndex.y == -1)
continue;
- var edgeStart = m_MovedVerticesCache[edgeIndex1];
- var edgeEnd = m_MovedVerticesCache[edgeIndex2];
+ var edgeStart = selectionIndex.x != -1 ? movedVertices[selectionIndex.x] : meshVertices[meshEdges[i].x];
+ var edgeEnd = selectionIndex.y != -1 ? movedVertices[selectionIndex.y] : meshVertices[meshEdges[i].y];
for (var o = 0; o < edgeCount; o++)
{
- if (o == e)
+ if (o == i)
continue;
- var otherEdge = edges[o];
- var otherIndex1 = otherEdge.x;
- var otherIndex2 = otherEdge.y;
-
- if (edgeInSelection.x == otherIndex1 || edgeInSelection.y == otherIndex1 ||
- edgeInSelection.x == otherIndex2 || edgeInSelection.y == otherIndex2)
+ if (meshEdges[i].x == meshEdges[o].x || meshEdges[i].y == meshEdges[o].x ||
+ meshEdges[i].x == meshEdges[o].y || meshEdges[i].y == meshEdges[o].y)
continue;
- if (MathUtility.SegmentIntersection(edgeStart, edgeEnd, m_MovedVerticesCache[otherIndex1], m_MovedVerticesCache[otherIndex2], ref edgeIntersectionPoint))
+ var otherSelectionIndex = FindSelectionIndexFromEdge(in selection, meshEdges[o]);
+ var otherEdgeStart = otherSelectionIndex.x != -1 ? movedVertices[otherSelectionIndex.x] : meshVertices[meshEdges[o].x];
+ var otherEdgeEnd = otherSelectionIndex.y != -1 ? movedVertices[otherSelectionIndex.y] : meshVertices[meshEdges[o].y];
+
+ if (MathUtility.SegmentIntersection(edgeStart, edgeEnd, otherEdgeStart, otherEdgeEnd, ref edgeIntersectionPoint))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ static int2 FindSelectionIndexFromEdge(in int[] selection, int2 edge)
+ {
+ var selectionIndex = new int2(-1, -1);
+ for (var m = 0; m < selection.Length; ++m)
+ {
+ if (selection[m] == edge.x)
+ {
+ selectionIndex.x = m;
+ break;
+ }
+ if (selection[m] == edge.y)
+ {
+ selectionIndex.y = m;
+ break;
+ }
+ }
+
+ return selectionIndex;
+ }
+
+ static bool IsMovedVertexIntersectingWithOutline(in int[] selection, in Vector2[] movedVertices, in int2[] outlineEdges, in Vector2[] meshVertices)
+ {
+ var edgeIntersectionPoint = Vector2.zero;
+
+ for (var i = 0; i < selection.Length; ++i)
+ {
+ var edgeStart = meshVertices[selection[i]];
+ var edgeEnd = movedVertices[i];
+
+ for (var m = 0; m < outlineEdges.Length; ++m)
+ {
+ if (selection[i] == outlineEdges[m].x || selection[i] == outlineEdges[m].y)
+ continue;
+
+ var otherSelectionIndex = FindSelectionIndexFromEdge(in selection, outlineEdges[m]);
+ var otherEdgeStart = otherSelectionIndex.x != -1 ? movedVertices[otherSelectionIndex.x] : meshVertices[outlineEdges[m].x];
+ var otherEdgeEnd = otherSelectionIndex.y != -1 ? movedVertices[otherSelectionIndex.y] : meshVertices[outlineEdges[m].y];
+
+ if (MathUtility.SegmentIntersection(edgeStart, edgeEnd, otherEdgeStart, otherEdgeEnd, ref edgeIntersectionPoint))
return true;
}
}
diff --git a/Editor/SkinningModule/IMGUI/WeightInspector.cs b/Editor/SkinningModule/IMGUI/WeightInspector.cs
index f3617571..1bc58d6e 100644
--- a/Editor/SkinningModule/IMGUI/WeightInspector.cs
+++ b/Editor/SkinningModule/IMGUI/WeightInspector.cs
@@ -8,7 +8,7 @@ internal class WeightInspector
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
private GUIContent[] m_BoneNameContents;
- public ISpriteMeshData spriteMeshData
+ public BaseSpriteMeshData spriteMeshData
{
get { return m_SpriteMeshDataController.spriteMeshData; }
set
diff --git a/Editor/SkinningModule/Selectors/CircleVertexSelector.cs b/Editor/SkinningModule/Selectors/CircleVertexSelector.cs
index 6eb8fb41..3f9bd54f 100644
--- a/Editor/SkinningModule/Selectors/CircleVertexSelector.cs
+++ b/Editor/SkinningModule/Selectors/CircleVertexSelector.cs
@@ -5,7 +5,7 @@ namespace UnityEditor.U2D.Animation
internal class CircleVertexSelector : ICircleSelector
{
public ISelection selection { get; set; }
- public ISpriteMeshData spriteMeshData { get; set; }
+ public BaseSpriteMeshData spriteMeshData { get; set; }
public Vector2 position { get; set; }
public float radius { get; set; }
diff --git a/Editor/SkinningModule/Selectors/GenericVertexSelector.cs b/Editor/SkinningModule/Selectors/GenericVertexSelector.cs
index e943a9ef..64809925 100644
--- a/Editor/SkinningModule/Selectors/GenericVertexSelector.cs
+++ b/Editor/SkinningModule/Selectors/GenericVertexSelector.cs
@@ -6,7 +6,7 @@ namespace UnityEditor.U2D.Animation
internal class GenericVertexSelector : ISelector
{
public ISelection selection { get; set; }
- public ISpriteMeshData spriteMeshData { get; set; }
+ public BaseSpriteMeshData spriteMeshData { get; set; }
public Func SelectionCallback;
public void Select()
diff --git a/Editor/SkinningModule/Selectors/RectVertexSelector.cs b/Editor/SkinningModule/Selectors/RectVertexSelector.cs
index 8a75770b..d88eba71 100644
--- a/Editor/SkinningModule/Selectors/RectVertexSelector.cs
+++ b/Editor/SkinningModule/Selectors/RectVertexSelector.cs
@@ -5,7 +5,7 @@ namespace UnityEditor.U2D.Animation
internal class RectVertexSelector : IRectSelector
{
public ISelection selection { get; set; }
- public ISpriteMeshData spriteMeshData { get; set; }
+ public BaseSpriteMeshData spriteMeshData { get; set; }
public Rect rect { get; set; }
public void Select()
diff --git a/Editor/SkinningModule/SkinningCache/CharacterPartCache.cs b/Editor/SkinningModule/SkinningCache/CharacterPartCache.cs
index d0a606be..676c18f3 100644
--- a/Editor/SkinningModule/SkinningCache/CharacterPartCache.cs
+++ b/Editor/SkinningModule/SkinningCache/CharacterPartCache.cs
@@ -75,7 +75,7 @@ public virtual SpriteCache sprite
set => m_Sprite = value;
}
- public BoneCache[] bones
+ public virtual BoneCache[] bones
{
get => m_Bones.ToArray();
set => m_Bones = new List(value);
diff --git a/Editor/SkinningModule/SkinningCache/MeshCache.cs b/Editor/SkinningModule/SkinningCache/MeshCache.cs
index cd6eee69..2be88e9f 100644
--- a/Editor/SkinningModule/SkinningCache/MeshCache.cs
+++ b/Editor/SkinningModule/SkinningCache/MeshCache.cs
@@ -1,24 +1,22 @@
+using System;
using System.Collections.Generic;
using UnityEditor.U2D.Sprites;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
- internal class MeshCache : SkinningObject, ISpriteMeshData
+ [Serializable]
+ internal class MeshCache : BaseSpriteMeshData
{
- [SerializeField]
- SpriteCache m_Sprite;
- [SerializeField]
- Vector2[] m_Vertices = new Vector2[0];
- [SerializeField]
- EditableBoneWeight[] m_VertexWeights = new EditableBoneWeight[0];
- [SerializeField]
- int[] m_Indices = new int[0];
- [SerializeField]
- Vector2Int[] m_Edges = new Vector2Int[0];
[SerializeField]
List m_Bones = new List();
+ [SerializeField]
+ SpriteCache m_Sprite;
+ public override string spriteName => sprite.name;
+ public override int boneCount => m_Bones.Count;
+ public override Rect frame => sprite.textureRect;
+
public ITextureDataProvider textureDataProvider { get; set; }
public SpriteCache sprite
@@ -27,66 +25,18 @@ public SpriteCache sprite
set => m_Sprite = value;
}
- public string spriteName => sprite.name;
- public Vector2[] vertices => m_Vertices;
- public EditableBoneWeight[] vertexWeights => m_VertexWeights;
-
- public Vector2Int[] edges
- {
- get => m_Edges;
- set => m_Edges = value;
- }
-
- public int[] indices
- {
- get => m_Indices;
- set => m_Indices = value;
- }
-
public BoneCache[] bones
{
get => m_Bones.ToArray();
set => SetBones(value);
}
-
- Rect ISpriteMeshData.frame => sprite.textureRect;
- public int vertexCount => m_Vertices.Length;
- public int boneCount => m_Bones.Count;
-
- public void SetVertices(Vector2[] newVertices, EditableBoneWeight[] newWeights)
- {
- m_Vertices = newVertices;
- m_VertexWeights = newWeights;
- }
-
- public void AddVertex(Vector2 position, BoneWeight weight)
- {
- var listOfVertices = new List(m_Vertices);
- listOfVertices.Add(position);
- m_Vertices = listOfVertices.ToArray();
-
- var listOfWeights = new List(m_VertexWeights);
- listOfWeights.Add(EditableBoneWeightUtility.CreateFromBoneWeight(weight));
- m_VertexWeights = listOfWeights.ToArray();
- }
-
- public void RemoveVertex(int index)
- {
- var listOfVertices = new List(m_Vertices);
- listOfVertices.RemoveAt(index);
- m_Vertices = listOfVertices.ToArray();
-
- var listOfWeights = new List(m_VertexWeights);
- listOfWeights.RemoveAt(index);
- m_VertexWeights = listOfWeights.ToArray();
- }
-
- SpriteBoneData ISpriteMeshData.GetBoneData(int index)
+
+ public override SpriteBoneData GetBoneData(int index)
{
var worldToLocalMatrix = sprite.worldToLocalMatrix;
-
+
//We expect m_Bones to contain character's bones references if character exists. Sprite's skeleton bones otherwise.
- if (skinningCache.hasCharacter)
+ if (sprite.skinningCache.hasCharacter)
worldToLocalMatrix = sprite.GetCharacterPart().worldToLocalMatrix;
SpriteBoneData spriteBoneData;
@@ -111,19 +61,11 @@ SpriteBoneData ISpriteMeshData.GetBoneData(int index)
return spriteBoneData;
}
- float ISpriteMeshData.GetBoneDepth(int index)
+ public override float GetBoneDepth(int index)
{
return m_Bones[index].depth;
}
- public void Clear()
- {
- m_Indices = new int[0];
- m_Vertices = new Vector2[0];
- m_VertexWeights = new EditableBoneWeight[0];
- m_Edges = new Vector2Int[0];
- }
-
public bool ContainsBone(BoneCache bone)
{
return m_Bones.Contains(bone);
diff --git a/Editor/SkinningModule/SkinningCache/SkinningCache.cs b/Editor/SkinningModule/SkinningCache/SkinningCache.cs
index ba7d71cb..ec3c938f 100644
--- a/Editor/SkinningModule/SkinningCache/SkinningCache.cs
+++ b/Editor/SkinningModule/SkinningCache/SkinningCache.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using Unity.Mathematics;
using UnityEngine;
using UnityEditor.U2D.Layout;
using UnityEditor.U2D.Sprites;
@@ -750,7 +751,7 @@ void CreateMeshCache(SpriteCache sprite, ISpriteMeshDataProvider meshProvider, I
Debug.Assert(m_SkeletonMap.ContainsKey(sprite));
var guid = new GUID(sprite.id);
- var mesh = CreateCache();
+ var mesh = new MeshCache();
var skeleton = m_SkeletonMap[sprite] as SkeletonCache;
mesh.sprite = sprite;
@@ -768,8 +769,8 @@ void CreateMeshCache(SpriteCache sprite, ISpriteMeshDataProvider meshProvider, I
}
mesh.SetVertices(vertices, weights);
- mesh.indices = meshProvider.GetIndices(guid);
- mesh.edges = meshProvider.GetEdges(guid);
+ mesh.SetIndices(meshProvider.GetIndices(guid));
+ mesh.SetEdges(EditorUtilities.ToInt2(meshProvider.GetEdges(guid)));
}
else
{
@@ -780,8 +781,8 @@ void CreateMeshCache(SpriteCache sprite, ISpriteMeshDataProvider meshProvider, I
vertexWeights[i] = new EditableBoneWeight();
mesh.SetVertices(vertices, vertexWeights);
- mesh.indices = indices;
- mesh.edges = edges.ToArray();
+ mesh.SetIndices(indices);
+ mesh.SetEdges(edges);
}
mesh.textureDataProvider = textureDataProvider;
@@ -790,25 +791,23 @@ void CreateMeshCache(SpriteCache sprite, ISpriteMeshDataProvider meshProvider, I
}
static void GenerateOutline(SpriteCache sprite, ITextureDataProvider textureDataProvider,
- out Vector2[] vertices, out int[] indices, out Vector2Int[] edges)
+ out Vector2[] vertices, out int[] indices, out int2[] edges)
{
if (textureDataProvider == null ||
textureDataProvider.texture == null)
{
vertices = new Vector2[0];
indices = new int[0];
- edges = new Vector2Int[0];
+ edges = new int2[0];
return;
}
const float detail = 0.05f;
const byte alphaTolerance = 200;
- var smd = new SpriteMeshData
- {
- frame = sprite.textureRect,
- };
-
+ var smd = new SpriteMeshData();
+ smd.SetFrame(sprite.textureRect);
+
var meshDataController = new SpriteMeshDataController
{
spriteMeshData = smd
diff --git a/Editor/SkinningModule/SkinningCache/SkinningCachePersistentState.cs b/Editor/SkinningModule/SkinningCache/SkinningCachePersistentState.cs
index de02aaa5..a1690010 100644
--- a/Editor/SkinningModule/SkinningCache/SkinningCachePersistentState.cs
+++ b/Editor/SkinningModule/SkinningCache/SkinningCachePersistentState.cs
@@ -145,6 +145,16 @@ public SkinningCachePersistentState()
m_VertexSelection = new IndexedSelection();
}
+ void OnEnable()
+ {
+ name = GetType().ToString();
+ }
+
+ void OnDisable()
+ {
+ Undo.ClearUndo(this);
+ }
+
public void SetDefault()
{
m_LastUsedTool = Tools.EditPose;
diff --git a/Editor/SkinningModule/SkinningCache/SkinningEvents.cs b/Editor/SkinningModule/SkinningCache/SkinningEvents.cs
index 9a91d53c..8908dced 100644
--- a/Editor/SkinningModule/SkinningCache/SkinningEvents.cs
+++ b/Editor/SkinningModule/SkinningCache/SkinningEvents.cs
@@ -4,21 +4,83 @@ namespace UnityEditor.U2D.Animation
{
internal class SkinningEvents
{
- public class SpriteEvent : UnityEvent {}
- public class SkeletonEvent : UnityEvent {}
- public class MeshEvent : UnityEvent {}
- public class MeshPreviewEvent : UnityEvent {}
- public class SkinningModuleModeEvent : UnityEvent {}
- public class BoneSelectionEvent : UnityEvent {}
- public class BoneEvent : UnityEvent {}
- public class CharacterPartEvent : UnityEvent {}
- public class ToolChangeEvent : UnityEvent {}
- public class RestoreBindPoseEvent : UnityEvent {}
- public class CopyEvent : UnityEvent {}
- public class PasteEvent : UnityEvent {}
- public class ShortcutEvent : UnityEvent {}
- public class BoneVisibilityEvent : UnityEvent {}
- public class MeshPreviewBehaviourChangeEvent : UnityEvent {}
+ // The re-implemented virtual methods in these classes are there so that
+ // they can be mocked in tests.
+ public class SpriteEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class SkeletonEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class MeshEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class MeshPreviewEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class SkinningModuleModeEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class BoneSelectionEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class BoneEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class CharacterPartEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class ToolChangeEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class RestoreBindPoseEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class CopyEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class PasteEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class ShortcutEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class BoneVisibilityEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
+ public class MeshPreviewBehaviourChangeEvent : UnityEvent
+ {
+ public new virtual void AddListener(UnityAction listener) => base.AddListener(listener);
+ public new virtual void RemoveListener(UnityAction listener) => base.RemoveListener(listener);
+ }
SpriteEvent m_SelectedSpriteChanged = new SpriteEvent();
SkeletonEvent m_SkeletonPreviewPoseChanged = new SkeletonEvent();
diff --git a/Editor/SkinningModule/SkinningModule.cs b/Editor/SkinningModule/SkinningModule.cs
index 97986291..541b669b 100644
--- a/Editor/SkinningModule/SkinningModule.cs
+++ b/Editor/SkinningModule/SkinningModule.cs
@@ -25,6 +25,7 @@ private static class Styles
private ModuleToolGroup m_ModuleToolGroup;
IMeshPreviewBehaviour m_MeshPreviewBehaviourOverride = null;
bool m_CollapseToolbar;
+ bool m_HasUnsavedChanges = false;
Texture2D m_WorkspaceBackgroundTexture;
internal SkinningCache skinningCache
@@ -73,6 +74,7 @@ public override void OnModuleActivate()
m_SpriteOutlineRenderer = new SpriteOutlineRenderer(skinningCache.events);
spriteEditor.enableMouseMoveEvent = true;
+ EditorApplication.playModeStateChanged += PlayModeStateChanged;
Undo.undoRedoPerformed += UndoRedoPerformed;
skinningCache.events.skeletonTopologyChanged.AddListener(SkeletonTopologyChanged);
@@ -130,6 +132,7 @@ public override void OnModuleDeactivate()
m_SpriteOutlineRenderer.Dispose();
spriteEditor.enableMouseMoveEvent = false;
+ EditorApplication.playModeStateChanged -= PlayModeStateChanged;
Undo.undoRedoPerformed -= UndoRedoPerformed;
skinningCache.events.skeletonTopologyChanged.RemoveListener(SkeletonTopologyChanged);
@@ -148,9 +151,18 @@ public override void OnModuleDeactivate()
RestoreSpriteEditor();
m_Analytics.Dispose();
m_Analytics = null;
-
+
Cache.Destroy(m_SkinningCache);
}
+
+ void PlayModeStateChanged(PlayModeStateChange newState)
+ {
+ if (newState == PlayModeStateChange.ExitingEditMode && m_HasUnsavedChanges)
+ {
+ var shouldApply = EditorUtility.DisplayDialog(TextContent.savePopupTitle, TextContent.savePopupMessage, TextContent.savePopupOptionYes, TextContent.savePopupOptionNo);
+ spriteEditor.ApplyOrRevertModification(shouldApply);
+ }
+ }
private void UpdateCollapseToolbar()
{
@@ -208,9 +220,10 @@ private void OnPivotChanged()
DataModified();
}
- private void DataModified()
+ void DataModified()
{
spriteEditor.SetDataModified();
+ m_HasUnsavedChanges = true;
}
private void OnViewModeChanged(SkinningMode mode)
@@ -469,10 +482,12 @@ public override bool ApplyRevert(bool apply)
}
else
skinningCache.Revert();
+
+ m_HasUnsavedChanges = false;
return true;
}
- static internal void ApplyChanges(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
+ internal static void ApplyChanges(SkinningCache skinningCache, ISpriteEditorDataProvider dataProvider)
{
skinningCache.applyingChanges = true;
skinningCache.RestoreBindPose();
@@ -530,7 +545,9 @@ static void ApplyMesh(SkinningCache skinningCache, ISpriteEditorDataProvider dat
meshDataProvider.SetVertices(guid, vertices);
meshDataProvider.SetIndices(guid, mesh.indices);
- meshDataProvider.SetEdges(guid, mesh.edges.Select(edge => edge).ToArray());
+
+ var edgeVectArr = EditorUtilities.ToVector2Int(mesh.edges);
+ meshDataProvider.SetEdges(guid, edgeVectArr);
}
}
}
diff --git a/Editor/SkinningModule/SkinningSerializer/ISkinningSerializer.cs b/Editor/SkinningModule/SkinningSerializer/ISkinningSerializer.cs
index 3f1666f2..3f11a074 100644
--- a/Editor/SkinningModule/SkinningSerializer/ISkinningSerializer.cs
+++ b/Editor/SkinningModule/SkinningSerializer/ISkinningSerializer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.U2D.Animation
@@ -26,7 +27,7 @@ internal class SkinningCopySpriteData
public Vector2[] vertices;
public EditableBoneWeight[] vertexWeights;
public int[] indices;
- public Vector2Int[] edges;
+ public int2[] edges;
public List boneWeightGuids;
public List boneWeightNames;
}
diff --git a/Editor/SkinningModule/SpriteMeshData/SpriteMeshData.cs b/Editor/SkinningModule/SpriteMeshData/SpriteMeshData.cs
index 424c8f8c..34404a28 100644
--- a/Editor/SkinningModule/SpriteMeshData/SpriteMeshData.cs
+++ b/Editor/SkinningModule/SpriteMeshData/SpriteMeshData.cs
@@ -1,6 +1,9 @@
using System;
using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Mathematics;
using UnityEngine;
+using UnityEngine.U2D.Animation;
namespace UnityEditor.U2D.Animation
{
@@ -16,71 +19,50 @@ internal class SpriteBoneData
public float length;
}
- internal interface ISpriteMeshData
- {
- Rect frame { get; }
- Vector2[] vertices { get; }
- EditableBoneWeight[] vertexWeights { get; }
- int[] indices { get; set; }
- Vector2Int[] edges { get; set; }
- int vertexCount { get; }
- int boneCount { get; }
- string spriteName { get; }
- void SetVertices(Vector2[] newVertices, EditableBoneWeight[] newWeights);
- void AddVertex(Vector2 position, BoneWeight weight);
- void RemoveVertex(int index);
- SpriteBoneData GetBoneData(int index);
- float GetBoneDepth(int index);
- void Clear();
- }
-
[Serializable]
- internal class SpriteMeshData : ISpriteMeshData
+ internal abstract class BaseSpriteMeshData
{
- [SerializeField]
- List m_Bones = new List();
- [SerializeField]
- Rect m_Frame;
[SerializeField]
Vector2[] m_Vertices = new Vector2[0];
[SerializeField]
EditableBoneWeight[] m_VertexWeights = new EditableBoneWeight[0];
[SerializeField]
int[] m_Indices = new int[0];
- [SerializeField]
- Vector2Int[] m_Edges = new Vector2Int[0];
-
- public Rect frame
- {
- get => m_Frame;
- set => m_Frame = value;
- }
-
+ [SerializeField]
+ int2[] m_Edges = new int2[0];
+ [SerializeField]
+ int2[] m_OutlineEdges = new int2[0];
+
+ public abstract Rect frame { get; }
+
public Vector2[] vertices => m_Vertices;
public EditableBoneWeight[] vertexWeights => m_VertexWeights;
+
+ public int[] indices => m_Indices;
+
+ public int2[] edges => m_Edges;
+ public int2[] outlineEdges => m_OutlineEdges;
+
+ public int vertexCount => m_Vertices.Length;
+ public virtual int boneCount => 0;
+ public virtual string spriteName => "";
- public int[] indices
+ public void SetIndices(int[] newIndices)
{
- get => m_Indices;
- set => m_Indices = value;
+ m_Indices = newIndices;
+ UpdateOutlineEdges();
}
- public Vector2Int[] edges
+ void UpdateOutlineEdges()
{
- get => m_Edges;
- set => m_Edges = value;
+ m_OutlineEdges = MeshUtilities.GetOutlineEdges(m_Indices);
}
-
- public List bones
+
+ public void SetEdges(int2[] newEdges)
{
- get => m_Bones;
- set => m_Bones = value;
+ m_Edges = newEdges;
}
- public string spriteName => "";
- public int vertexCount => m_Vertices.Length;
- public int boneCount => m_Bones.Count;
-
public void SetVertices(Vector2[] newVertices, EditableBoneWeight[] newWeights)
{
m_Vertices = newVertices;
@@ -109,22 +91,51 @@ public void RemoveVertex(int index)
m_VertexWeights = listOfWeights.ToArray();
}
- public SpriteBoneData GetBoneData(int index)
+ public abstract SpriteBoneData GetBoneData(int index);
+
+ public abstract float GetBoneDepth(int index);
+
+ public void Clear()
+ {
+ m_Indices = new int[0];
+ m_Vertices = new Vector2[0];
+ m_VertexWeights = new EditableBoneWeight[0];
+ m_Edges = new int2[0];
+ m_OutlineEdges = new int2[0];
+ }
+ }
+
+ [Serializable]
+ internal class SpriteMeshData : BaseSpriteMeshData
+ {
+ [SerializeField]
+ List m_Bones = new List();
+
+ [SerializeField]
+ Rect m_Frame;
+
+ public override Rect frame => m_Frame;
+ public override int boneCount => m_Bones.Count;
+
+ public List bones
+ {
+ get => m_Bones;
+ set => m_Bones = value;
+ }
+
+ public override SpriteBoneData GetBoneData(int index)
{
return m_Bones[index];
}
- public float GetBoneDepth(int index)
+ public override float GetBoneDepth(int index)
{
return m_Bones[index].depth;
}
- public void Clear()
+ public void SetFrame(Rect newFrame)
{
- m_Indices = new int[0];
- m_Vertices = new Vector2[0];
- m_VertexWeights = new EditableBoneWeight[0];
- m_Edges = new Vector2Int[0];
+ m_Frame = newFrame;
}
}
}
diff --git a/Editor/SkinningModule/SpriteMeshData/SpriteMeshDataController.cs b/Editor/SkinningModule/SpriteMeshData/SpriteMeshDataController.cs
index 00c2458e..74ba648d 100644
--- a/Editor/SkinningModule/SpriteMeshData/SpriteMeshDataController.cs
+++ b/Editor/SkinningModule/SpriteMeshData/SpriteMeshDataController.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Unity.Mathematics;
using UnityEngine;
using UnityEditor.U2D.Sprites;
@@ -20,9 +21,9 @@ public int CompareTo(WeightedTriangle other)
internal class SpriteMeshDataController
{
- public ISpriteMeshData spriteMeshData;
- List m_VerticesTemp = new List();
- List m_EdgesTemp = new List();
+ public BaseSpriteMeshData spriteMeshData;
+ float2[] m_VerticesTemp = new float2[0];
+ int2[] m_EdgesTemp = new int2[0];
public void CreateVertex(Vector2 position)
{
@@ -53,14 +54,14 @@ public void CreateEdge(int index1, int index2)
Debug.Assert(index2 < spriteMeshData.vertexCount, $"Assert failed. Expected: index2 < spriteMeshData.vertexCount. Actual: index2 == {index2} spriteMeshData.vertexCount == {spriteMeshData.vertexCount}");
Debug.Assert(index1 != index2, $"Assert failed. Expected: index1 != index2. Actual: index1 == {index1} index2 == {index2}");
- var newEdge = new Vector2Int(index1, index2);
+ var newEdge = new int2(index1, index2);
if (!spriteMeshData.edges.ContainsAny(newEdge))
{
- var listOfEdges = new List(spriteMeshData.edges)
+ var listOfEdges = new List(spriteMeshData.edges)
{
newEdge
};
- spriteMeshData.edges = listOfEdges.ToArray();
+ spriteMeshData.SetEdges(listOfEdges.ToArray());
}
}
@@ -69,8 +70,7 @@ public void RemoveVertex(int index)
Debug.Assert(spriteMeshData != null);
//We need to delete the edges that reference the index
- List edgesWithIndex;
- if (FindEdgesContainsIndex(index, out edgesWithIndex))
+ if (FindEdgesContainsIndex(index, out var edgesWithIndex))
{
//If there are 2 edges referencing the same index we are removing, we can create a new one that connects the endpoints ("Unsplit").
if (edgesWithIndex.Count == 2)
@@ -122,21 +122,21 @@ public void RemoveVertex(IEnumerable indices)
}
}
- void RemoveEdge(Vector2Int edge)
+ void RemoveEdge(int2 edge)
{
Debug.Assert(spriteMeshData != null);
- var listOfEdges = new List(spriteMeshData.edges);
+ var listOfEdges = new List(spriteMeshData.edges);
listOfEdges.Remove(edge);
- spriteMeshData.edges = listOfEdges.ToArray();
+ spriteMeshData.SetEdges(listOfEdges.ToArray());
}
- bool FindEdgesContainsIndex(int index, out List result)
+ bool FindEdgesContainsIndex(int index, out List result)
{
Debug.Assert(spriteMeshData != null);
bool found = false;
- result = new List();
+ result = new List();
for (int i = 0; i < spriteMeshData.edges.Length; ++i)
{
@@ -182,27 +182,24 @@ public void Triangulate(ITriangulator triangulator)
Debug.Assert(spriteMeshData != null);
Debug.Assert(triangulator != null);
- FillMeshDataContainers(ref m_VerticesTemp, ref m_EdgesTemp, out var weightData, out var hasWeightData);
+ FillMeshDataContainers(out m_VerticesTemp, out m_EdgesTemp, out var weightData, out var hasWeightData);
+ triangulator.Triangulate(ref m_EdgesTemp, ref m_VerticesTemp, out var indices);
- var indices = new List();
- triangulator.Triangulate(m_VerticesTemp, m_EdgesTemp, indices);
-
- if (m_VerticesTemp.Count == 0 || indices.Count == 0)
+ if (m_VerticesTemp.Length == 0 || indices.Length == 0)
{
spriteMeshData.Clear();
CreateQuad();
- FillMeshDataContainers(ref m_VerticesTemp, ref m_EdgesTemp, out weightData, out hasWeightData);
- indices.Clear();
- triangulator.Triangulate(m_VerticesTemp, m_EdgesTemp, indices);
+ FillMeshDataContainers(out m_VerticesTemp, out m_EdgesTemp, out weightData, out hasWeightData);
+ triangulator.Triangulate(ref m_EdgesTemp, ref m_VerticesTemp, out indices);
}
spriteMeshData.Clear();
- spriteMeshData.edges = m_EdgesTemp.ToArray();
- spriteMeshData.indices = indices.ToArray();
+ spriteMeshData.SetIndices(indices);
+ spriteMeshData.SetEdges(m_EdgesTemp);
- var hasNewVertices = m_VerticesTemp.Count != weightData.Count;
- for (var i = 0; i < m_VerticesTemp.Count; ++i)
+ var hasNewVertices = m_VerticesTemp.Length != weightData.Length;
+ for (var i = 0; i < m_VerticesTemp.Length; ++i)
{
var boneWeight = default(BoneWeight);
if (!hasNewVertices)
@@ -214,22 +211,16 @@ public void Triangulate(ITriangulator triangulator)
CalculateWeights(new BoundedBiharmonicWeightsGenerator(), null, 0.01f);
}
- void FillMeshDataContainers(ref List vertices, ref List edges, out List weightData, out bool hasWeightData)
+ void FillMeshDataContainers(out float2[] vertices, out int2[] edges, out EditableBoneWeight[] weightData, out bool hasWeightData)
{
- edges.Clear();
- vertices.Clear();
-
- edges.AddRange(spriteMeshData.edges);
-
- weightData = new List(spriteMeshData.vertexCount);
- for (var i = 0; i < spriteMeshData.vertexCount; ++i)
- {
- vertices.Add(spriteMeshData.vertices[i]);
- weightData.Add(spriteMeshData.vertexWeights[i]);
- }
-
+ edges = spriteMeshData.edges;
+ vertices = EditorUtilities.ToFloat2(spriteMeshData.vertices);
+
+ weightData = new EditableBoneWeight[spriteMeshData.vertexWeights.Length];
+ Array.Copy(spriteMeshData.vertexWeights, weightData, weightData.Length);
+
hasWeightData = false;
- if (weightData.Count > 0 && weightData[0] != default)
+ if (weightData.Length > 0 && weightData[0] != default)
hasWeightData = true;
}
@@ -238,25 +229,20 @@ public void Subdivide(ITriangulator triangulator, float largestAreaFactor, float
Debug.Assert(spriteMeshData != null);
Debug.Assert(triangulator != null);
- m_VerticesTemp.Clear();
- m_EdgesTemp.Clear();
- m_EdgesTemp.AddRange(spriteMeshData.edges);
+ m_EdgesTemp = spriteMeshData.edges;
+ m_VerticesTemp = EditorUtilities.ToFloat2(spriteMeshData.vertices);
- for (int i = 0; i < spriteMeshData.vertexCount; ++i)
- m_VerticesTemp.Add(spriteMeshData.vertices[i]);
-
try
{
- var indices = new List();
- triangulator.Tessellate(0f, 0f, 0f, largestAreaFactor, areaThreshold, 100, m_VerticesTemp, m_EdgesTemp, indices);
+ triangulator.Tessellate(0f, 0f, 0f, largestAreaFactor, areaThreshold, 100, ref m_VerticesTemp, ref m_EdgesTemp, out var indices);
spriteMeshData.Clear();
- for (var i = 0; i < m_VerticesTemp.Count; ++i)
+ for (var i = 0; i < m_VerticesTemp.Length; ++i)
spriteMeshData.AddVertex(m_VerticesTemp[i], default(BoneWeight));
-
- spriteMeshData.edges = m_EdgesTemp.ToArray();
- spriteMeshData.indices = indices.ToArray();
+
+ spriteMeshData.SetIndices(indices);
+ spriteMeshData.SetEdges(m_EdgesTemp);
}
catch (Exception) { }
}
@@ -279,20 +265,22 @@ public void OutlineFromAlpha(IOutlineGenerator outlineGenerator, ITextureDataPro
int width, height;
textureDataProvider.GetTextureActualWidthAndHeight(out width, out height);
- Vector2 scale = new Vector2(textureDataProvider.texture.width / (float)width, textureDataProvider.texture.height / (float)height);
- Vector2 scaleInv = new Vector2(1f / scale.x, 1f / scale.y);
- Vector2 rectOffset = spriteMeshData.frame.size * 0.5f;
+ var scale = new Vector2(textureDataProvider.texture.width / (float)width, textureDataProvider.texture.height / (float)height);
+ var scaleInv = new Vector2(1f / scale.x, 1f / scale.y);
+ var rectOffset = spriteMeshData.frame.size * 0.5f;
- Rect scaledRect = spriteMeshData.frame;
+ var scaledRect = spriteMeshData.frame;
scaledRect.min = Vector2.Scale(scaledRect.min, scale);
scaledRect.max = Vector2.Scale(scaledRect.max, scale);
spriteMeshData.Clear();
- Vector2[][] paths;
- outlineGenerator.GenerateOutline(textureDataProvider, scaledRect, outlineDetail, alphaTolerance, false, out paths);
+ outlineGenerator.GenerateOutline(textureDataProvider, scaledRect, outlineDetail, alphaTolerance, false, out var paths);
var vertexIndexBase = 0;
+
+ var vertices = new List(spriteMeshData.vertices);
+ var edges = new List(spriteMeshData.edges);
for (var i = 0; i < paths.Length; ++i)
{
var numPathVertices = paths[i].Length;
@@ -300,18 +288,20 @@ public void OutlineFromAlpha(IOutlineGenerator outlineGenerator, ITextureDataPro
for (var j = 0; j <= numPathVertices; j++)
{
if (j < numPathVertices)
- spriteMeshData.AddVertex(Vector2.Scale(paths[i][j], scaleInv) + rectOffset, default(BoneWeight));
-
+ vertices.Add(Vector2.Scale(paths[i][j], scaleInv) + rectOffset);
if (j > 0)
- {
- var listOfEdges = new List(spriteMeshData.edges);
- listOfEdges.Add(new Vector2Int(vertexIndexBase + j - 1, vertexIndexBase + j % numPathVertices));
- spriteMeshData.edges = listOfEdges.ToArray();
- }
+ edges.Add(new int2(vertexIndexBase + j - 1, vertexIndexBase + j % numPathVertices));
}
vertexIndexBase += numPathVertices;
}
+
+ var vertexWeights = new EditableBoneWeight[vertices.Count];
+ for (var i = 0; i < vertexWeights.Length; ++i)
+ vertexWeights[i] = new EditableBoneWeight();
+
+ spriteMeshData.SetVertices(vertices.ToArray(), vertexWeights);
+ spriteMeshData.SetEdges(edges.ToArray());
}
public void NormalizeWeights(ISelection selection)
@@ -328,12 +318,12 @@ public void CalculateWeights(IWeightsGenerator weightsGenerator, ISelection
Debug.Assert(spriteMeshData != null);
GetControlPoints(out var controlPoints, out var bones, out var pins);
+
+ var vertices = EditorUtilities.ToFloat2(spriteMeshData.vertices);
+ var indices = spriteMeshData.indices;
+ var edges = spriteMeshData.edges;
- var vertices = new Vector2[spriteMeshData.vertexCount];
- for (var i = 0; i < spriteMeshData.vertexCount; ++i)
- vertices[i] = spriteMeshData.vertices[i];
-
- var boneWeights = weightsGenerator.Calculate(spriteMeshData.spriteName, vertices, spriteMeshData.indices, spriteMeshData.edges, controlPoints, bones, pins);
+ var boneWeights = weightsGenerator.Calculate(spriteMeshData.spriteName, in vertices, in indices, in edges, in controlPoints, in bones, in pins);
Debug.Assert(boneWeights.Length == spriteMeshData.vertexCount);
@@ -376,9 +366,8 @@ public void SmoothWeights(int iterations, ISelection selection)
for (var i = 0; i < spriteMeshData.vertexCount; i++)
boneWeights[i] = spriteMeshData.vertexWeights[i].ToBoneWeight(false);
-
- BoneWeight[] smoothedWeights;
- SmoothingUtility.SmoothWeights(boneWeights, spriteMeshData.indices, spriteMeshData.boneCount, iterations, out smoothedWeights);
+
+ SmoothingUtility.SmoothWeights(boneWeights, spriteMeshData.indices, spriteMeshData.boneCount, iterations, out var smoothedWeights);
for (var i = 0; i < spriteMeshData.vertexCount; i++)
if (selection == null || (selection.Count == 0 || selection.Contains(i)))
@@ -441,12 +430,12 @@ public void SortTrianglesByDepth()
m_VertexOrderList.Add(vertexOrder);
}
- for (int i = 0; i < spriteMeshData.indices.Length; i += 3)
+ for (var i = 0; i < spriteMeshData.indices.Length; i += 3)
{
- int p1 = spriteMeshData.indices[i];
- int p2 = spriteMeshData.indices[i + 1];
- int p3 = spriteMeshData.indices[i + 2];
- float weight = (m_VertexOrderList[p1] + m_VertexOrderList[p2] + m_VertexOrderList[p3]) / 3f;
+ var p1 = spriteMeshData.indices[i];
+ var p2 = spriteMeshData.indices[i + 1];
+ var p3 = spriteMeshData.indices[i + 2];
+ var weight = (m_VertexOrderList[p1] + m_VertexOrderList[p2] + m_VertexOrderList[p3]) / 3f;
m_WeightedTriangles.Add(new WeightedTriangle() { p1 = p1, p2 = p2, p3 = p3, weight = weight });
}
@@ -462,7 +451,7 @@ public void SortTrianglesByDepth()
newIndices[indexCount + 1] = triangle.p2;
newIndices[indexCount + 2] = triangle.p3;
}
- spriteMeshData.indices = newIndices;
+ spriteMeshData.SetIndices(newIndices);
}
public void GetMultiEditChannelData(ISelection selection, int channel,
@@ -484,7 +473,7 @@ public void GetMultiEditChannelData(ISelection selection, int channel,
var indices = selection.elements;
- foreach (int i in indices)
+ foreach (var i in indices)
{
var editableBoneWeight = spriteMeshData.vertexWeights[i];
@@ -551,7 +540,7 @@ public void SetMultiEditChannelData(ISelection selection, int channel,
}
}
- public void GetControlPoints(out Vector2[] points, out Vector2Int[] edges, out int[] pins)
+ public void GetControlPoints(out float2[] points, out int2[] edges, out int[] pins)
{
Debug.Assert(spriteMeshData != null);
@@ -559,7 +548,7 @@ public void GetControlPoints(out Vector2[] points, out Vector2Int[] edges, out i
edges = null;
var pointList = new List();
- var edgeList = new List();
+ var edgeList = new List();
var pinList = new List();
var bones = new List(spriteMeshData.boneCount);
@@ -587,7 +576,7 @@ public void GetControlPoints(out Vector2[] points, out Vector2Int[] edges, out i
index2 = pointList.Count - 1;
}
- edgeList.Add(new Vector2Int(index1, index2));
+ edgeList.Add(new int2(index1, index2));
}
else if (bone.length == 0f)
{
@@ -596,7 +585,10 @@ public void GetControlPoints(out Vector2[] points, out Vector2Int[] edges, out i
}
}
- points = pointList.ToArray();
+ points = new float2[pointList.Count];
+ for (var i = 0; i < pointList.Count; ++i)
+ points[i] = pointList[i];
+
edges = edgeList.ToArray();
pins = pinList.ToArray();
}
@@ -604,7 +596,6 @@ public void GetControlPoints(out Vector2[] points, out Vector2Int[] edges, out i
static int FindPoint(IReadOnlyList points, Vector2 point, float distanceTolerance)
{
var sqrTolerance = distanceTolerance * distanceTolerance;
-
for (var i = 0; i < points.Count; ++i)
{
if ((points[i] - point).sqrMagnitude <= sqrTolerance)
diff --git a/Editor/SkinningModule/SpriteMeshData/WeightEditor.cs b/Editor/SkinningModule/SpriteMeshData/WeightEditor.cs
index ca5a2f68..1f424138 100644
--- a/Editor/SkinningModule/SpriteMeshData/WeightEditor.cs
+++ b/Editor/SkinningModule/SpriteMeshData/WeightEditor.cs
@@ -13,7 +13,7 @@ internal enum WeightEditorMode
internal class WeightEditor
{
- public ISpriteMeshData spriteMeshData
+ public BaseSpriteMeshData spriteMeshData
{
get => m_SpriteMeshDataController.spriteMeshData;
set => m_SpriteMeshDataController.spriteMeshData = value;
diff --git a/Editor/SkinningModule/TextContent.cs b/Editor/SkinningModule/TextContent.cs
index f2ee6525..aa590f92 100644
--- a/Editor/SkinningModule/TextContent.cs
+++ b/Editor/SkinningModule/TextContent.cs
@@ -88,11 +88,7 @@ internal static class TextContent
public static string spriteLabelColumnEmpty = L10n.Tr("To start creating labels drag and drop Sprites or Sprite Texture or select the '+' button.");
public static string spriteCategoryMultiSelect = L10n.Tr("Multiple Categories selected.");
public static string spriteCategoryNoSelection = L10n.Tr("No Categories selected.");
- public static string spriteLibrarySaveTitle = L10n.Tr("Unsaved changes");
- public static string spriteLibrarySaveMessage = L10n.Tr("There are some unsaved changes, would you like to save them?");
public static string spriteLibraryRevertMessage = L10n.Tr("There are some unsaved changes, are you sure you want to revert them?");
- public static string spriteLibrarySaveYes = L10n.Tr("Yes");
- public static string spriteLibrarySaveNo = L10n.Tr("No");
public static string spriteLibraryMainLibraryTooltip = L10n.Tr("This field is optional. By linking a Main Library, this Sprite Library becomes a Variant of the Main Library allowing it to reference all the Main Library’s Categories.");
public static string spriteLibraryCategoriesTooltip = L10n.Tr("A container to organize the Labels. The Category must be unique from other Categories in the same Sprite Library or Sprite Library hierarchy.");
public static string spriteLibraryLabelsTooltip = L10n.Tr("Label contains a Sprite reference. Name has to be unique.");
@@ -124,6 +120,10 @@ internal static class TextContent
public static string spriteLibraryCreateMessage = L10n.Tr("Create a new Sprite Library Asset");
// Other
+ public static string savePopupTitle = L10n.Tr("Unsaved changes");
+ public static string savePopupMessage = L10n.Tr("There are some unsaved changes, would you like to save them?");
+ public static string savePopupOptionYes = L10n.Tr("Yes");
+ public static string savePopupOptionNo = L10n.Tr("No");
public static string generatingOutline = L10n.Tr("Generating Outline");
public static string triangulatingGeometry = L10n.Tr("Triangulating Geometry");
public static string subdividingGeometry = L10n.Tr("Subdividing Geometry");
diff --git a/Editor/SkinningModule/Triangulation/ITriangulator.cs b/Editor/SkinningModule/Triangulation/ITriangulator.cs
index 92e1c4ff..0f786596 100644
--- a/Editor/SkinningModule/Triangulation/ITriangulator.cs
+++ b/Editor/SkinningModule/Triangulation/ITriangulator.cs
@@ -1,11 +1,10 @@
-using System.Collections.Generic;
-using UnityEngine;
+using Unity.Mathematics;
namespace UnityEditor.U2D.Animation
{
internal interface ITriangulator
{
- void Triangulate(IList vertices, IList edges, IList indices);
- void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float areaThreshold, int smoothIterations, IList vertices, IList edges, IList indices);
+ void Triangulate(ref int2[] edges, ref float2[] vertices, out int[] indices);
+ void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float areaThreshold, int smoothIterations, ref float2[] vertices, ref int2[] edges, out int[] indices);
}
}
diff --git a/Editor/SkinningModule/Triangulation/TriangulationUtility.cs b/Editor/SkinningModule/Triangulation/TriangulationUtility.cs
index df360fc2..c1477345 100644
--- a/Editor/SkinningModule/Triangulation/TriangulationUtility.cs
+++ b/Editor/SkinningModule/Triangulation/TriangulationUtility.cs
@@ -17,7 +17,7 @@ internal class TriangulationUtility
static readonly float k_CollinearTolerance = 0.0001f;
[BurstCompile]
- private static unsafe int ValidateCollinear(float2* points, int pointCount, float epsilon)
+ static unsafe int ValidateCollinear(float2* points, int pointCount, float epsilon)
{
if (pointCount < 3)
return 0;
@@ -38,34 +38,34 @@ private static unsafe int ValidateCollinear(float2* points, int pointCount, floa
}
[BurstCompile]
- private static unsafe void TessellateBurst(Allocator allocator, float2* points, int pointCount, int2* edges, int edgeCount, float2* outVertices, int* outIndices, int2* outEdges, int arrayCount, int3* result)
+ static unsafe void TessellateBurst(Allocator allocator, float2* points, int pointCount, int2* edges, int edgeCount, float2* outVertices, int* outIndices, int2* outEdges, int arrayCount, int3* result)
{
- NativeArray _edges = new NativeArray(edgeCount, allocator);
+ var _edges = new NativeArray(edgeCount, allocator);
for (int i = 0; i < _edges.Length; ++i)
_edges[i] = edges[i];
- NativeArray _points = new NativeArray(pointCount, allocator);
+ var _points = new NativeArray(pointCount, allocator);
for (int i = 0; i < _points.Length; ++i)
_points[i] = points[i];
- NativeArray _outIndices = new NativeArray(arrayCount, allocator);
- NativeArray _outEdges = new NativeArray(arrayCount, allocator);
- NativeArray _outVertices = new NativeArray(arrayCount, allocator);
+ var _outIndices = new NativeArray(arrayCount, allocator);
+ var _outEdges = new NativeArray(arrayCount, allocator);
+ var _outVertices = new NativeArray(arrayCount, allocator);
- int outEdgeCount = 0;
- int outIndexCount = 0;
- int outVertexCount = 0;
+ var outEdgeCount = 0;
+ var outIndexCount = 0;
+ var outVertexCount = 0;
var check = ValidateCollinear((float2*)_points.GetUnsafeReadOnlyPtr(), pointCount, k_CollinearTolerance);
if (0 != check)
- ModuleHandle.Tessellate(allocator, _points, _edges, ref _outVertices, ref outVertexCount, ref _outIndices, ref outIndexCount, ref _outEdges, ref outEdgeCount);
+ ModuleHandle.Tessellate(allocator, in _points, in _edges, ref _outVertices, out outVertexCount, ref _outIndices, out outIndexCount, ref _outEdges, out outEdgeCount);
- for (int i = 0; i < outEdgeCount; ++i)
+ for (var i = 0; i < outEdgeCount; ++i)
outEdges[i] = _outEdges[i];
- for (int i = 0; i < outIndexCount; ++i)
+ for (var i = 0; i < outIndexCount; ++i)
outIndices[i] = _outIndices[i];
- for (int i = 0; i < outVertexCount; ++i)
+ for (var i = 0; i < outVertexCount; ++i)
outVertices[i] = _outVertices[i];
result->x = outVertexCount;
@@ -81,30 +81,30 @@ private static unsafe void TessellateBurst(Allocator allocator, float2* points,
}
[BurstCompile]
- private static unsafe void SubdivideBurst(Allocator allocator, float2* points, int pointCount, int2* edges, int edgeCount, float2* outVertices, int* outIndices, int2* outEdges, int arrayCount, float areaFactor, float areaThreshold, int refineIterations, int smoothenIterations, int3* result)
+ static unsafe void SubdivideBurst(Allocator allocator, float2* points, int pointCount, int2* edges, int edgeCount, float2* outVertices, int* outIndices, int2* outEdges, int arrayCount, float areaFactor, float areaThreshold, int refineIterations, int smoothenIterations, int3* result)
{
- NativeArray _edges = new NativeArray(edgeCount, allocator);
+ var _edges = new NativeArray(edgeCount, allocator);
for (int i = 0; i < _edges.Length; ++i)
_edges[i] = edges[i];
- NativeArray _points = new NativeArray(pointCount, allocator);
+ var _points = new NativeArray(pointCount, allocator);
for (int i = 0; i < _points.Length; ++i)
_points[i] = points[i];
- NativeArray _outIndices = new NativeArray(arrayCount, allocator);
- NativeArray _outEdges = new NativeArray(arrayCount, allocator);
- NativeArray _outVertices = new NativeArray(arrayCount, allocator);
- int outEdgeCount = 0;
- int outIndexCount = 0;
- int outVertexCount = 0;
+ var _outIndices = new NativeArray(arrayCount, allocator);
+ var _outEdges = new NativeArray(arrayCount, allocator);
+ var _outVertices = new NativeArray(arrayCount, allocator);
+ var outEdgeCount = 0;
+ var outIndexCount = 0;
+ var outVertexCount = 0;
ModuleHandle.Subdivide(allocator, _points, _edges, ref _outVertices, ref outVertexCount, ref _outIndices, ref outIndexCount, ref _outEdges, ref outEdgeCount, areaFactor, areaThreshold, refineIterations, smoothenIterations);
- for (int i = 0; i < outEdgeCount; ++i)
+ for (var i = 0; i < outEdgeCount; ++i)
outEdges[i] = _outEdges[i];
- for (int i = 0; i < outIndexCount; ++i)
+ for (var i = 0; i < outIndexCount; ++i)
outIndices[i] = _outIndices[i];
- for (int i = 0; i < outVertexCount; ++i)
+ for (var i = 0; i < outVertexCount; ++i)
outVertices[i] = _outVertices[i];
result->x = outVertexCount;
@@ -118,7 +118,7 @@ private static unsafe void SubdivideBurst(Allocator allocator, float2* points, i
_edges.Dispose();
}
- private static bool TessellateSafe(NativeArray points, NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount)
+ static bool TessellateSafe(in NativeArray points, in NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount)
{
unsafe
{
@@ -129,7 +129,7 @@ private static bool TessellateSafe(NativeArray points, NativeArray
try
{
- ModuleHandle.Tessellate(Allocator.Persistent, points, edges, ref outVertices, ref outVertexCount, ref outIndices, ref outIndexCount, ref outEdges, ref outEdgeCount);
+ ModuleHandle.Tessellate(Allocator.Persistent, in points, in edges, ref outVertices, out outVertexCount, ref outIndices, out outIndexCount, ref outEdges, out outEdgeCount);
}
catch (Exception)
{
@@ -137,7 +137,7 @@ private static bool TessellateSafe(NativeArray points, NativeArray
}
return true;
}
- private static bool SubdivideSafe(NativeArray points, NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount, float areaFactor, float areaThreshold, int refineIterations, int smoothenIterations)
+ static bool SubdivideSafe(NativeArray points, NativeArray edges, ref NativeArray outVertices, ref int outVertexCount, ref NativeArray outIndices, ref int outIndexCount, ref NativeArray outEdges, ref int outEdgeCount, float areaFactor, float areaThreshold, int refineIterations, int smoothenIterations)
{
try
{
@@ -155,8 +155,8 @@ internal static void Quad(IList vertices, IList edges, ILis
if (vertices.Count < 3)
return;
- NativeArray points = new NativeArray(vertices.Count, allocator);
- for (int i = 0; i < vertices.Count; ++i)
+ var points = new NativeArray(vertices.Count, allocator);
+ for (var i = 0; i < vertices.Count; ++i)
points[i] = vertices[i];
var arrayCount = vertices.Count * vertices.Count * 4;
@@ -166,7 +166,7 @@ internal static void Quad(IList vertices, IList edges, ILis
var outputVertices = new NativeArray(arrayCount, allocator);
var fallback = new NativeArray(0, allocator);
- TessellateSafe(points, fallback, ref outputVertices, ref vertexCount, ref outputIndices,
+ TessellateSafe(in points, in fallback, ref outputVertices, ref vertexCount, ref outputIndices,
ref indexCount, ref outputEdges, ref edgeCount);
fallback.Dispose();
@@ -186,19 +186,18 @@ internal static void Quad(IList vertices, IList edges, ILis
points.Dispose();
}
- internal static void Triangulate(IList vertices, IList edges, IList indices, Allocator allocator)
+ internal static void Triangulate(ref int2[] edges, ref float2[] vertices, out int[] indices, Allocator allocator)
{
- if (vertices.Count < 3)
+ if (vertices.Length < 3)
+ {
+ indices = new int[0];
return;
-
- var points = new NativeArray(vertices.Count, allocator);
- for (var i = 0; i < vertices.Count; ++i)
- points[i] = vertices[i];
- var inputEdges = new NativeArray(edges.Count, allocator);
- for (var i = 0; i < edges.Count; ++i)
- inputEdges[i] = new int2(edges[i].x, edges[i].y);
+ }
- var arrayCount = vertices.Count * vertices.Count * 4;
+ var points = new NativeArray(vertices, allocator);
+ var inputEdges = new NativeArray(edges, allocator);
+
+ var arrayCount = vertices.Length * vertices.Length * 4;
int vertexCount = 0, indexCount = 0, edgeCount = 0;
var outputIndices = new NativeArray(arrayCount, allocator);
var outputEdges = new NativeArray(arrayCount, allocator);
@@ -214,17 +213,17 @@ internal static void Triangulate(IList vertices, IList edge
}
// Fallback on numerical precision errors.
if (vertexCount <= 8 || indexCount == 0)
- TessellateSafe(points, inputEdges, ref outputVertices, ref vertexCount, ref outputIndices, ref indexCount, ref outputEdges, ref edgeCount);
-
- vertices.Clear();
- for (int i = 0; i < vertexCount; ++i)
- vertices.Add(outputVertices[i]);
- indices.Clear();
- for (int i = 0; i < indexCount; ++i)
- indices.Add(outputIndices[i]);
- edges.Clear();
- for (int i = 0; i < edgeCount; ++i)
- edges.Add(new Vector2Int(outputEdges[i].x, outputEdges[i].y));
+ TessellateSafe(in points, in inputEdges, ref outputVertices, ref vertexCount, ref outputIndices, ref indexCount, ref outputEdges, ref edgeCount);
+
+ vertices = new float2[vertexCount];
+ for (var i = 0; i < vertexCount; ++i)
+ vertices[i] = outputVertices[i];
+ indices = new int[indexCount];
+ for (var i = 0; i < indexCount; ++i)
+ indices[i] = outputIndices[i];
+ edges = new int2[edgeCount];
+ for (var i = 0; i < edgeCount; ++i)
+ edges[i] = outputEdges[i];
outputEdges.Dispose();
outputResult.Dispose();
@@ -234,36 +233,34 @@ internal static void Triangulate(IList vertices, IList edge
points.Dispose();
}
- internal static bool TriangulateSafe(IList vertices, IList edges, IList indices)
+ internal static bool TriangulateSafe(ref float2[] vertices, ref int2[] edges, out int[] indices)
{
- if (vertices.Count < 3)
+ indices = new int[0];
+
+ if (vertices.Length < 3)
return false;
- var points = new NativeArray(vertices.Count, Allocator.Persistent);
- for (var i = 0; i < vertices.Count; ++i)
- points[i] = vertices[i];
- var inputEdges = new NativeArray(edges.Count, Allocator.Persistent);
- for (var i = 0; i < edges.Count; ++i)
- inputEdges[i] = new int2(edges[i].x, edges[i].y);
-
- var arrayCount = vertices.Count * vertices.Count * 4;
+ var points = new NativeArray(vertices, Allocator.Persistent);
+ var inputEdges = new NativeArray(edges, Allocator.Persistent);
+
+ var arrayCount = vertices.Length * vertices.Length * 4;
int vertexCount = 0, indexCount = 0, edgeCount = 0;
var outputIndices = new NativeArray(arrayCount, Allocator.Persistent);
var outputEdges = new NativeArray(arrayCount, Allocator.Persistent);
var outputVertices = new NativeArray(arrayCount, Allocator.Persistent);
- var ok = TessellateSafe(points, inputEdges, ref outputVertices, ref vertexCount, ref outputIndices, ref indexCount, ref outputEdges, ref edgeCount);
+ var ok = TessellateSafe(in points, in inputEdges, ref outputVertices, ref vertexCount, ref outputIndices, ref indexCount, ref outputEdges, ref edgeCount);
if (ok)
{
- vertices.Clear();
+ vertices = new float2[vertexCount];
for (var i = 0; i < vertexCount; ++i)
- vertices.Add(outputVertices[i]);
- indices.Clear();
- for (var i = 0; i < indexCount; ++i)
- indices.Add(outputIndices[i]);
- edges.Clear();
+ vertices[i] = outputVertices[i];
+ edges = new int2[edgeCount];
for (var i = 0; i < edgeCount; ++i)
- edges.Add(new Vector2Int(outputEdges[i].x, outputEdges[i].y));
+ edges[i] = outputEdges[i];
+ indices = new int[indexCount];
+ for (var i = 0; i < indexCount; ++i)
+ indices[i] = outputIndices[i];
}
outputEdges.Dispose();
@@ -274,26 +271,28 @@ internal static bool TriangulateSafe(IList vertices, IList
return ok;
}
- public static void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float targetArea, int refineIterations, int smoothenIterations, IList vertices, IList edges, IList indices, Allocator allocator)
+ public static void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float targetArea, int refineIterations, int smoothenIterations, ref float2[] vertices, ref int2[] edges, out int[] indices, Allocator allocator)
{
- if (vertices.Count < 3)
+ indices = new int[0];
+
+ if (vertices.Length < 3)
return;
largestTriangleAreaFactor = Mathf.Clamp01(largestTriangleAreaFactor);
- var points = new NativeArray(vertices.Count, allocator);
- for (var i = 0; i < vertices.Count; ++i)
+ var points = new NativeArray(vertices.Length, allocator);
+ for (var i = 0; i < vertices.Length; ++i)
points[i] = vertices[i];
- NativeArray inputEdges = new NativeArray(edges.Count, allocator);
- for (int i = 0; i < edges.Count; ++i)
+ var inputEdges = new NativeArray(edges.Length, allocator);
+ for (var i = 0; i < edges.Length; ++i)
inputEdges[i] = new int2(edges[i].x, edges[i].y);
- int maxDataCount = 65536;
+ const int maxDataCount = 65536;
int vertexCount = 0, indexCount = 0, edgeCount = 0;
- NativeArray outputIndices = new NativeArray(maxDataCount, allocator);
- NativeArray outputEdges = new NativeArray(maxDataCount, allocator);
- NativeArray outputResult = new NativeArray(1, allocator);
- NativeArray outputVertices = new NativeArray(maxDataCount, allocator);
+ var outputIndices = new NativeArray(maxDataCount, allocator);
+ var outputEdges = new NativeArray(maxDataCount, allocator);
+ var outputResult = new NativeArray(1, allocator);
+ var outputVertices = new NativeArray(maxDataCount, allocator);
unsafe
{
@@ -306,15 +305,15 @@ public static void Tessellate(float minAngle, float maxAngle, float meshAreaFact
if (vertexCount <= 8)
SubdivideSafe(points, inputEdges, ref outputVertices, ref vertexCount, ref outputIndices, ref indexCount, ref outputEdges, ref edgeCount, largestTriangleAreaFactor, targetArea, refineIterations, smoothenIterations);
- vertices.Clear();
+ vertices = new float2[vertexCount];
for (var i = 0; i < vertexCount; ++i)
- vertices.Add(outputVertices[i]);
- indices.Clear();
- for (var i = 0; i < indexCount; ++i)
- indices.Add(outputIndices[i]);
- edges.Clear();
+ vertices[i] = outputVertices[i];
+ edges = new int2[edgeCount];
for (var i = 0; i < edgeCount; ++i)
- edges.Add(new Vector2Int(outputEdges[i].x, outputEdges[i].y));
+ edges[i] = outputEdges[i];
+ indices = new int[indexCount];
+ for (var i = 0; i < indexCount; ++i)
+ indices[i] = outputIndices[i];
outputEdges.Dispose();
outputResult.Dispose();
@@ -324,20 +323,18 @@ public static void Tessellate(float minAngle, float maxAngle, float meshAreaFact
points.Dispose();
}
- public static void TessellateSafe(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float targetArea, int refineIterations, int smoothenIterations, IList vertices, IList edges, IList indices)
+ public static void TessellateSafe(float largestTriangleAreaFactor, float targetArea, int refineIterations, int smoothenIterations, ref float2[] vertices, ref int2[] edges, out int[] indices)
{
- if (vertices.Count < 3)
+ indices = new int[0];
+
+ if (vertices.Length < 3)
return;
largestTriangleAreaFactor = Mathf.Clamp01(largestTriangleAreaFactor);
- var points = new NativeArray(vertices.Count, Allocator.Persistent);
- for (var i = 0; i < vertices.Count; ++i)
- points[i] = vertices[i];
- var inputEdges = new NativeArray(edges.Count, Allocator.Persistent);
- for (var i = 0; i < edges.Count; ++i)
- inputEdges[i] = new int2(edges[i].x, edges[i].y);
-
+ var points = new NativeArray(vertices, Allocator.Persistent);
+ var inputEdges = new NativeArray(edges, Allocator.Persistent);
+
int vertexCount = 0, indexCount = 0, edgeCount = 0, maxDataCount = 65536;
var outputVertices = new NativeArray(maxDataCount, Allocator.Persistent);
var outputIndices = new NativeArray(maxDataCount, Allocator.Persistent);
@@ -346,15 +343,15 @@ public static void TessellateSafe(float minAngle, float maxAngle, float meshArea
if (ok)
{
- vertices.Clear();
- for (var i = 0; i < vertexCount; ++i)
- vertices.Add(outputVertices[i]);
- indices.Clear();
- for (var i = 0; i < indexCount; ++i)
- indices.Add(outputIndices[i]);
- edges.Clear();
- for (var i = 0; i < edgeCount; ++i)
- edges.Add(new Vector2Int(outputEdges[i].x, outputEdges[i].y));
+ vertices = new float2[vertexCount];
+ for (var i = 0; i < vertices.Length; ++i)
+ vertices[i] = outputVertices[i];
+ indices = new int[indexCount];
+ for (var i = 0; i < indices.Length; ++i)
+ indices[i] = outputIndices[i];
+ edges = new int2[edgeCount];
+ for (var i = 0; i < edges.Length; ++i)
+ edges[i] = outputEdges[i];
}
outputEdges.Dispose();
@@ -365,19 +362,19 @@ public static void TessellateSafe(float minAngle, float maxAngle, float meshArea
}
// Find Target Area to Subdivide for BBW. todo: Burst it.
- internal static float FindTargetAreaForWeightMesh(List triVertices, List triIndices, float meshAreaFactor, float largestTriangleFactor)
+ internal static float FindTargetAreaForWeightMesh(in float2[] triVertices, in int[] triIndices, float meshAreaFactor, float largestTriangleFactor)
{
float totalArea = 0, largestArea = 0, targetArea = 0;
- for (int i = 0; i < triIndices.Count / 3; ++i)
+ for (var i = 0; i < triIndices.Length / 3; ++i)
{
- int i1 = triIndices[0 + (i * 3)];
- int i2 = triIndices[1 + (i * 3)];
- int i3 = triIndices[2 + (i * 3)];
- float2 v1 = triVertices[i1];
- float2 v2 = triVertices[i2];
- float2 v3 = triVertices[i3];
- float area = ModuleHandle.TriangleArea(v1, v2, v3);
+ var i1 = triIndices[0 + (i * 3)];
+ var i2 = triIndices[1 + (i * 3)];
+ var i3 = triIndices[2 + (i * 3)];
+ var v1 = triVertices[i1];
+ var v2 = triVertices[i2];
+ var v3 = triVertices[i3];
+ var area = ModuleHandle.TriangleArea(v1, v2, v3);
totalArea = totalArea + area;
largestArea = largestArea > area ? largestArea : area;
}
@@ -387,20 +384,20 @@ internal static float FindTargetAreaForWeightMesh(List triVertices, Lis
}
// Triangulate Bone Samplers. todo: Burst it.
- internal static void TriangulateSamplers(Vector2[] samplers, List triVertices, List triIndices)
+ internal static void TriangulateSamplers(in float2[] samplers, ref List triVertices, ref List triIndices)
{
foreach(var v in samplers)
{
var vertexCount = triVertices.Count;
- for (int i = 0; i < triIndices.Count / 3; ++i)
+ for (var i = 0; i < triIndices.Count / 3; ++i)
{
- int i1 = triIndices[0 + (i * 3)];
- int i2 = triIndices[1 + (i * 3)];
- int i3 = triIndices[2 + (i * 3)];
- float2 v1 = triVertices[i1];
- float2 v2 = triVertices[i2];
- float2 v3 = triVertices[i3];
+ var i1 = triIndices[0 + (i * 3)];
+ var i2 = triIndices[1 + (i * 3)];
+ var i3 = triIndices[2 + (i * 3)];
+ var v1 = triVertices[i1];
+ var v2 = triVertices[i2];
+ var v3 = triVertices[i3];
var inside = ModuleHandle.IsInsideTriangle(v, v1, v2, v3);
if (inside)
{
@@ -416,21 +413,21 @@ internal static void TriangulateSamplers(Vector2[] samplers, List triVe
// Triangulate Skipped Original Points. These points are discarded during PlanarGrapg cleanup. But bbw only cares if these are part of any geometry. So just insert them. todo: Burst it.
- internal static void TriangulateInternal(int[] internalIndices, List triVertices, List triIndices)
+ internal static void TriangulateInternal(in int[] internalIndices, in float2[] triVertices, ref List triIndices)
{
var triangleCount = triIndices.Count / 3;
foreach(var index in internalIndices)
{
var v = triVertices[index];
- for (int i = 0; i < triangleCount; ++i)
+ for (var i = 0; i < triangleCount; ++i)
{
- int i1 = triIndices[0 + (i * 3)];
- int i2 = triIndices[1 + (i * 3)];
- int i3 = triIndices[2 + (i * 3)];
- float2 v1 = triVertices[i1];
- float2 v2 = triVertices[i2];
- float2 v3 = triVertices[i3];
+ var i1 = triIndices[0 + (i * 3)];
+ var i2 = triIndices[1 + (i * 3)];
+ var i3 = triIndices[2 + (i * 3)];
+ var v1 = triVertices[i1];
+ var v2 = triVertices[i2];
+ var v3 = triVertices[i3];
var c1 = (float)Math.Round(ModuleHandle.OrientFast(v1, v2, v), 2);
if (c1 == 0)
{
diff --git a/Editor/SkinningModule/Triangulation/Triangulator.cs b/Editor/SkinningModule/Triangulation/Triangulator.cs
index b2134dc5..84d1e745 100644
--- a/Editor/SkinningModule/Triangulation/Triangulator.cs
+++ b/Editor/SkinningModule/Triangulation/Triangulator.cs
@@ -1,20 +1,19 @@
-using System.Collections;
-using System.Collections.Generic;
using Unity.Collections;
+using Unity.Mathematics;
using UnityEngine;
namespace UnityEditor.U2D.Animation
{
internal class Triangulator : ITriangulator
{
- public void Triangulate(IList vertices, IList edges, IList