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) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyDescription
CategoriesDisplays 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 groupGroups all Categories created in this Sprite Library Asset.
Inherited foldout groupGroups 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.
LabelsDisplays 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 fieldDisplays 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 indices) + public void Triangulate(ref int2[] edges, ref float2[] vertices, out int[] indices) { - TriangulationUtility.Triangulate(vertices, edges, indices, Allocator.Persistent); + TriangulationUtility.Triangulate(ref edges, ref vertices, out indices, Allocator.Persistent); } - public void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float areaThreshold, int smoothIterations, IList vertices, IList edges, IList indices) + public void Tessellate(float minAngle, float maxAngle, float meshAreaFactor, float largestTriangleAreaFactor, float areaThreshold, int smoothIterations, ref float2[] vertices, ref int2[] edges, out int[] indices) { - TriangulationUtility.Tessellate(minAngle, maxAngle, meshAreaFactor, largestTriangleAreaFactor, areaThreshold, 10, smoothIterations, vertices, edges, indices, Allocator.Persistent); + TriangulationUtility.Tessellate(minAngle, maxAngle, meshAreaFactor, largestTriangleAreaFactor, areaThreshold, 10, smoothIterations, ref vertices, ref edges, out indices, Allocator.Persistent); } } } diff --git a/Editor/SkinningModule/UI/WeightPainterPanel.cs b/Editor/SkinningModule/UI/WeightPainterPanel.cs index bf1ea409..26154531 100644 --- a/Editor/SkinningModule/UI/WeightPainterPanel.cs +++ b/Editor/SkinningModule/UI/WeightPainterPanel.cs @@ -216,7 +216,7 @@ private void LinkSliderToIntegerField(Slider slider, IntegerField field) }); } - public void UpdateWeightInspector(ISpriteMeshData spriteMeshData, string[] boneNames, ISelection selection, ICacheUndo cacheUndo) + public void UpdateWeightInspector(BaseSpriteMeshData spriteMeshData, string[] boneNames, ISelection selection, ICacheUndo cacheUndo) { m_WeightInspectorPanel.weightInspector.spriteMeshData = spriteMeshData; m_WeightInspectorPanel.weightInspector.boneNames = ModuleUtility.ToGUIContentArray(boneNames); diff --git a/Editor/SkinningModule/WeightsGenerator/BoundedBiharmonicWeightsGenerator.cs b/Editor/SkinningModule/WeightsGenerator/BoundedBiharmonicWeightsGenerator.cs index 22ed829f..4e8b87a5 100644 --- a/Editor/SkinningModule/WeightsGenerator/BoundedBiharmonicWeightsGenerator.cs +++ b/Editor/SkinningModule/WeightsGenerator/BoundedBiharmonicWeightsGenerator.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Runtime.InteropServices; using Unity.Collections; @@ -13,14 +12,13 @@ namespace UnityEditor.U2D.Animation internal class BoundedBiharmonicWeightsGenerator : IWeightsGenerator { internal static readonly BoneWeight defaultWeight = new BoneWeight() { weight0 = 1 }; - private const int kNumIterations = 100; - private const int kNumSamples = 4; - private const float kMinAngle = 20f; - private const float kLargestTriangleAreaFactor = 0.4f; - private const float kMeshAreaFactor = 0.004f; + const int k_NumIterations = 100; + const int k_NumSamples = 4; + const float k_LargestTriangleAreaFactor = 0.4f; + const float k_MeshAreaFactor = 0.004f; [DllImport("BoundedBiharmonicWeightsModule")] - private static extern int Bbw(int iterations, + static extern int Bbw(int iterations, [In, Out] IntPtr vertices, int vertexCount, int originalVertexCount, [In, Out] IntPtr indices, int indexCount, [In, Out] IntPtr controlPoints, int controlPointsCount, @@ -29,21 +27,21 @@ private static extern int Bbw(int iterations, [In, Out] IntPtr weights ); - public BoneWeight[] Calculate(string name, Vector2[] vertices, int[] indices, Vector2Int[] edges, Vector2[] controlPoints, Vector2Int[] bones, int[] pins) + public BoneWeight[] Calculate(string name, in float2[] vertices, in int[] indices, in int2[] edges, in float2[] controlPoints, in int2[] bones, in int[] pins) { - edges = SanitizeEdges(edges, vertices.Length); + var sanitizedEdges = SanitizeEdges(edges, vertices.Length); // In almost all cases subdivided mesh weights fine. Non-subdivide is only a fail-safe. bool success = false; - var weights = CalculateInternal(name, vertices, indices, edges, controlPoints, bones, pins, kNumSamples, true, ref success); + var weights = CalculateInternal(vertices, indices, sanitizedEdges, controlPoints, bones, pins, k_NumSamples, true, ref success); if (!success) - weights = CalculateInternal(name, vertices, indices, edges, controlPoints, bones, pins, kNumSamples, false, ref success); + weights = CalculateInternal(vertices, indices, sanitizedEdges, controlPoints, bones, pins, k_NumSamples, false, ref success); return weights; } - static Vector2Int[] SanitizeEdges(Vector2Int[] edges, int noOfVertices) + static int2[] SanitizeEdges(in int2[] edges, int noOfVertices) { - var tmpEdges = new List(edges); + var tmpEdges = new List(edges); for (var i = tmpEdges.Count - 1; i >= 0; i--) { if (tmpEdges[i].x >= noOfVertices || tmpEdges[i].y >= noOfVertices) @@ -53,7 +51,7 @@ static Vector2Int[] SanitizeEdges(Vector2Int[] edges, int noOfVertices) return tmpEdges.ToArray(); } - static void Round(Vector2[] data) + static void Round(ref float2[] data) { for (var i = 0; i < data.Length; ++i) { @@ -61,92 +59,101 @@ static void Round(Vector2[] data) var y = data[i].y; x = (float) Math.Round(x, 8); y = (float) Math.Round(y, 8); - data[i] = new Vector2(x, y); + data[i] = new float2(x, y); } } - BoneWeight[] CalculateInternal(string name, Vector2[] vertices, int[] indices, Vector2Int[] edges, Vector2[] controlPoints, Vector2Int[] bones, int[] pins, int numSamples, bool subdivide, ref bool done) + static BoneWeight[] CalculateInternal(in float2[] inputVertices, in int[] inputIndices, in int2[] inputEdges, in float2[] inputControlPoints, in int2[] inputBones, in int[] inputPins, int numSamples, bool subdivide, ref bool done) { done = false; - var weights = new BoneWeight[vertices.Length]; + var weights = new BoneWeight[inputVertices.Length]; for (var i = 0; i < weights.Length; ++i) weights[i] = defaultWeight; - if (vertices.Length < 3) - return weights; - - var boneSamples = SampleBones(controlPoints, bones, numSamples); - var verticesList = new List(vertices.Length + controlPoints.Length + boneSamples.Length); - Round(vertices); - Round(controlPoints); - Round(boneSamples); - verticesList.AddRange(vertices); - verticesList.AddRange(controlPoints); - verticesList.AddRange(boneSamples); - - var utEdges = new List(edges); - var utIndices = new List(); - var utVertices = new List(vertices); - + if (inputVertices.Length < 3) + return weights; + + var edges = EditorUtilities.CreateCopy(inputEdges); + var controlPoints = EditorUtilities.CreateCopy(inputControlPoints); + var vertices = EditorUtilities.CreateCopy(inputVertices); + + var boneSamples = SampleBones(in inputControlPoints, in inputBones, numSamples); + Round(ref vertices); + Round(ref controlPoints); + Round(ref boneSamples); + // Input Vertices are well refined and smoothed, just triangulate with bones and cages. - var ok = TriangulationUtility.TriangulateSafe(utVertices, utEdges, utIndices); - if (!ok || utIndices.Count == 0) + var ok = TriangulationUtility.TriangulateSafe(ref vertices, ref edges, out var indices); + if (!ok || indices.Length == 0) { - utIndices.AddRange(indices); - utVertices.AddRange(vertices); + indices = EditorUtilities.CreateCopy(inputIndices); + vertices = EditorUtilities.CreateCopy(inputVertices); + Round(ref vertices); } else if(subdivide) { - var targetArea = TriangulationUtility.FindTargetAreaForWeightMesh(utVertices, utIndices, kMeshAreaFactor, kLargestTriangleAreaFactor); - TriangulationUtility.TessellateSafe(0, 0, 0, 0, targetArea, 1, 0, utVertices, utEdges, utIndices); + var targetArea = TriangulationUtility.FindTargetAreaForWeightMesh(in vertices, in indices, k_MeshAreaFactor, k_LargestTriangleAreaFactor); + TriangulationUtility.TessellateSafe(0, targetArea, 1, 0, ref vertices, ref edges, out indices); } - if (utIndices.Count == 0) + if (indices.Length == 0) return weights; // Copy Original Indices. - var coIndices = new List(utIndices); - utIndices.Clear(); - for (int i = 0; i < coIndices.Count / 3; ++i) + var coIndices = EditorUtilities.CreateCopy(indices); + var tmpIndices = new List(indices.Length); + for (var i = 0; i < coIndices.Length / 3; ++i) { var i1 = coIndices[0 + (i * 3)]; var i2 = coIndices[1 + (i * 3)]; var i3 = coIndices[2 + (i * 3)]; - float2 v1 = utVertices[i1]; - float2 v2 = utVertices[i2]; - float2 v3 = utVertices[i3]; + var v1 = vertices[i1]; + var v2 = vertices[i2]; + var v3 = vertices[i3]; var rt = (float)Math.Round(ModuleHandle.OrientFast(v1, v2, v3), 2); if (rt != 0) { - utIndices.Add(i1); - utIndices.Add(i2); - utIndices.Add(i3); + tmpIndices.Add(i1); + tmpIndices.Add(i2); + tmpIndices.Add(i3); } } + indices = tmpIndices.ToArray(); // Insert Samplers. var internalPoints = new List(); - for (var i = 0; i < utVertices.Count; ++i) - if (utIndices.Count(x => x == i) == 0) + for (var i = 0; i < vertices.Length; ++i) + { + var counter = 0; + for (var m = 0; m < indices.Length; ++m) + { + if (indices[m] == i) + counter++; + } + if (counter == 0) internalPoints.Add(i); + } + + tmpIndices = new List(indices); + TriangulationUtility.TriangulateInternal(internalPoints.ToArray(), in vertices, ref tmpIndices); - TriangulationUtility.TriangulateInternal(internalPoints.ToArray(), utVertices, utIndices); - TriangulationUtility.TriangulateSamplers(boneSamples, utVertices, utIndices); - TriangulationUtility.TriangulateSamplers(controlPoints, utVertices, utIndices); - var tessellatedIndices = utIndices.ToArray(); - var tessellatedVertices = utVertices.ToArray(); + var tmpVertices = new List(vertices); + TriangulationUtility.TriangulateSamplers(boneSamples, ref tmpVertices, ref tmpIndices); + TriangulationUtility.TriangulateSamplers(controlPoints, ref tmpVertices, ref tmpIndices); + vertices = tmpVertices.ToArray(); + indices = tmpIndices.ToArray(); - var verticesHandle = GCHandle.Alloc(tessellatedVertices, GCHandleType.Pinned); - var indicesHandle = GCHandle.Alloc(tessellatedIndices, GCHandleType.Pinned); + var verticesHandle = GCHandle.Alloc(vertices, GCHandleType.Pinned); + var indicesHandle = GCHandle.Alloc(indices, GCHandleType.Pinned); var controlPointsHandle = GCHandle.Alloc(controlPoints, GCHandleType.Pinned); - var bonesHandle = GCHandle.Alloc(bones, GCHandleType.Pinned); - var pinsHandle = GCHandle.Alloc(pins, GCHandleType.Pinned); + var bonesHandle = GCHandle.Alloc(inputBones, GCHandleType.Pinned); + var pinsHandle = GCHandle.Alloc(inputPins, GCHandleType.Pinned); var weightsHandle = GCHandle.Alloc(weights, GCHandleType.Pinned); - var result = Bbw(kNumIterations, - verticesHandle.AddrOfPinnedObject(), tessellatedVertices.Length, vertices.Length, - indicesHandle.AddrOfPinnedObject(), tessellatedIndices.Length, + var result = Bbw(k_NumIterations, + verticesHandle.AddrOfPinnedObject(), vertices.Length, inputVertices.Length, + indicesHandle.AddrOfPinnedObject(), indices.Length, controlPointsHandle.AddrOfPinnedObject(), controlPoints.Length, - bonesHandle.AddrOfPinnedObject(), bones.Length, - pinsHandle.AddrOfPinnedObject(), pins.Length, + bonesHandle.AddrOfPinnedObject(), inputBones.Length, + pinsHandle.AddrOfPinnedObject(), inputPins.Length, weightsHandle.AddrOfPinnedObject()); switch (result) @@ -167,9 +174,7 @@ BoneWeight[] CalculateInternal(string name, Vector2[] vertices, int[] indices, V bonesHandle.Free(); pinsHandle.Free(); weightsHandle.Free(); - - // OhmDebugger.WriteStatisticsText(verticesList.ToArray(), edges, bones, boneSamples, weights, tessellatedVertices, tessellatedIndices, vertices.Length, controlPoints.Length, boneSamples.Length, "UTess", 1000); - + for (var i = 0; i < weights.Length; ++i) { var weight = weights[i]; @@ -183,38 +188,43 @@ BoneWeight[] CalculateInternal(string name, Vector2[] vertices, int[] indices, V return weights; } - public void DebugMesh(ISpriteMeshData spriteMeshData, Vector2[] vertices, Vector2Int[] edges, Vector2[] controlPoints, Vector2Int[] bones, int[] pins) + public void DebugMesh(BaseSpriteMeshData spriteMeshData, float2[] vertices, int2[] edges, float2[] controlPoints, int2[] bones, int[] pins) { - var boneSamples = SampleBones(controlPoints, bones, kNumSamples); - var verticesList = new List(vertices.Length + controlPoints.Length + boneSamples.Length); - var edgesList = new List(edges); - var indicesList = new List(); - - verticesList.AddRange(vertices); - verticesList.AddRange(controlPoints); - verticesList.AddRange(boneSamples); + var boneSamples = SampleBones(controlPoints, bones, k_NumSamples); + var testVertices = new float2[vertices.Length + controlPoints.Length + boneSamples.Length]; + + var headIndex = 0; + Array.Copy(vertices, 0, testVertices, headIndex, vertices.Length); + headIndex = vertices.Length; + Array.Copy(controlPoints, 0, testVertices, headIndex, controlPoints.Length); + headIndex += controlPoints.Length; + Array.Copy(boneSamples, 0, testVertices, headIndex, boneSamples.Length); + + TriangulationUtility.Triangulate(ref edges, ref testVertices, out var indices, Allocator.Temp); - TriangulationUtility.Triangulate(verticesList, edgesList, indicesList, Allocator.Temp); spriteMeshData.Clear(); - verticesList.ForEach(v => spriteMeshData.AddVertex(v, new BoneWeight())); - spriteMeshData.edges = edgesList.ToArray(); - spriteMeshData.indices = indicesList.ToArray(); + for (var i = 0; i < testVertices.Length; ++i) + spriteMeshData.AddVertex(testVertices[i], new BoneWeight()); + + spriteMeshData.SetIndices(indices); + + var convertedEdges = new int2[edges.Length]; + Array.Copy(edges, convertedEdges, edges.Length); + spriteMeshData.SetEdges(convertedEdges); } - Vector2[] SampleBones(Vector2[] points, Vector2Int[] edges, int numSamples) + static float2[] SampleBones(in float2[] points, in int2[] edges, int numSamples) { Debug.Assert(numSamples > 0); - var sampledEdges = new List(); - + var sampledEdges = new List(edges.Length * numSamples); for (var i = 0; i < edges.Length; i++) { var edge = edges[i]; var tip = points[edge.x]; var tail = points[edge.y]; - var length = (tip - tail).magnitude; for (var s = 0; s < numSamples; s++) { diff --git a/Editor/SkinningModule/WeightsGenerator/IWeightsGenerator.cs b/Editor/SkinningModule/WeightsGenerator/IWeightsGenerator.cs index 533b4988..99d6252c 100644 --- a/Editor/SkinningModule/WeightsGenerator/IWeightsGenerator.cs +++ b/Editor/SkinningModule/WeightsGenerator/IWeightsGenerator.cs @@ -1,9 +1,10 @@ +using Unity.Mathematics; using UnityEngine; namespace UnityEditor.U2D.Animation { internal interface IWeightsGenerator { - BoneWeight[] Calculate(string name, Vector2[] vertices, int[] indices, Vector2Int[] edges, Vector2[] controlPoints, Vector2Int[] bones, int[] pins); + BoneWeight[] Calculate(string name, in float2[] vertices, in int[] indices, in int2[] edges, in float2[] controlPoints, in int2[] bones, in int[] pins); } } diff --git a/Editor/SpriteLib/SpriteLibraryEditor/SpriteLibraryEditorWindow.cs b/Editor/SpriteLib/SpriteLibraryEditor/SpriteLibraryEditorWindow.cs index 1871915c..1432eeca 100644 --- a/Editor/SpriteLib/SpriteLibraryEditor/SpriteLibraryEditorWindow.cs +++ b/Editor/SpriteLib/SpriteLibraryEditor/SpriteLibraryEditorWindow.cs @@ -66,7 +66,7 @@ void OnDestroy() void InitializeWindow() { titleContent = new GUIContent(k_WindowTitle, EditorIconUtility.LoadIconResource("Animation.SpriteLibraryManager", "ComponentIcons", "ComponentIcons")); - saveChangesMessage = TextContent.spriteLibrarySaveMessage; + saveChangesMessage = TextContent.savePopupMessage; m_ControllerEvents = new ControllerEvents(); m_ViewEvents = new ViewEvents(); @@ -153,10 +153,10 @@ public override void DiscardChanges() public void HandleUnsavedChanges() { if (EditorUtility.DisplayDialog( - TextContent.spriteLibrarySaveTitle, - TextContent.spriteLibrarySaveMessage, - TextContent.spriteLibrarySaveYes, - TextContent.spriteLibrarySaveNo)) + TextContent.savePopupTitle, + TextContent.savePopupMessage, + TextContent.savePopupOptionYes, + TextContent.savePopupOptionNo)) SaveChanges(); else DiscardChanges(); @@ -165,10 +165,10 @@ public void HandleUnsavedChanges() public void HandleRevertChanges() { if (EditorUtility.DisplayDialog( - TextContent.spriteLibrarySaveTitle, + TextContent.savePopupTitle, TextContent.spriteLibraryRevertMessage, - TextContent.spriteLibrarySaveYes, - TextContent.spriteLibrarySaveNo)) + TextContent.savePopupOptionYes, + TextContent.savePopupOptionNo)) m_Controller.RevertChanges(); } diff --git a/Editor/SpriteLib/SpriteLibrarySourceAsset/SpriteLibraryDataInspector.cs b/Editor/SpriteLib/SpriteLibrarySourceAsset/SpriteLibraryDataInspector.cs index 4868607c..e4c9bab8 100644 --- a/Editor/SpriteLib/SpriteLibrarySourceAsset/SpriteLibraryDataInspector.cs +++ b/Editor/SpriteLib/SpriteLibrarySourceAsset/SpriteLibraryDataInspector.cs @@ -335,7 +335,7 @@ void DrawCategory(Rect rect, int index) EditorGUI.BeginChangeCheck(); var oldLabelWidth = EditorGUIUtility.labelWidth; EditorGUIUtility.labelWidth = m_Style.categoryLabelWidth; - var newCatName = EditorGUI.DelayedTextField(catRect, m_Style.categoryLabel, categoryName); + var newCatName = EditorGUI.TextField(catRect, m_Style.categoryLabel, categoryName); EditorGUIUtility.labelWidth = oldLabelWidth; if (EditorGUI.EndChangeCheck()) { @@ -596,7 +596,7 @@ void DrawLabel(int index, nameRect.width -= 20; } - var newName = EditorGUI.DelayedTextField( + var newName = EditorGUI.TextField( nameRect, GUIContent.none, oldName); diff --git a/Editor/SpriteOutlineRenderer/SpriteOutlineRenderer.cs b/Editor/SpriteOutlineRenderer/SpriteOutlineRenderer.cs index 9f980e51..2a5be3a8 100644 --- a/Editor/SpriteOutlineRenderer/SpriteOutlineRenderer.cs +++ b/Editor/SpriteOutlineRenderer/SpriteOutlineRenderer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Unity.Mathematics; using UnityEditor.U2D.Sprites; using UnityEngine; @@ -83,7 +84,7 @@ internal void RenderSpriteOutline(ISpriteEditor spriteEditor, SpriteCache sprite UnityEngine.Profiling.Profiler.EndSample(); } - void DrawEdgeOutline(Vector2Int[] edges, Vector3[] vertices, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma) + void DrawEdgeOutline(int2[] edges, Vector3[] vertices, Matrix4x4 multMatrix, float outlineSize, Color outlineColor, float adjustForGamma) { m_EdgeOutlineMaterial.SetColor(k_OutlineColorProperty, outlineColor); m_EdgeOutlineMaterial.SetFloat(k_AdjustLinearForGammaProperty, adjustForGamma); diff --git a/Editor/SpriteSkin/SpriteSkinEditor.cs b/Editor/SpriteSkin/SpriteSkinEditor.cs index 6d089be0..d7be54cf 100644 --- a/Editor/SpriteSkin/SpriteSkinEditor.cs +++ b/Editor/SpriteSkin/SpriteSkinEditor.cs @@ -1,7 +1,7 @@ +using System.Collections.Generic; using UnityEngine; using UnityEditorInternal; using UnityEngine.U2D.Animation; -using UnityEditor.IMGUI.Controls; using UnityEngine.U2D; namespace UnityEditor.U2D.Animation @@ -10,7 +10,7 @@ namespace UnityEditor.U2D.Animation [CanEditMultipleObjects] class SpriteSkinEditor : Editor { - private static class Contents + static class Contents { public static readonly GUIContent listHeaderLabel = new GUIContent("Bones", "GameObject Transform to represent the Bones defined by the Sprite that is currently used for deformation."); public static readonly GUIContent rootBoneLabel = new GUIContent("Root Bone", "GameObject Transform to represent the Root Bone."); @@ -18,123 +18,83 @@ private static class Contents public static readonly string spriteHasNoSkinningInformation = L10n.Tr("Sprite has no Bind Poses"); public static readonly string spriteHasNoWeights = L10n.Tr("Sprite has no weights"); public static readonly string rootTransformNotFound = L10n.Tr("Root Bone not set"); - public static readonly string rootTransformNotFoundInArray = L10n.Tr("Bone list doesn't contain a reference to the Root Bone"); public static readonly string invalidTransformArray = L10n.Tr("Bone list is invalid"); + public static readonly string invalidBoneWeights = L10n.Tr("Bone weights are invalid"); public static readonly string transformArrayContainsNull = L10n.Tr("Bone list contains unassigned references"); public static readonly string invalidTransformArrayLength = L10n.Tr("The number of Sprite's Bind Poses and the number of Transforms should match"); - public static readonly GUIContent useManager = new GUIContent("Enable batching", "When enabled, SpriteSkin deformation will be done in batch to improve performance."); public static readonly GUIContent alwaysUpdate = new GUIContent("Always Update", "Executes deformation of SpriteSkin even when the associated SpriteRenderer has been culled and is not visible."); public static readonly GUIContent autoRebind = new GUIContent("Auto Rebind", "When the Sprite in SpriteRenderer is changed, SpriteSkin will try to look for the Transforms that is needed for the Sprite using the Root Bone Tranform as parent."); } - private static Color s_BoundingBoxHandleColor = new Color(255, 255, 255, 150) / 255; - - private SerializedProperty m_RootBoneProperty; - private SerializedProperty m_BoneTransformsProperty; - private SerializedProperty m_AlwaysUpdateProperty; - private SerializedProperty m_AutoRebindProperty; - private SpriteSkin m_SpriteSkin; - private ReorderableList m_ReorderableList; - private Sprite m_CurrentSprite; - private BoxBoundsHandle m_BoundsHandle = new BoxBoundsHandle(); - private bool m_NeedsRebind = false; - private bool m_BoneFold = true; + SerializedProperty m_RootBoneProperty; + SerializedProperty m_BoneTransformsProperty; + SerializedProperty m_AlwaysUpdateProperty; + SerializedProperty m_AutoRebindProperty; + + SpriteSkin[] m_SpriteSkins; + Sprite[] m_CurrentSprites; + ReorderableList m_ReorderableList; + + bool m_NeedsRebind = false; + bool m_BoneFold = true; - private void OnEnable() + void OnEnable() { - m_SpriteSkin = (SpriteSkin)target; - m_SpriteSkin.OnEditorEnable(); + var listOfSkins = new List(targets.Length); + foreach (var obj in targets) + { + if (obj is SpriteSkin skin) + { + listOfSkins.Add(skin); + skin.OnEditorEnable(); + } + } + m_SpriteSkins = listOfSkins.ToArray(); m_RootBoneProperty = serializedObject.FindProperty("m_RootBone"); m_BoneTransformsProperty = serializedObject.FindProperty("m_BoneTransforms"); m_AlwaysUpdateProperty = serializedObject.FindProperty("m_AlwaysUpdate"); m_AutoRebindProperty = serializedObject.FindProperty("m_AutoRebind"); - - m_CurrentSprite = m_SpriteSkin.spriteRenderer.sprite; - m_BoundsHandle.axes = BoxBoundsHandle.Axes.X | BoxBoundsHandle.Axes.Y; - m_BoundsHandle.SetColor(s_BoundingBoxHandleColor); + m_CurrentSprites = new Sprite[m_SpriteSkins.Length]; + + UpdateSpriteCache(); SetupReorderableList(); Undo.undoRedoPerformed += UndoRedoPerformed; } - private void OnDestroy() + void OnDestroy() { Undo.undoRedoPerformed -= UndoRedoPerformed; } - private void UndoRedoPerformed() - { - m_CurrentSprite = m_SpriteSkin.spriteRenderer.sprite; - } - - private void SetupReorderableList() + void UndoRedoPerformed() { - m_ReorderableList = new ReorderableList(serializedObject, m_BoneTransformsProperty, false, true, false, false); - m_ReorderableList.headerHeight = 1.0f; - m_ReorderableList.elementHeightCallback = (int index) => - { - return EditorGUIUtility.singleLineHeight + 6; - }; - m_ReorderableList.drawElementCallback = (Rect rect, int index, bool isactive, bool isfocused) => - { - var content = GUIContent.none; - - if (m_CurrentSprite != null) - { - var bones = m_CurrentSprite.GetBones(); - if (index < bones.Length) - content = new GUIContent(bones[index].name); - } - - rect.y += 2f; - rect.height = EditorGUIUtility.singleLineHeight; - SerializedProperty element = m_BoneTransformsProperty.GetArrayElementAtIndex(index); - EditorGUI.PropertyField(rect, element, content); - }; + UpdateSpriteCache(); } - - private void InitializeBoneTransformArray() + + void UpdateSpriteCache() { - if (m_CurrentSprite) + for (var i = 0; i < m_SpriteSkins.Length; ++i) { - var elementCount = m_BoneTransformsProperty.arraySize; - var bones = m_CurrentSprite.GetBones(); - - if (elementCount != bones.Length) - { - m_BoneTransformsProperty.arraySize = bones.Length; - - for (int i = elementCount; i < m_BoneTransformsProperty.arraySize; ++i) - m_BoneTransformsProperty.GetArrayElementAtIndex(i).objectReferenceValue = null; - - m_NeedsRebind = true; - } - } + m_CurrentSprites[i] = m_SpriteSkins[i].sprite; + } } - + public override void OnInspectorGUI() { serializedObject.Update(); + EditorGUILayout.PropertyField(m_AlwaysUpdateProperty, Contents.alwaysUpdate); + EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_AutoRebindProperty, Contents.autoRebind); if (EditorGUI.EndChangeCheck() && m_AutoRebindProperty.boolValue) { m_NeedsRebind = true; } - - var sprite = m_SpriteSkin.spriteRenderer.sprite; - var spriteChanged = m_CurrentSprite != sprite; - - if (m_ReorderableList == null || spriteChanged) - { - m_CurrentSprite = sprite; - InitializeBoneTransformArray(); - SetupReorderableList(); - } EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_RootBoneProperty, Contents.rootBoneLabel); @@ -143,17 +103,15 @@ public override void OnInspectorGUI() m_NeedsRebind = true; } - m_BoneFold = EditorGUILayout.Foldout(m_BoneFold, Contents.listHeaderLabel, true); - if (m_BoneFold) + var hasSpriteChanged = HasAnySpriteChanged(); + if (m_ReorderableList == null || hasSpriteChanged) { - EditorGUILayout.Space(); - if (!serializedObject.isEditingMultipleObjects) - { - EditorGUI.BeginDisabledGroup(m_SpriteSkin.rootBone == null); - m_ReorderableList.DoLayoutList(); - EditorGUI.EndDisabledGroup(); - } + UpdateSpriteCache(); + InitializeBoneTransformArray(); + SetupReorderableList(); } + + DoBoneFoldout(); EditorGUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); @@ -174,147 +132,218 @@ public override void OnInspectorGUI() if (m_NeedsRebind) Rebind(); - if (spriteChanged && !m_SpriteSkin.ignoreNextSpriteChange) + if (hasSpriteChanged && !m_SpriteSkins[0].ignoreNextSpriteChange) { ResetBounds(Undo.GetCurrentGroupName()); - m_SpriteSkin.ignoreNextSpriteChange = false; + m_SpriteSkins[0].ignoreNextSpriteChange = false; } DoValidationWarnings(); } - private void Rebind() + bool HasAnySpriteChanged() + { + for (var i = 0; i < m_SpriteSkins.Length; ++i) + { + var sprite = m_SpriteSkins[i].sprite; + if (m_CurrentSprites[i] != sprite) + return true; + } + + return false; + } + + void DoBoneFoldout() + { + EditorGUILayout.Space(); + + m_BoneFold = EditorGUILayout.Foldout(m_BoneFold, Contents.listHeaderLabel, true); + if (m_BoneFold) + { + EditorGUI.BeginDisabledGroup(m_SpriteSkins[0].rootBone == null || m_BoneTransformsProperty.hasMultipleDifferentValues); + m_ReorderableList.DoLayoutList(); + EditorGUI.EndDisabledGroup(); + } + } + + void InitializeBoneTransformArray() + { + var hasSameNumberOfBones = true; + var noOfBones = -1; + for (var i = 0; i < m_SpriteSkins.Length; ++i) + { + if (m_SpriteSkins[i] == null) + continue; + if (i == 0) + noOfBones = m_CurrentSprites[i].GetBones().Length; + else if (m_CurrentSprites[i].GetBones().Length != noOfBones) + { + hasSameNumberOfBones = false; + break; + } + } + + if (hasSameNumberOfBones) + { + var elementCount = m_BoneTransformsProperty.arraySize; + var bones = m_CurrentSprites[0].GetBones(); + + if (elementCount != bones.Length) + { + m_BoneTransformsProperty.arraySize = bones.Length; + + for (var i = elementCount; i < m_BoneTransformsProperty.arraySize; ++i) + m_BoneTransformsProperty.GetArrayElementAtIndex(i).objectReferenceValue = null; + + m_NeedsRebind = true; + } + } + } + + void SetupReorderableList() { - foreach (var t in targets) + m_ReorderableList = new ReorderableList(serializedObject, m_BoneTransformsProperty, false, true, false, false); + m_ReorderableList.headerHeight = 1.0f; + m_ReorderableList.elementHeightCallback = _ => EditorGUIUtility.singleLineHeight + 6; + m_ReorderableList.drawElementCallback = (rect, index, _, _) => { - var spriteSkin = t as SpriteSkin; + var content = GUIContent.none; + + if (m_CurrentSprites[0] != null) + { + var bones = m_CurrentSprites[0].GetBones(); + if (index < bones.Length) + content = new GUIContent(bones[index].name); + } + + rect.y += 2f; + rect.height = EditorGUIUtility.singleLineHeight; + var element = m_BoneTransformsProperty.GetArrayElementAtIndex(index); + + EditorGUI.showMixedValue = m_BoneTransformsProperty.hasMultipleDifferentValues; + EditorGUI.PropertyField(rect, element, content); + }; + } + - if(spriteSkin.spriteRenderer.sprite == null || spriteSkin.rootBone == null) + void Rebind() + { + foreach (var skin in m_SpriteSkins) + { + if(skin.sprite == null || skin.rootBone == null) continue; - if (!SpriteSkin.GetSpriteBonesTransforms(spriteSkin, out var transforms)) - Debug.LogWarning($"Rebind failed for {spriteSkin.name}. Could not find all bones required by the Sprite: {spriteSkin.sprite.name}."); - spriteSkin.boneTransforms = transforms; + if (!SpriteSkin.GetSpriteBonesTransforms(skin, out var transforms)) + Debug.LogWarning($"Rebind failed for {skin.name}. Could not find all bones required by the Sprite: {skin.sprite.name}."); + skin.boneTransforms = transforms; - ResetBoundsIfNeeded(spriteSkin); + ResetBoundsIfNeeded(skin); } serializedObject.Update(); m_NeedsRebind = false; } - private void ResetBounds(string undoName = "Reset Bounds") + void ResetBounds(string undoName = "Reset Bounds") { - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - - if (!spriteSkin.isValid) + if (!skin.isValid) continue; - Undo.RegisterCompleteObjectUndo(spriteSkin, undoName); - spriteSkin.CalculateBounds(); + Undo.RegisterCompleteObjectUndo(skin, undoName); + skin.CalculateBounds(); - EditorUtility.SetDirty(spriteSkin); + EditorUtility.SetDirty(skin); } } - private void ResetBoundsIfNeeded(SpriteSkin spriteSkin) + static void ResetBoundsIfNeeded(SpriteSkin spriteSkin) { if (spriteSkin.isValid && spriteSkin.bounds == new Bounds()) spriteSkin.CalculateBounds(); } - private bool EnableCreateBones() + bool EnableCreateBones() { - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - var sprite = spriteSkin.spriteRenderer.sprite; - - if (sprite != null && spriteSkin.rootBone == null) + var sprite = skin.sprite; + if (sprite != null && skin.rootBone == null) return true; } return false; } - private bool EnableSetBindPose() + bool EnableSetBindPose() { return IsAnyTargetValid(); } - private bool IsAnyTargetValid() + bool IsAnyTargetValid() { - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - - if (spriteSkin.isValid) + if (skin.isValid) return true; } return false; } - private void DoGenerateBonesButton() + void DoGenerateBonesButton() { if (GUILayout.Button("Create Bones", GUILayout.MaxWidth(125f))) { - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - var sprite = spriteSkin.spriteRenderer.sprite; - - if (sprite == null || spriteSkin.rootBone != null) + var sprite = skin.sprite; + if (sprite == null || skin.rootBone != null) continue; - Undo.RegisterCompleteObjectUndo(spriteSkin, "Create Bones"); + Undo.RegisterCompleteObjectUndo(skin, "Create Bones"); - spriteSkin.CreateBoneHierarchy(); + skin.CreateBoneHierarchy(); - foreach (var transform in spriteSkin.boneTransforms) + foreach (var transform in skin.boneTransforms) Undo.RegisterCreatedObjectUndo(transform.gameObject, "Create Bones"); - ResetBoundsIfNeeded(spriteSkin); + ResetBoundsIfNeeded(skin); - EditorUtility.SetDirty(spriteSkin); + EditorUtility.SetDirty(skin); } + + serializedObject.SetIsDifferentCacheDirty(); BoneGizmo.instance.boneGizmoController.OnSelectionChanged(); } } - private void DoResetBindPoseButton() + void DoResetBindPoseButton() { if (GUILayout.Button("Reset Bind Pose", GUILayout.MaxWidth(125f))) { - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - - if (!spriteSkin.isValid) + if (!skin.isValid) continue; - Undo.RecordObjects(spriteSkin.boneTransforms, "Reset Bind Pose"); - spriteSkin.ResetBindPose(); + Undo.RecordObjects(skin.boneTransforms, "Reset Bind Pose"); + skin.ResetBindPose(); } } } - private void DoValidationWarnings() + void DoValidationWarnings() { EditorGUILayout.Space(); - bool preAppendObjectName = targets.Length > 1; + var preAppendObjectName = targets.Length > 1; - foreach (var t in targets) + foreach (var skin in m_SpriteSkins) { - var spriteSkin = t as SpriteSkin; - - var validationResult = spriteSkin.Validate(); - + var validationResult = skin.Validate(); if (validationResult == SpriteSkinValidationResult.Ready) continue; var text = ""; - switch (validationResult) { case SpriteSkinValidationResult.SpriteNotFound: @@ -338,10 +367,13 @@ private void DoValidationWarnings() case SpriteSkinValidationResult.TransformArrayContainsNull: text = Contents.transformArrayContainsNull; break; + case SpriteSkinValidationResult.InvalidBoneWeights: + text = Contents.invalidBoneWeights; + break; } if (preAppendObjectName) - text = string.Format("{0}:{1}",spriteSkin.name,text); + text = $"{skin.name}:{text}"; EditorGUILayout.HelpBox(text, MessageType.Warning); } diff --git a/IK/Runtime/CCDSolver2D.cs b/IK/Runtime/CCDSolver2D.cs index c1d6800c..3aaaff46 100644 --- a/IK/Runtime/CCDSolver2D.cs +++ b/IK/Runtime/CCDSolver2D.cs @@ -12,30 +12,35 @@ namespace UnityEngine.U2D.IK [IconAttribute(IconUtility.IconPath + "Animation.IKCCD.png")] public sealed class CCDSolver2D : Solver2D { - private const float kMinTolerance = 0.001f; - private const int kMinIterations = 1; - private const float kMinVelocity = 0.01f; - private const float kMaxVelocity = 1f; + const int k_MinIterations = 1; + const float k_MinTolerance = 0.001f; + const float k_MinVelocity = 0.01f; + const float k_MaxVelocity = 1f; [SerializeField] - private IKChain2D m_Chain = new IKChain2D(); + IKChain2D m_Chain = new IKChain2D(); - [SerializeField][Range(kMinIterations, 50)] - private int m_Iterations = 10; - [SerializeField][Range(kMinTolerance, 0.1f)] - private float m_Tolerance = 0.01f; - [SerializeField][Range(0f, 1f)] - private float m_Velocity = 0.5f; + [SerializeField] + [Range(k_MinIterations, 50)] + int m_Iterations = 10; + + [SerializeField] + [Range(k_MinTolerance, 0.1f)] + float m_Tolerance = 0.01f; + + [SerializeField] + [Range(0f, 1f)] + float m_Velocity = 0.5f; - private Vector3[] m_Positions; + Vector3[] m_Positions; /// - /// Get and Set the solver's itegration count. + /// Get and Set the solver's integration count. /// public int iterations { - get { return m_Iterations; } - set { m_Iterations = Mathf.Max(value, kMinIterations); } + get => m_Iterations; + set => m_Iterations = Mathf.Max(value, k_MinIterations); } /// @@ -43,8 +48,8 @@ public int iterations /// public float tolerance { - get { return m_Tolerance; } - set { m_Tolerance = Mathf.Max(value, kMinTolerance); } + get => m_Tolerance; + set => m_Tolerance = Mathf.Max(value, k_MinTolerance); } /// @@ -52,28 +57,22 @@ public float tolerance /// public float velocity { - get { return m_Velocity; } - set { m_Velocity = Mathf.Clamp01(value); } + get => m_Velocity; + set => m_Velocity = Mathf.Clamp01(value); } /// /// Returns the number of chain in the solver. /// /// This always returns 1 - protected override int GetChainCount() - { - return 1; - } + protected override int GetChainCount() => 1; /// /// Gets the chain in the solver by index. /// /// Chain index. /// Returns IKChain2D at the index. - public override IKChain2D GetChain(int index) - { - return m_Chain; - } + public override IKChain2D GetChain(int index) => m_Chain; /// /// DoPrepare override from base class. @@ -83,7 +82,7 @@ protected override void DoPrepare() if (m_Positions == null || m_Positions.Length != m_Chain.transformCount) m_Positions = new Vector3[m_Chain.transformCount]; - for (int i = 0; i < m_Chain.transformCount; ++i) + for (var i = 0; i < m_Chain.transformCount; ++i) m_Positions[i] = m_Chain.transforms[i].position; } @@ -93,18 +92,18 @@ protected override void DoPrepare() /// Target position for the chain. protected override void DoUpdateIK(List effectorPositions) { - Profiler.BeginSample("CCDSolver2D.DoUpdateIK"); + Profiler.BeginSample(nameof(CCDSolver2D.DoUpdateIK)); - Vector3 effectorPosition = effectorPositions[0]; - Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition); + var effectorPosition = effectorPositions[0]; + var effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition); effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D); - if (CCD2D.Solve(effectorPosition, GetPlaneRootTransform().forward, iterations, tolerance, Mathf.Lerp(kMinVelocity, kMaxVelocity, m_Velocity), ref m_Positions)) + if (CCD2D.Solve(effectorPosition, GetPlaneRootTransform().forward, iterations, tolerance, Mathf.Lerp(k_MinVelocity, k_MaxVelocity, m_Velocity), ref m_Positions)) { - for (int i = 0; i < m_Chain.transformCount - 1; ++i) + for (var i = 0; i < m_Chain.transformCount - 1; ++i) { - Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition; - Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_Positions[i + 1]); + var startLocalPosition = m_Chain.transforms[i + 1].localPosition; + var endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_Positions[i + 1]); m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition); } } @@ -112,4 +111,4 @@ protected override void DoUpdateIK(List effectorPositions) Profiler.EndSample(); } } -} +} \ No newline at end of file diff --git a/IK/Runtime/FabrikSolver2D.cs b/IK/Runtime/FabrikSolver2D.cs index 9b50e56f..324b3767 100644 --- a/IK/Runtime/FabrikSolver2D.cs +++ b/IK/Runtime/FabrikSolver2D.cs @@ -12,27 +12,31 @@ namespace UnityEngine.U2D.IK [IconAttribute(IconUtility.IconPath + "Animation.IKFabrik.png")] public sealed class FabrikSolver2D : Solver2D { - private const float kMinTolerance = 0.001f; - private const int kMinIterations = 1; + const float k_MinTolerance = 0.001f; + const int k_MinIterations = 1; [SerializeField] - private IKChain2D m_Chain = new IKChain2D(); - [SerializeField][Range(kMinIterations, 50)] - private int m_Iterations = 10; - [SerializeField][Range(kMinTolerance, 0.1f)] - private float m_Tolerance = 0.01f; + IKChain2D m_Chain = new IKChain2D(); - private float[] m_Lengths; - private Vector2[] m_Positions; - private Vector3[] m_WorldPositions; + [SerializeField] + [Range(k_MinIterations, 50)] + int m_Iterations = 10; + + [SerializeField] + [Range(k_MinTolerance, 0.1f)] + float m_Tolerance = 0.01f; + + float[] m_Lengths; + Vector2[] m_Positions; + Vector3[] m_WorldPositions; /// - /// Get and Set the solver's itegration count. + /// Get and Set the solver's integration count. /// public int iterations { - get { return m_Iterations; } - set { m_Iterations = Mathf.Max(value, kMinIterations); } + get => m_Iterations; + set => m_Iterations = Mathf.Max(value, k_MinIterations); } /// @@ -40,28 +44,22 @@ public int iterations /// public float tolerance { - get { return m_Tolerance; } - set { m_Tolerance = Mathf.Max(value, kMinTolerance); } + get => m_Tolerance; + set => m_Tolerance = Mathf.Max(value, k_MinTolerance); } /// /// Returns the number of chain in the solver. /// /// This always returns 1. - protected override int GetChainCount() - { - return 1; - } + protected override int GetChainCount() => 1; /// /// Gets the chain in the solver by index. /// /// Chain index. /// Returns IKChain2D at the index. - public override IKChain2D GetChain(int index) - { - return m_Chain; - } + public override IKChain2D GetChain(int index) => m_Chain; /// /// DoPrepare override from base class. @@ -75,11 +73,12 @@ protected override void DoPrepare() m_WorldPositions = new Vector3[m_Chain.transformCount]; } - for (int i = 0; i < m_Chain.transformCount; ++i) + for (var i = 0; i < m_Chain.transformCount; ++i) { m_Positions[i] = GetPointOnSolverPlane(m_Chain.transforms[i].position); } - for (int i = 0; i < m_Chain.transformCount - 1; ++i) + + for (var i = 0; i < m_Chain.transformCount - 1; ++i) { m_Lengths[i] = (m_Positions[i + 1] - m_Positions[i]).magnitude; } @@ -91,22 +90,22 @@ protected override void DoPrepare() /// Target position for the chain. protected override void DoUpdateIK(List effectorPositions) { - Profiler.BeginSample("FABRIKSolver2D.DoUpdateIK"); + Profiler.BeginSample(nameof(FabrikSolver2D.DoUpdateIK)); - Vector3 effectorPosition = effectorPositions[0]; + var effectorPosition = effectorPositions[0]; effectorPosition = GetPointOnSolverPlane(effectorPosition); if (FABRIK2D.Solve(effectorPosition, iterations, tolerance, m_Lengths, ref m_Positions)) { // Convert all plane positions to world positions - for (int i = 0; i < m_Positions.Length; ++i) + for (var i = 0; i < m_Positions.Length; ++i) { m_WorldPositions[i] = GetWorldPositionFromSolverPlanePoint(m_Positions[i]); } - for (int i = 0; i < m_Chain.transformCount - 1; ++i) + for (var i = 0; i < m_Chain.transformCount - 1; ++i) { - Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition; - Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_WorldPositions[i + 1]); + var startLocalPosition = m_Chain.transforms[i + 1].localPosition; + var endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_WorldPositions[i + 1]); m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition); } } @@ -114,4 +113,4 @@ protected override void DoUpdateIK(List effectorPositions) Profiler.EndSample(); } } -} +} \ No newline at end of file diff --git a/IK/Runtime/IKChain2D.cs b/IK/Runtime/IKChain2D.cs index c6a1f439..f20c3055 100644 --- a/IK/Runtime/IKChain2D.cs +++ b/IK/Runtime/IKChain2D.cs @@ -11,18 +11,25 @@ namespace UnityEngine.U2D.IK [Serializable] public class IKChain2D { - [SerializeField][FormerlySerializedAs("m_Target")] - private Transform m_EffectorTransform; - [SerializeField][FormerlySerializedAs("m_Effector")] - private Transform m_TargetTransform; [SerializeField] - private int m_TransformCount; + [FormerlySerializedAs("m_Target")] + Transform m_EffectorTransform; + + [SerializeField] + [FormerlySerializedAs("m_Effector")] + Transform m_TargetTransform; + [SerializeField] - private Transform[] m_Transforms; + int m_TransformCount; + [SerializeField] - private Quaternion[] m_DefaultLocalRotations; + Transform[] m_Transforms; + + [SerializeField] + Quaternion[] m_DefaultLocalRotations; + [SerializeField] - private Quaternion[] m_StoredLocalRotations; + Quaternion[] m_StoredLocalRotations; /// /// Lengths of IK Chain. @@ -35,8 +42,8 @@ public class IKChain2D /// public Transform effector { - get { return m_EffectorTransform; } - set { m_EffectorTransform = value; } + get => m_EffectorTransform; + set => m_EffectorTransform = value; } /// @@ -44,17 +51,14 @@ public Transform effector /// public Transform target { - get { return m_TargetTransform; } - set { m_TargetTransform = value; } + get => m_TargetTransform; + set => m_TargetTransform = value; } /// /// Get the Unity Transforms that are in the IK Chain. /// - public Transform[] transforms - { - get { return m_Transforms; } - } + public Transform[] transforms => m_Transforms; /// /// Get the root Unity Transform for the IK Chain. @@ -68,8 +72,8 @@ public Transform rootTransform return null; } } - - private Transform lastTransform + + Transform lastTransform { get { @@ -84,17 +88,14 @@ private Transform lastTransform /// public int transformCount { - get { return m_TransformCount; } - set { m_TransformCount = Mathf.Max(0, value); } + get => m_TransformCount; + set => m_TransformCount = Mathf.Max(0, value); } /// /// Returns true if the IK Chain is valid. False otherwise. /// - public bool isValid - { - get { return Validate(); } - } + public bool isValid => Validate(); /// /// Gets the length of the IK Chain. @@ -103,7 +104,7 @@ public float[] lengths { get { - if(isValid) + if (isValid) { PrepareLengths(); return m_Lengths; @@ -113,7 +114,7 @@ public float[] lengths } } - private bool Validate() + bool Validate() { if (effector == null) return false; @@ -123,15 +124,13 @@ private bool Validate() return false; if (m_DefaultLocalRotations == null || m_DefaultLocalRotations.Length != transformCount) return false; - if (m_StoredLocalRotations == null || m_StoredLocalRotations.Length != transformCount) + if (m_StoredLocalRotations == null || m_StoredLocalRotations.Length != transformCount) return false; if (rootTransform == null) return false; if (lastTransform != effector) return false; - if (target && IKUtility.IsDescendentOf(target, rootTransform)) - return false; - return true; + return !target || !IKUtility.IsDescendentOf(target, rootTransform); } /// @@ -147,7 +146,7 @@ public void Initialize() m_StoredLocalRotations = new Quaternion[transformCount]; var currentTransform = effector; - int index = transformCount - 1; + var index = transformCount - 1; while (currentTransform && index >= 0) { @@ -159,10 +158,10 @@ public void Initialize() } } - private void PrepareLengths() + void PrepareLengths() { var currentTransform = effector; - int index = transformCount - 1; + var index = transformCount - 1; if (m_Lengths == null || m_Lengths.Length != transformCount - 1) m_Lengths = new float[transformCount - 1]; @@ -183,8 +182,8 @@ private void PrepareLengths() /// True to constrain the target rotation. False otherwise. public void RestoreDefaultPose(bool targetRotationIsConstrained) { - var count = targetRotationIsConstrained ? transformCount : transformCount-1; - for (int i = 0; i < count; ++i) + var count = targetRotationIsConstrained ? transformCount : transformCount - 1; + for (var i = 0; i < count; ++i) m_Transforms[i].localRotation = m_DefaultLocalRotations[i]; } @@ -193,7 +192,7 @@ public void RestoreDefaultPose(bool targetRotationIsConstrained) /// public void StoreLocalRotations() { - for (int i = 0; i < m_Transforms.Length; ++i) + for (var i = 0; i < m_Transforms.Length; ++i) m_StoredLocalRotations[i] = m_Transforms[i].localRotation; } @@ -204,9 +203,9 @@ public void StoreLocalRotations() /// True to constrain target rotation. False otherwise. public void BlendFkToIk(float finalWeight, bool targetRotationIsConstrained) { - var count = targetRotationIsConstrained ? transformCount : transformCount-1; - for (int i = 0; i < count; ++i) + var count = targetRotationIsConstrained ? transformCount : transformCount - 1; + for (var i = 0; i < count; ++i) m_Transforms[i].localRotation = Quaternion.Slerp(m_StoredLocalRotations[i], m_Transforms[i].localRotation, finalWeight); } } -} +} \ No newline at end of file diff --git a/IK/Runtime/IKManager2D.cs b/IK/Runtime/IKManager2D.cs index a51a54a0..37af7e01 100644 --- a/IK/Runtime/IKManager2D.cs +++ b/IK/Runtime/IKManager2D.cs @@ -15,47 +15,45 @@ namespace UnityEngine.U2D.IK public partial class IKManager2D : MonoBehaviour, IPreviewable { [SerializeField] - private List m_Solvers = new List(); - [SerializeField][Range(0f, 1f)] - private float m_Weight = 1f; + List m_Solvers = new List(); + [SerializeField] + [Range(0f, 1f)] + float m_Weight = 1f; /// /// Get and Set the weight for solvers. /// public float weight { - get { return m_Weight; } - set { m_Weight = Mathf.Clamp01(value); } + get => m_Weight; + set => m_Weight = Mathf.Clamp01(value); } /// /// Get the Solvers that are managed by this manager. /// - public List solvers - { - get { return m_Solvers; } - } + public List solvers => m_Solvers; - private void OnValidate() + void OnValidate() { m_Weight = Mathf.Clamp01(m_Weight); OnEditorDataValidate(); } - private void Reset() + void Reset() { FindChildSolvers(); OnEditorDataValidate(); } - private void FindChildSolvers() + void FindChildSolvers() { m_Solvers.Clear(); - List solvers = new List(); + var solvers = new List(); transform.GetComponentsInChildren(true, solvers); - foreach (Solver2D solver in solvers) + foreach (var solver in solvers) { if (solver.GetComponentInParent() == this) AddSolver(solver); @@ -102,6 +100,7 @@ public void UpdateManager() solver.UpdateIK(weight); } + profilerMarker.End(); } @@ -111,22 +110,25 @@ public void UpdateManager() /// public void OnPreviewUpdate() { -#if UNITY_EDITOR - if(IsInGUIUpdateLoop()) +#if UNITY_EDITOR + if (IsInGUIUpdateLoop()) UpdateManager(); #endif } - static bool IsInGUIUpdateLoop() => Event.current != null; - - private void LateUpdate() + static bool IsInGUIUpdateLoop() => Event.current != null; + + void LateUpdate() { UpdateManager(); } #if UNITY_EDITOR internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent(); - private void OnDrawGizmos() { onDrawGizmos.Invoke(); } + void OnDrawGizmos() + { + onDrawGizmos.Invoke(); + } #endif } -} +} \ No newline at end of file diff --git a/IK/Runtime/IKManager2DEditorData.cs b/IK/Runtime/IKManager2DEditorData.cs index 14dd0a1d..1a8d9554 100644 --- a/IK/Runtime/IKManager2DEditorData.cs +++ b/IK/Runtime/IKManager2DEditorData.cs @@ -11,13 +11,7 @@ internal struct SolverEditorData { public Color color; public bool showGizmo; - public static SolverEditorData defaultValue - { - get - { - return new SolverEditorData(){ color = Color.green, showGizmo = true}; - } - } + public static SolverEditorData defaultValue => new SolverEditorData() { color = Color.green, showGizmo = true }; } [SerializeField] @@ -26,7 +20,7 @@ public static SolverEditorData defaultValue void OnEditorDataValidate() { var solverDataLength = m_SolverEditorData.Count; - for (int i = solverDataLength; i < m_Solvers.Count; ++i) + for (var i = solverDataLength; i < m_Solvers.Count; ++i) { AddSolverEditorData(); } @@ -37,11 +31,11 @@ internal SolverEditorData GetSolverEditorData(Solver2D solver) var index = m_Solvers.FindIndex(x => x == solver); if (index >= 0) { - if(index >= m_SolverEditorData.Count) + if (index >= m_SolverEditorData.Count) OnEditorDataValidate(); return m_SolverEditorData[index]; } - + return SolverEditorData.defaultValue; } @@ -57,14 +51,14 @@ void AddSolverEditorData() void RemoveSolverEditorData(Solver2D solver) { var index = m_Solvers.FindIndex(x => x == solver); - if(index >= 0) + if (index >= 0) m_SolverEditorData.RemoveAt(index); } + #else - void OnEditorDataValidate(){} - void AddSolverEditorData(){} - void RemoveSolverEditorData(Solver2D solver){} + void OnEditorDataValidate() { } + void AddSolverEditorData() { } + void RemoveSolverEditorData(Solver2D solver) { } #endif } -} - +} \ No newline at end of file diff --git a/IK/Runtime/IKUtility.cs b/IK/Runtime/IKUtility.cs index 988cd038..2c282049 100644 --- a/IK/Runtime/IKUtility.cs +++ b/IK/Runtime/IKUtility.cs @@ -18,7 +18,7 @@ public static bool IsDescendentOf(Transform transform, Transform ancestor) { Debug.Assert(transform != null, "Transform is null"); - Transform currentParent = transform.parent; + var currentParent = transform.parent; while (currentParent) { @@ -40,7 +40,7 @@ public static int GetAncestorCount(Transform transform) { Debug.Assert(transform != null, "Transform is null"); - int ancestorCount = 0; + var ancestorCount = 0; while (transform.parent) { @@ -59,7 +59,7 @@ public static int GetAncestorCount(Transform transform) /// Integer value for the maximum chain count. public static int GetMaxChainCount(IKChain2D chain) { - int maxChainCount = 0; + var maxChainCount = 0; if (chain.effector) maxChainCount = GetAncestorCount(chain.effector) + 1; @@ -67,4 +67,4 @@ public static int GetMaxChainCount(IKChain2D chain) return maxChainCount; } } -} +} \ No newline at end of file diff --git a/IK/Runtime/LimbSolver2D.cs b/IK/Runtime/LimbSolver2D.cs index e3ba243f..1ee46b27 100644 --- a/IK/Runtime/LimbSolver2D.cs +++ b/IK/Runtime/LimbSolver2D.cs @@ -12,21 +12,22 @@ namespace UnityEngine.U2D.IK public sealed class LimbSolver2D : Solver2D { [SerializeField] - private IKChain2D m_Chain = new IKChain2D(); + IKChain2D m_Chain = new IKChain2D(); [SerializeField] - private bool m_Flip; - private Vector3[] m_Positions = new Vector3[3]; - private float[] m_Lengths = new float[2]; - private float[] m_Angles = new float[2]; + bool m_Flip; + + Vector3[] m_Positions = new Vector3[3]; + float[] m_Lengths = new float[2]; + float[] m_Angles = new float[2]; /// /// Get Set for flip property. /// public bool flip { - get { return m_Flip; } - set { m_Flip = value; } + get => m_Flip; + set => m_Flip = value; } /// @@ -42,20 +43,14 @@ protected override void DoInitialize() /// Override base class GetChainCount. /// /// Always returns 1. - protected override int GetChainCount() - { - return 1; - } + protected override int GetChainCount() => 1; /// /// Override base class GetChain. /// /// Index to query. /// Returns IKChain2D for the Solver. - public override IKChain2D GetChain(int index) - { - return m_Chain; - } + public override IKChain2D GetChain(int index) => m_Chain; /// /// Override base class DoPrepare. @@ -71,22 +66,22 @@ protected override void DoPrepare() } /// - /// OVerride base class DoUpdateIK. + /// Override base class DoUpdateIK. /// /// List of effector positions. protected override void DoUpdateIK(List effectorPositions) { - Vector3 effectorPosition = effectorPositions[0]; - Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition); + var effectorPosition = effectorPositions[0]; + var effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition); effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D); if (effectorLocalPosition2D.sqrMagnitude > 0f && Limb.Solve(effectorPosition, m_Lengths, m_Positions, ref m_Angles)) { - float flipSign = flip ? -1f : 1f; + var flipSign = flip ? -1f : 1f; m_Chain.transforms[0].localRotation *= Quaternion.FromToRotation(Vector3.right, effectorLocalPosition2D) * Quaternion.FromToRotation(m_Chain.transforms[1].localPosition, Vector3.right); m_Chain.transforms[0].localRotation *= Quaternion.AngleAxis(flipSign * m_Angles[0], Vector3.forward); m_Chain.transforms[1].localRotation *= Quaternion.FromToRotation(Vector3.right, m_Chain.transforms[1].InverseTransformPoint(effectorPosition)) * Quaternion.FromToRotation(m_Chain.transforms[2].localPosition, Vector3.right); } } } -} +} \ No newline at end of file diff --git a/IK/Runtime/Solver2D.cs b/IK/Runtime/Solver2D.cs index 16269f43..058300be 100644 --- a/IK/Runtime/Solver2D.cs +++ b/IK/Runtime/Solver2D.cs @@ -13,31 +13,31 @@ namespace UnityEngine.U2D.IK public abstract class Solver2D : MonoBehaviour, IPreviewable { [SerializeField] - private bool m_ConstrainRotation = true; + bool m_ConstrainRotation = true; + [FormerlySerializedAs("m_RestoreDefaultPose")] [SerializeField] - private bool m_SolveFromDefaultPose = true; - [SerializeField][Range(0f, 1f)] - private float m_Weight = 1f; + bool m_SolveFromDefaultPose = true; + + [SerializeField] + [Range(0f, 1f)] + float m_Weight = 1f; - private Plane m_Plane; - private List m_TargetPositions = new List(); + Plane m_Plane; + List m_TargetPositions = new List(); /// /// Returns the number of IKChain2D in the solver. /// - public int chainCount - { - get { return GetChainCount(); } - } + public int chainCount => GetChainCount(); /// /// Get Set for rotation constrain property. /// public bool constrainRotation { - get { return m_ConstrainRotation; } - set { m_ConstrainRotation = value; } + get => m_ConstrainRotation; + set => m_ConstrainRotation = value; } /// @@ -45,37 +45,29 @@ public bool constrainRotation /// public bool solveFromDefaultPose { - get { return m_SolveFromDefaultPose; } - set { m_SolveFromDefaultPose = value; } + get => m_SolveFromDefaultPose; + set => m_SolveFromDefaultPose = value; } /// /// Returns true if the Solver2D is in a valid state. /// - public bool isValid - { - get { return Validate(); } - } + public bool isValid => Validate(); /// /// Returns true if all chains in the Solver has a target. /// - public bool allChainsHaveTargets - { - get { return HasTargets(); } - } + public bool allChainsHaveTargets => HasTargets(); /// /// Get and Set Solver weights. /// public float weight { - get { return m_Weight; } - set { m_Weight = Mathf.Clamp01(value); } + get => m_Weight; + set => m_Weight = Mathf.Clamp01(value); } - private void OnEnable() {} - /// /// Validate and initialize the Solver. /// @@ -87,25 +79,27 @@ protected virtual void OnValidate() Initialize(); } - private bool Validate() + bool Validate() { - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (!chain.isValid) return false; } + return DoValidate(); } - private bool HasTargets() + bool HasTargets() { - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (chain.target == null) return false; } + return true; } @@ -116,14 +110,14 @@ public void Initialize() { DoInitialize(); - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); chain.Initialize(); } } - private void Prepare() + void Prepare() { var rootTransform = GetPlaneRootTransform(); if (rootTransform != null) @@ -132,7 +126,7 @@ private void Prepare() m_Plane.distance = -Vector3.Dot(m_Plane.normal, rootTransform.position); } - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); var constrainTargetRotation = constrainRotation && chain.target != null; @@ -144,11 +138,11 @@ private void Prepare() DoPrepare(); } - private void PrepareEffectorPositions() + void PrepareEffectorPositions() { m_TargetPositions.Clear(); - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); @@ -158,12 +152,12 @@ private void PrepareEffectorPositions() } /// - /// Perfom Solver IK update. + /// Perform Solver IK update. /// /// Weight for position solving. public void UpdateIK(float globalWeight) { - if(allChainsHaveTargets) + if (allChainsHaveTargets) { PrepareEffectorPositions(); UpdateIK(m_TargetPositions, globalWeight); @@ -177,10 +171,10 @@ public void UpdateIK(float globalWeight) /// Weight for position solving. public void UpdateIK(List positions, float globalWeight) { - if(positions.Count != chainCount) + if (positions.Count != chainCount) return; - float finalWeight = globalWeight * weight; + var finalWeight = globalWeight * weight; if (finalWeight == 0f) return; @@ -196,7 +190,7 @@ public void UpdateIK(List positions, float globalWeight) if (constrainRotation) { - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); @@ -209,18 +203,18 @@ public void UpdateIK(List positions, float globalWeight) BlendFkToIk(finalWeight); } - private void StoreLocalRotations() + void StoreLocalRotations() { - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); chain.StoreLocalRotations(); } } - private void BlendFkToIk(float finalWeight) + void BlendFkToIk(float finalWeight) { - for (int i = 0; i < GetChainCount(); ++i) + for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); var constrainTargetRotation = constrainRotation && chain.target != null; @@ -236,7 +230,7 @@ private void BlendFkToIk(float finalWeight) public abstract IKChain2D GetChain(int index); /// - /// OVerride to return the number of chains in the Solver + /// Override to return the number of chains in the Solver /// /// Integer represents IKChain2D count. protected abstract int GetChainCount(); @@ -251,17 +245,17 @@ private void BlendFkToIk(float finalWeight) /// Override to perform custom validation. /// /// Returns true if the Solver is in a valid state. False otherwise. - protected virtual bool DoValidate() { return true; } + protected virtual bool DoValidate() => true; /// /// Override to perform initialize the solver /// - protected virtual void DoInitialize() {} + protected virtual void DoInitialize() { } /// /// Override to prepare the solver for update /// - protected virtual void DoPrepare() {} + protected virtual void DoPrepare() { } /// /// Override to return the root Unity Transform of the Solver. The default implementation returns the root @@ -270,9 +264,7 @@ protected virtual void DoPrepare() {} /// Unity Transform that represents the root. protected virtual Transform GetPlaneRootTransform() { - if (chainCount > 0) - return GetChain(0).rootTransform; - return null; + return chainCount > 0 ? GetChain(0).rootTransform : null; } /// @@ -300,4 +292,4 @@ protected Vector3 GetWorldPositionFromSolverPlanePoint(Vector2 planePoint) /// public void OnPreviewUpdate() { } } -} +} \ No newline at end of file diff --git a/IK/Runtime/Solver2DMenuAttribute.cs b/IK/Runtime/Solver2DMenuAttribute.cs index 6135c10a..aea443c4 100644 --- a/IK/Runtime/Solver2DMenuAttribute.cs +++ b/IK/Runtime/Solver2DMenuAttribute.cs @@ -16,10 +16,7 @@ public sealed class Solver2DMenuAttribute : Attribute /// /// Menu path. /// - public string menuPath - { - get { return m_MenuPath; } - } + public string menuPath => m_MenuPath; /// /// Constructor @@ -30,4 +27,4 @@ public Solver2DMenuAttribute(string _menuPath) m_MenuPath = _menuPath; } } -} +} \ No newline at end of file diff --git a/IK/Runtime/Solvers/CCD2D.cs b/IK/Runtime/Solvers/CCD2D.cs index 837d6da2..6d80283d 100644 --- a/IK/Runtime/Solvers/CCD2D.cs +++ b/IK/Runtime/Solvers/CCD2D.cs @@ -20,10 +20,10 @@ public static class CCD2D /// Returns true if solver successfully completes within iteration limit. False otherwise. public static bool Solve(Vector3 targetPosition, Vector3 forward, int solverLimit, float tolerance, float velocity, ref Vector3[] positions) { - int last = positions.Length - 1; - int iterations = 0; - float sqrTolerance = tolerance * tolerance; - float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude; + var last = positions.Length - 1; + var iterations = 0; + var sqrTolerance = tolerance * tolerance; + var sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude; while (sqrDistanceToTarget > sqrTolerance) { DoIteration(targetPosition, forward, last, velocity, ref positions); @@ -31,30 +31,31 @@ public static bool Solve(Vector3 targetPosition, Vector3 forward, int solverLimi if (++iterations >= solverLimit) break; } + return iterations != 0; } static void DoIteration(Vector3 targetPosition, Vector3 forward, int last, float velocity, ref Vector3[] positions) { - for (int i = last - 1; i >= 0; --i) + for (var i = last - 1; i >= 0; --i) { - Vector3 toTarget = targetPosition - positions[i]; - Vector3 toLast = positions[last] - positions[i]; + var toTarget = targetPosition - positions[i]; + var toLast = positions[last] - positions[i]; - float angle = Vector3.SignedAngle(toLast, toTarget, forward); + var angle = Vector3.SignedAngle(toLast, toTarget, forward); angle = Mathf.Lerp(0f, angle, velocity); - Quaternion deltaRotation = Quaternion.AngleAxis(angle, forward); - for (int j = last; j > i; --j) + var deltaRotation = Quaternion.AngleAxis(angle, forward); + for (var j = last; j > i; --j) positions[j] = RotatePositionFrom(positions[j], positions[i], deltaRotation); } } static Vector3 RotatePositionFrom(Vector3 position, Vector3 pivot, Quaternion rotation) { - Vector3 v = position - pivot; + var v = position - pivot; v = rotation * v; return pivot + v; } } -} +} \ No newline at end of file diff --git a/IK/Runtime/Solvers/FABRIK2D.cs b/IK/Runtime/Solvers/FABRIK2D.cs index db5211ec..63323b91 100644 --- a/IK/Runtime/Solvers/FABRIK2D.cs +++ b/IK/Runtime/Solvers/FABRIK2D.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using UnityEngine.Scripting.APIUpdating; namespace UnityEngine.U2D.IK @@ -11,49 +12,43 @@ public struct FABRIKChain2D /// /// Returns the first element's position. /// - public Vector2 first - { - get { return positions[0]; } - } + public Vector2 first => positions[0]; /// /// Returns the last element's position. /// - public Vector2 last - { - get { return positions[positions.Length - 1]; } - } + public Vector2 last => positions[^1]; /// /// Position of the origin. /// public Vector2 origin; - + /// /// Position of the target which is used to indicate the desired position for the Effector. /// public Vector2 target; - + /// /// Target position's tolerance (squared). /// public float sqrTolerance; - + /// /// Array of chain positions. /// public Vector2[] positions; - + /// /// Array of chain lengths. /// public float[] lengths; - + /// /// Sub-Chain indices. /// public int[] subChainIndices; - + /// /// Array of world positions. /// @@ -76,11 +71,11 @@ public static class FABRIK2D /// Returns true if solver successfully completes within iteration limit. False otherwise. public static bool Solve(Vector2 targetPosition, int solverLimit, float tolerance, float[] lengths, ref Vector2[] positions) { - int last = positions.Length - 1; - int iterations = 0; - float sqrTolerance = tolerance * tolerance; - float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude; - Vector2 originPosition = positions[0]; + var last = positions.Length - 1; + var iterations = 0; + var sqrTolerance = tolerance * tolerance; + var sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude; + var originPosition = positions[0]; while (sqrDistanceToTarget > sqrTolerance) { Forward(targetPosition, lengths, ref positions); @@ -107,13 +102,15 @@ public static bool SolveChain(int solverLimit, ref FABRIKChain2D[] chains) return false; // Validation failed, solve chain - for (int iterations = 0; iterations < solverLimit; ++iterations) + for (var iterations = 0; iterations < solverLimit; ++iterations) { SolveForwardsChain(0, ref chains); + // Break if solution is solved if (!SolveBackwardsChain(0, ref chains)) break; } + return true; } @@ -124,6 +121,7 @@ static bool ValidateChain(FABRIKChain2D[] chains) if (chain.subChainIndices.Length == 0 && (chain.target - chain.last).sqrMagnitude > chain.sqrTolerance) return false; } + return true; } @@ -133,40 +131,44 @@ static void SolveForwardsChain(int idx, ref FABRIKChain2D[] chains) if (chains[idx].subChainIndices.Length > 0) { target = Vector2.zero; - for (int i = 0; i < chains[idx].subChainIndices.Length; ++i) + for (var i = 0; i < chains[idx].subChainIndices.Length; ++i) { var childIdx = chains[idx].subChainIndices[i]; SolveForwardsChain(childIdx, ref chains); target += chains[childIdx].first; } - target = target / chains[idx].subChainIndices.Length; + + target /= chains[idx].subChainIndices.Length; } + Forward(target, chains[idx].lengths, ref chains[idx].positions); } static bool SolveBackwardsChain(int idx, ref FABRIKChain2D[] chains) { - bool notSolved = false; + var notSolved = false; Backward(chains[idx].origin, chains[idx].lengths, ref chains[idx].positions); - for (int i = 0; i < chains[idx].subChainIndices.Length; ++i) + for (var i = 0; i < chains[idx].subChainIndices.Length; ++i) { var childIdx = chains[idx].subChainIndices[i]; chains[childIdx].origin = chains[idx].last; notSolved |= SolveBackwardsChain(childIdx, ref chains); } + // Check if end point has reached the target if (chains[idx].subChainIndices.Length == 0) { notSolved |= (chains[idx].target - chains[idx].last).sqrMagnitude > chains[idx].sqrTolerance; } + return notSolved; } - static void Forward(Vector2 targetPosition, float[] lengths, ref Vector2[] positions) + static void Forward(Vector2 targetPosition, IList lengths, ref Vector2[] positions) { var last = positions.Length - 1; positions[last] = targetPosition; - for (int i = last - 1; i >= 0; --i) + for (var i = last - 1; i >= 0; --i) { var r = positions[i + 1] - positions[i]; var l = lengths[i] / r.magnitude; @@ -175,11 +177,11 @@ static void Forward(Vector2 targetPosition, float[] lengths, ref Vector2[] posit } } - static void Backward(Vector2 originPosition, float[] lengths, ref Vector2[] positions) + static void Backward(Vector2 originPosition, IList lengths, ref Vector2[] positions) { positions[0] = originPosition; var last = positions.Length - 1; - for (int i = 0; i < last; ++i) + for (var i = 0; i < last; ++i) { var r = positions[i + 1] - positions[i]; var l = lengths[i] / r.magnitude; @@ -204,7 +206,8 @@ static Vector2 ValidateJoint(Vector2 endPosition, Vector2 startPosition, Vector2 var maxRotation = Quaternion.Euler(0f, 0f, max); validatedPosition = startPosition + (Vector2)(maxRotation * right * localDifference.magnitude); } + return validatedPosition; } } -} +} \ No newline at end of file diff --git a/IK/Runtime/Solvers/Limb.cs b/IK/Runtime/Solvers/Limb.cs index 8fc404fc..4a5bae4f 100644 --- a/IK/Runtime/Solvers/Limb.cs +++ b/IK/Runtime/Solvers/Limb.cs @@ -24,17 +24,17 @@ public static bool Solve(Vector3 targetPosition, float[] lengths, Vector3[] posi if (lengths[0] == 0f || lengths[1] == 0f) return false; - Vector3 startToEnd = targetPosition - positions[0]; - float distanceMagnitude = startToEnd.magnitude; - float sqrDistance = startToEnd.sqrMagnitude; + var startToEnd = targetPosition - positions[0]; + var distanceMagnitude = startToEnd.magnitude; + var sqrDistance = startToEnd.sqrMagnitude; - float sqrParentLength = (lengths[0] * lengths[0]); - float sqrTargetLength = (lengths[1] * lengths[1]); + var sqrParentLength = (lengths[0] * lengths[0]); + var sqrTargetLength = (lengths[1] * lengths[1]); - float angle0Cos = (sqrDistance + sqrParentLength - sqrTargetLength) / (2f * lengths[0] * distanceMagnitude); - float angle1Cos = (sqrDistance - sqrParentLength - sqrTargetLength) / (2f * lengths[0] * lengths[1]); + var angle0Cos = (sqrDistance + sqrParentLength - sqrTargetLength) / (2f * lengths[0] * distanceMagnitude); + var angle1Cos = (sqrDistance - sqrParentLength - sqrTargetLength) / (2f * lengths[0] * lengths[1]); - if ((angle0Cos >= -1f && angle0Cos <= 1f) && (angle1Cos >= -1f && angle1Cos <= 1f)) + if (angle0Cos >= -1f && angle0Cos <= 1f && angle1Cos >= -1f && angle1Cos <= 1f) { outAngles[0] = Mathf.Acos(angle0Cos) * Mathf.Rad2Deg; outAngles[1] = Mathf.Acos(angle1Cos) * Mathf.Rad2Deg; @@ -43,4 +43,4 @@ public static bool Solve(Vector3 targetPosition, float[] lengths, Vector3[] posi return true; } } -} +} \ No newline at end of file diff --git a/Runtime/SpriteSkin.cs b/Runtime/SpriteSkin.cs index 9a455227..283b7527 100644 --- a/Runtime/SpriteSkin.cs +++ b/Runtime/SpriteSkin.cs @@ -5,6 +5,7 @@ using UnityEngine.Scripting; using UnityEngine.U2D.Common; using Unity.Collections; +using Unity.Collections.LowLevel.Unsafe; using Unity.Profiling; using UnityEngine.Rendering; using UnityEngine.Scripting.APIUpdating; @@ -50,7 +51,7 @@ internal struct PositionTangentVertex [IconAttribute(IconUtility.IconPath + "Animation.SpriteSkin.png")] [MovedFrom("UnityEngine.U2D.Experimental.Animation")] [HelpURL("https://docs.unity3d.com/Packages/com.unity.2d.animation@latest/index.html?subfolder=/manual/SpriteSkin.html")] - public sealed partial class SpriteSkin : MonoBehaviour, IPreviewable, ISerializationCallbackReceiver + public sealed class SpriteSkin : MonoBehaviour, IPreviewable, ISerializationCallbackReceiver { static class Profiling { @@ -60,6 +61,12 @@ static class Profiling public static readonly ProfilerMarker getSpriteBonesTransformFromPath = new ProfilerMarker("SpriteSkin.GetSpriteBoneTransformsFromPath"); } + struct TransformData + { + public string fullName; + public Transform transform; + } + [SerializeField] Transform m_RootBone; [SerializeField] @@ -80,9 +87,28 @@ static class Profiling bool m_ForceSkinning; bool m_IsValid = false; int m_TransformsHash = 0; - - internal Dictionary> hierarchyCache => m_HierarchyCache; + + int m_TransformId; + NativeArray m_BoneTransformId; + int m_RootBoneTransformId; + NativeCustomSlice m_SpriteUVs; + NativeCustomSlice m_SpriteVertices; + NativeCustomSlice m_SpriteTangents; + NativeCustomSlice m_SpriteBoneWeights; + NativeCustomSlice m_SpriteBindPoses; + NativeCustomSlice m_BoneTransformIdNativeSlice; + bool m_SpriteHasTangents; + int m_SpriteVertexStreamSize; + int m_SpriteVertexCount; + int m_SpriteTangentVertexOffset; + int m_DataIndex = -1; + bool m_BoneCacheUpdateToDate = false; + Dictionary> m_HierarchyCache = new Dictionary>(); + + internal Sprite sprite => spriteRenderer.sprite; + internal SpriteRenderer spriteRenderer => m_SpriteRenderer; + internal NativeCustomSlice spriteBoneWeights => m_SpriteBoneWeights; internal bool autoRebind { @@ -93,20 +119,67 @@ internal bool autoRebind CacheHierarchy(); CacheCurrentSprite(m_AutoRebind); } - } + + /// + /// Returns the Transform Components that is used for deformation. + /// + /// An array of Transform Components. + public Transform[] boneTransforms + { + get => m_BoneTransforms; + internal set + { + m_BoneTransforms = value; + CacheValidFlag(); + OnBoneTransformChanged(); + } + } + + /// + /// Returns the Transform Component that represents the root bone for deformation. + /// + /// A Transform Component. + public Transform rootBone + { + get => m_RootBone; + internal set + { + m_RootBone = value; + CacheValidFlag(); + CacheHierarchy(); + OnRootBoneTransformChanged(); + } + } + + internal Bounds bounds + { + get => m_Bounds; + set => m_Bounds = value; + } + + /// + /// Determines if the SpriteSkin executes even if the associated + /// SpriteRenderer has been culled from view. + /// + public bool alwaysUpdate + { + get => m_AlwaysUpdate; + set => m_AlwaysUpdate = value; + } + + internal bool isValid => this.Validate() == SpriteSkinValidationResult.Ready; #if UNITY_EDITOR internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent(); void OnDrawGizmos() { onDrawGizmos.Invoke(); } - bool m_IgnoreNextSpriteChange = true; internal bool ignoreNextSpriteChange { - get => m_IgnoreNextSpriteChange; - set => m_IgnoreNextSpriteChange = value; - } + get; + set; + } = true; #endif int GetSpriteInstanceID() @@ -130,18 +203,88 @@ void OnEnable() OnEnableBatch(); } + + void OnEnableBatch() + { + m_TransformId = gameObject.transform.GetInstanceID(); + UpdateSpriteDeform(); + + CacheBoneTransformIds(true); + SpriteSkinComposite.instance.AddSpriteSkin(this); + } + + void OnResetBatch() + { + CacheBoneTransformIds(true); + SpriteSkinComposite.instance.CopyToSpriteSkinData(this); + } + + void OnDisableBatch() + { + RemoveTransformFromSpriteSkinComposite(); + SpriteSkinComposite.instance.RemoveSpriteSkin(this); + } + + void OnBoneTransformChanged() + { + if (enabled) + CacheBoneTransformIds(true); + } + + void OnRootBoneTransformChanged() + { + if (enabled) + CacheBoneTransformIds(true); + } + + void OnDestroy() + { + DeactivateSkinning(); + } + + /// + /// Called before object is serialized. + /// + public void OnBeforeSerialize() + { + OnBeforeSerializeBatch(); + } + + /// + /// Called after object is deserialized. + /// + public void OnAfterDeserialize() + { + OnAfterSerializeBatch(); + } + + void OnBeforeSerializeBatch() {} + + void OnAfterSerializeBatch() + { +#if UNITY_EDITOR + m_BoneCacheUpdateToDate = false; +#endif + } internal void OnEditorEnable() { Awake(); } - + void CacheValidFlag() { m_IsValid = isValid; if(!m_IsValid) DeactivateSkinning(); } + + internal bool BatchValidate() + { + CacheBoneTransformIds(); + CacheCurrentSprite(m_AutoRebind); + return (m_IsValid && spriteRenderer.enabled && (alwaysUpdate || spriteRenderer.isVisible)); + } void Reset() { @@ -152,6 +295,80 @@ void Reset() OnResetBatch(); } } + + void CacheBoneTransformIds(bool forceUpdate = false) + { + if (!m_BoneCacheUpdateToDate || forceUpdate) + { + SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId); + if (rootBone != null) + { + m_RootBoneTransformId = rootBone.GetInstanceID(); + if (enabled) + SpriteSkinComposite.instance.AddSpriteSkinRootBoneTransform(this); + } + else + m_RootBoneTransformId = 0; + + if (boneTransforms != null) + { + var boneCount = 0; + for (var i = 0; i < boneTransforms.Length; ++i) + { + if (boneTransforms[i] != null) + ++boneCount; + } + + if (m_BoneTransformId.IsCreated) + { + for (var i = 0; i < m_BoneTransformId.Length; ++i) + SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]); + NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, boneCount); + } + else + { + m_BoneTransformId = new NativeArray(boneCount, Allocator.Persistent); + } + + m_BoneTransformIdNativeSlice = new NativeCustomSlice(m_BoneTransformId); + for (int i = 0, j = 0; i < boneTransforms.Length; ++i) + { + if (boneTransforms[i] != null) + { + m_BoneTransformId[j] = boneTransforms[i].GetInstanceID(); + ++j; + } + } + if (enabled) + { + SpriteSkinComposite.instance.AddSpriteSkinBoneTransform(this); + } + } + else + { + if (m_BoneTransformId.IsCreated) + NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, 0); + else + m_BoneTransformId = new NativeArray(0, Allocator.Persistent); + } + CacheValidFlag(); + m_BoneCacheUpdateToDate = true; + SpriteSkinComposite.instance.CopyToSpriteSkinData(this); + } + } + + void RemoveTransformFromSpriteSkinComposite() + { + if (m_BoneTransformId.IsCreated) + { + for (var i = 0; i < m_BoneTransformId.Length; ++i) + SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]); + m_BoneTransformId.Dispose(); + } + SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId); + m_RootBoneTransformId = -1; + m_BoneCacheUpdateToDate = false; + } internal NativeByteArray GetDeformedVertices(int spriteVertexCount) { @@ -293,11 +510,6 @@ public void OnPreviewUpdate() } static bool IsInGUIUpdateLoop() => Event.current != null; - - internal void OnLateUpdate() - { - Deform(); - } void Deform() { @@ -342,46 +554,66 @@ void CacheCurrentSprite(bool rebind) } } } - - internal Sprite sprite => spriteRenderer.sprite; - - internal SpriteRenderer spriteRenderer => m_SpriteRenderer; - - /// - /// Returns the Transform Components that is used for deformation. - /// - /// An array of Transform Components. - public Transform[] boneTransforms + + void UpdateSpriteDeform() { - get => m_BoneTransforms; - internal set + if (sprite == null) { - m_BoneTransforms = value; - CacheValidFlag(); - OnBoneTransformChanged(); + m_SpriteUVs = NativeCustomSlice.Default(); + m_SpriteVertices = NativeCustomSlice.Default(); + m_SpriteTangents = NativeCustomSlice.Default(); + m_SpriteBoneWeights = NativeCustomSlice.Default(); + m_SpriteBindPoses = NativeCustomSlice.Default(); + m_SpriteHasTangents = false; + m_SpriteVertexStreamSize = 0; + m_SpriteVertexCount = 0; + m_SpriteTangentVertexOffset = 0; } - } - - /// - /// Returns the Transform Component that represents the root bone for deformation. - /// - /// A Transform Component. - public Transform rootBone - { - get => m_RootBone; - internal set + else { - m_RootBone = value; - CacheValidFlag(); - CacheHierarchy(); - OnRootBoneTransformChanged(); + m_SpriteUVs = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.TexCoord0)); + m_SpriteVertices = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.Position)); + m_SpriteTangents = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.Tangent)); + m_SpriteBoneWeights = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.BlendWeight)); + m_SpriteBindPoses = new NativeCustomSlice(sprite.GetBindPoses()); + m_SpriteHasTangents = sprite.HasVertexAttribute(VertexAttribute.Tangent); + m_SpriteVertexStreamSize = sprite.GetVertexStreamSize(); + m_SpriteVertexCount = sprite.GetVertexCount(); + m_SpriteTangentVertexOffset = sprite.GetVertexStreamOffset(VertexAttribute.Tangent); } + SpriteSkinComposite.instance.CopyToSpriteSkinData(this); + } + + internal void CopyToSpriteSkinData(ref SpriteSkinData data, int spriteSkinIndex) + { + CacheBoneTransformIds(); + CacheCurrentSprite(m_AutoRebind); + + data.vertices = m_SpriteVertices; + data.boneWeights = m_SpriteBoneWeights; + data.bindPoses = m_SpriteBindPoses; + data.tangents = m_SpriteTangents; + data.hasTangents = m_SpriteHasTangents; + data.spriteVertexStreamSize = m_SpriteVertexStreamSize; + data.spriteVertexCount = m_SpriteVertexCount; + data.tangentVertexOffset = m_SpriteTangentVertexOffset; + data.transformId = m_TransformId; + data.boneTransformId = m_BoneTransformIdNativeSlice; + m_DataIndex = spriteSkinIndex; } - internal struct TransformData + internal bool NeedUpdateCompositeCache() { - public string fullName; - public Transform transform; + unsafe + { + var iptr = new IntPtr(sprite.GetVertexAttribute(VertexAttribute.TexCoord0).GetUnsafeReadOnlyPtr()); + var rs = m_SpriteUVs.data != iptr; + if (rs) + { + UpdateSpriteDeform(); + } + return rs; + } } void CacheHierarchy() @@ -442,22 +674,6 @@ static string GenerateTransformPath(Transform rootBone, Transform child) return path; } - internal Bounds bounds - { - get => m_Bounds; - set => m_Bounds = value; - } - - /// - /// Determines if the SpriteSkin executes even if the associated - /// SpriteRenderer has been culled from view. - /// - public bool alwaysUpdate - { - get => m_AlwaysUpdate; - set => m_AlwaysUpdate = value; - } - internal static bool GetSpriteBonesTransforms(SpriteSkin spriteSkin, out Transform[] outTransform) { var rootBone = spriteSkin.rootBone; @@ -490,7 +706,7 @@ internal static bool GetSpriteBonesTransforms(SpriteSkin spriteSkin, out Transfo } } - var hierarchyCache = spriteSkin.hierarchyCache; + var hierarchyCache = spriteSkin.m_HierarchyCache; if (hierarchyCache.Count == 0) spriteSkin.CacheHierarchy(); @@ -558,13 +774,6 @@ static void CalculateBoneTransformsPath(int index, SpriteBone[] spriteBones, str else paths[index] = bonePath; } - - internal bool isValid => this.Validate() == SpriteSkinValidationResult.Ready; - - void OnDestroy() - { - DeactivateSkinning(); - } internal void DeactivateSkinning() { @@ -580,21 +789,5 @@ internal void ResetSprite() m_CurrentDeformSprite = 0; CacheValidFlag(); } - - /// - /// Called before object is serialized. - /// - public void OnBeforeSerialize() - { - OnBeforeSerializeBatch(); - } - - /// - /// Called after object is deserialized. - /// - public void OnAfterDeserialize() - { - OnAfterSerializeBatch(); - } } } diff --git a/Runtime/SpriteSkinBatch.cs b/Runtime/SpriteSkinBatch.cs deleted file mode 100644 index 4b9079ac..00000000 --- a/Runtime/SpriteSkinBatch.cs +++ /dev/null @@ -1,215 +0,0 @@ -using System; -using UnityEngine.Rendering; -using Unity.Collections; -using Unity.Collections.LowLevel.Unsafe; -using UnityEngine.U2D.Common; - -namespace UnityEngine.U2D.Animation -{ - public sealed partial class SpriteSkin : MonoBehaviour, IPreviewable - { - int m_TransformId; - NativeArray m_BoneTransformId; - int m_RootBoneTransformId; - NativeCustomSlice m_SpriteUVs; - NativeCustomSlice m_SpriteVertices; - NativeCustomSlice m_SpriteTangents; - NativeCustomSlice m_SpriteBoneWeights; - NativeCustomSlice m_SpriteBindPoses; - NativeCustomSlice m_BoneTransformIdNativeSlice; - bool m_SpriteHasTangents; - int m_SpriteVertexStreamSize; - int m_SpriteVertexCount; - int m_SpriteTangentVertexOffset; - int m_DataIndex = -1; - bool m_BoneCacheUpdateToDate = false; - - void OnEnableBatch() - { - m_TransformId = gameObject.transform.GetInstanceID(); - UpdateSpriteDeform(); - - CacheBoneTransformIds(true); - SpriteSkinComposite.instance.AddSpriteSkin(this); - } - - void OnResetBatch() - { - CacheBoneTransformIds(true); - SpriteSkinComposite.instance.CopyToSpriteSkinData(this); - } - - void OnDisableBatch() - { - RemoveTransformFromSpriteSkinComposite(); - SpriteSkinComposite.instance.RemoveSpriteSkin(this); - } - - internal void UpdateSpriteDeform() - { - if (sprite == null) - { - m_SpriteUVs = NativeCustomSlice.Default(); - m_SpriteVertices = NativeCustomSlice.Default(); - m_SpriteTangents = NativeCustomSlice.Default(); - m_SpriteBoneWeights = NativeCustomSlice.Default(); - m_SpriteBindPoses = NativeCustomSlice.Default(); - m_SpriteHasTangents = false; - m_SpriteVertexStreamSize = 0; - m_SpriteVertexCount = 0; - m_SpriteTangentVertexOffset = 0; - } - else - { - m_SpriteUVs = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.TexCoord0)); - m_SpriteVertices = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.Position)); - m_SpriteTangents = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.Tangent)); - m_SpriteBoneWeights = new NativeCustomSlice(sprite.GetVertexAttribute(VertexAttribute.BlendWeight)); - m_SpriteBindPoses = new NativeCustomSlice(sprite.GetBindPoses()); - m_SpriteHasTangents = sprite.HasVertexAttribute(VertexAttribute.Tangent); - m_SpriteVertexStreamSize = sprite.GetVertexStreamSize(); - m_SpriteVertexCount = sprite.GetVertexCount(); - m_SpriteTangentVertexOffset = sprite.GetVertexStreamOffset(VertexAttribute.Tangent); - } - SpriteSkinComposite.instance.CopyToSpriteSkinData(this); - } - - void CacheBoneTransformIds(bool forceUpdate = false) - { - if (!m_BoneCacheUpdateToDate || forceUpdate) - { - SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId); - if (rootBone != null) - { - m_RootBoneTransformId = rootBone.GetInstanceID(); - if (this.enabled) - SpriteSkinComposite.instance.AddSpriteSkinRootBoneTransform(this); - } - else - m_RootBoneTransformId = 0; - - if (boneTransforms != null) - { - var boneCount = 0; - for (var i = 0; i < boneTransforms.Length; ++i) - { - if (boneTransforms[i] != null) - ++boneCount; - } - - if (m_BoneTransformId.IsCreated) - { - for (var i = 0; i < m_BoneTransformId.Length; ++i) - SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]); - NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, boneCount); - } - else - { - m_BoneTransformId = new NativeArray(boneCount, Allocator.Persistent); - } - - m_BoneTransformIdNativeSlice = new NativeCustomSlice(m_BoneTransformId); - for (int i = 0, j = 0; i < boneTransforms.Length; ++i) - { - if (boneTransforms[i] != null) - { - m_BoneTransformId[j] = boneTransforms[i].GetInstanceID(); - ++j; - } - } - if (this.enabled) - { - SpriteSkinComposite.instance.AddSpriteSkinBoneTransform(this); - } - } - else - { - if (m_BoneTransformId.IsCreated) - NativeArrayHelpers.ResizeIfNeeded(ref m_BoneTransformId, 0); - else - m_BoneTransformId = new NativeArray(0, Allocator.Persistent); - } - CacheValidFlag(); - m_BoneCacheUpdateToDate = true; - SpriteSkinComposite.instance.CopyToSpriteSkinData(this); - } - } - - void RemoveTransformFromSpriteSkinComposite() - { - if (m_BoneTransformId.IsCreated) - { - for (var i = 0; i < m_BoneTransformId.Length; ++i) - SpriteSkinComposite.instance.RemoveTransformById(m_BoneTransformId[i]); - m_BoneTransformId.Dispose(); - } - SpriteSkinComposite.instance.RemoveTransformById(m_RootBoneTransformId); - m_RootBoneTransformId = -1; - m_BoneCacheUpdateToDate = false; - } - - internal void CopyToSpriteSkinData(ref SpriteSkinData data, int spriteSkinIndex) - { - CacheBoneTransformIds(); - CacheCurrentSprite(m_AutoRebind); - - data.vertices = m_SpriteVertices; - data.boneWeights = m_SpriteBoneWeights; - data.bindPoses = m_SpriteBindPoses; - data.tangents = m_SpriteTangents; - data.hasTangents = m_SpriteHasTangents; - data.spriteVertexStreamSize = m_SpriteVertexStreamSize; - data.spriteVertexCount = m_SpriteVertexCount; - data.tangentVertexOffset = m_SpriteTangentVertexOffset; - data.transformId = m_TransformId; - data.boneTransformId = m_BoneTransformIdNativeSlice; - m_DataIndex = spriteSkinIndex; - } - - internal bool NeedUpdateCompositeCache() - { - unsafe - { - var iptr = new IntPtr(sprite.GetVertexAttribute(VertexAttribute.TexCoord0).GetUnsafeReadOnlyPtr()); - var rs = m_SpriteUVs.data != iptr; - if (rs) - { - UpdateSpriteDeform(); - } - return rs; - } - } - - internal bool BatchValidate() - { - CacheBoneTransformIds(); - CacheCurrentSprite(m_AutoRebind); - return (m_IsValid && spriteRenderer.enabled && (alwaysUpdate || spriteRenderer.isVisible)); - } - - void OnBoneTransformChanged() - { - if (this.enabled) - { - CacheBoneTransformIds(true); - } - } - - void OnRootBoneTransformChanged() - { - if (this.enabled) - { - CacheBoneTransformIds(true); - } - } - - void OnBeforeSerializeBatch() {} - - void OnAfterSerializeBatch() - { -#if UNITY_EDITOR - m_BoneCacheUpdateToDate = false; -#endif - } - } -} diff --git a/Runtime/SpriteSkinBatch.cs.meta b/Runtime/SpriteSkinBatch.cs.meta deleted file mode 100644 index 4d102a4e..00000000 --- a/Runtime/SpriteSkinBatch.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8092360c71284424f8739b0eb0b2a04a -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/SpriteSkinUtility.cs b/Runtime/SpriteSkinUtility.cs index 0ec235b8..54f48fa9 100644 --- a/Runtime/SpriteSkinUtility.cs +++ b/Runtime/SpriteSkinUtility.cs @@ -1,4 +1,5 @@ using System; +using Unity.Burst; using Unity.Collections; using Unity.Mathematics; using Unity.Collections.LowLevel.Unsafe; @@ -15,6 +16,7 @@ internal enum SpriteSkinValidationResult InvalidTransformArray, InvalidTransformArrayLength, TransformArrayContainsNull, + InvalidBoneWeights, Ready } @@ -44,8 +46,9 @@ internal static SpriteSkinValidationResult Validate(this SpriteSkin spriteSkin) return SpriteSkinValidationResult.SpriteNotFound; var bindPoses = spriteSkin.spriteRenderer.sprite.GetBindPoses(); + var bindPoseCount = bindPoses.Length; - if (bindPoses.Length == 0) + if (bindPoseCount == 0) return SpriteSkinValidationResult.SpriteHasNoSkinningInformation; if (spriteSkin.rootBone == null) @@ -54,7 +57,7 @@ internal static SpriteSkinValidationResult Validate(this SpriteSkin spriteSkin) if (spriteSkin.boneTransforms == null) return SpriteSkinValidationResult.InvalidTransformArray; - if (bindPoses.Length != spriteSkin.boneTransforms.Length) + if (bindPoseCount != spriteSkin.boneTransforms.Length) return SpriteSkinValidationResult.InvalidTransformArrayLength; foreach (var boneTransform in spriteSkin.boneTransforms) @@ -63,6 +66,10 @@ internal static SpriteSkinValidationResult Validate(this SpriteSkin spriteSkin) return SpriteSkinValidationResult.TransformArrayContainsNull; } + var boneWeights = spriteSkin.spriteBoneWeights; + if (!BurstedSpriteSkinUtilities.ValidateBoneWeights(in boneWeights, bindPoseCount)) + return SpriteSkinValidationResult.InvalidBoneWeights; + return SpriteSkinValidationResult.Ready; } @@ -396,4 +403,30 @@ internal static unsafe void UpdateBounds(this SpriteSkin spriteSkin, NativeArray InternalEngineBridge.SetLocalAABB(spriteSkin.spriteRenderer, spriteSkin.bounds); } } + + [BurstCompile] + internal static class BurstedSpriteSkinUtilities + { + [BurstCompile] + internal static bool ValidateBoneWeights(in NativeCustomSlice boneWeights, int bindPoseCount) + { + var boneWeightCount = boneWeights.Length; + for (var i = 0; i < boneWeightCount; ++i) + { + var boneWeight = boneWeights[i]; + var idx0 = boneWeight.boneIndex0; + var idx1 = boneWeight.boneIndex1; + var idx2 = boneWeight.boneIndex2; + var idx3 = boneWeight.boneIndex3; + + if ((idx0 < 0 || idx0 >= bindPoseCount) || + (idx1 < 0 || idx1 >= bindPoseCount) || + (idx2 < 0 || idx2 >= bindPoseCount) || + (idx3 < 0 || idx3 >= bindPoseCount)) + return false; + } + + return true; + } + } } diff --git a/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapFullSkin.cs b/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapFullSkin.cs index 7e039612..a2c49eff 100644 --- a/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapFullSkin.cs +++ b/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapFullSkin.cs @@ -1,7 +1,10 @@ using System.Collections.Generic; using UnityEngine; using UnityEngine.U2D.Animation; + +#if UGUI_ENABLED using UnityEngine.UI; +#endif namespace Unity.U2D.Animation.Sample { @@ -9,7 +12,10 @@ internal class SwapFullSkin : MonoBehaviour { public SpriteLibraryAsset[] spriteLibraries; public SpriteLibrary spriteLibraryTarget; + +#if UGUI_ENABLED public Dropdown dropDownSelection; +#endif // Start is called before the first frame update void Start() @@ -24,6 +30,7 @@ void OnDropDownValueChanged(int value) internal void UpdateSelectionChoice() { +#if UGUI_ENABLED dropDownSelection.ClearOptions(); var options = new List(spriteLibraries.Length); for (int i = 0; i < spriteLibraries.Length; ++i) @@ -32,6 +39,7 @@ internal void UpdateSelectionChoice() } dropDownSelection.options = options; dropDownSelection.onValueChanged.AddListener(OnDropDownValueChanged); +#endif } } diff --git a/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapPart.cs b/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapPart.cs index 38c0f34e..ecb471a8 100644 --- a/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapPart.cs +++ b/Samples~/AnimationSamples/4 SpriteSwap/Scripts/Runtime/SwapPart.cs @@ -3,14 +3,19 @@ using System.Linq; using UnityEngine; using UnityEngine.U2D.Animation; + +#if UGUI_ENABLED using UnityEngine.UI; +#endif namespace Unity.U2D.Animation.Sample { [Serializable] internal struct SwapOptionData { +#if UGUI_ENABLED public Dropdown dropdown; +#endif public SpriteResolver spriteResolver; public string category; } @@ -22,6 +27,7 @@ internal class SwapPart : MonoBehaviour void Start() { +#if UGUI_ENABLED foreach (var swapOption in swapOptionData) { swapOption.dropdown.ClearOptions(); @@ -38,6 +44,7 @@ void Start() swapOption.spriteResolver.SetCategoryAndLabel(swapOption.category, swapOption.dropdown.options[x].text); }); } +#endif } } diff --git a/package.json b/package.json index f995dd62..cac107a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.unity.2d.animation", - "version": "9.0.0-pre.3", + "version": "9.0.0", "unity": "2022.2", "displayName": "2D Animation", "description": "2D Animation provides all the necessary tooling and runtime components for skeletal animation using Sprites.", @@ -10,16 +10,16 @@ ], "category": "2D", "dependencies": { - "com.unity.2d.common": "8.0.0-pre.2", + "com.unity.2d.common": "8.0.0", "com.unity.2d.sprite": "1.0.0", "com.unity.collections": "1.1.0", "com.unity.modules.animation": "1.0.0", "com.unity.modules.uielements": "1.0.0" }, "relatedPackages": { - "com.unity.2d.animation.tests": "9.0.0-pre.3", - "com.unity.2d.common.tests": "8.0.0-pre.2", - "com.unity.2d.psdimporter": "8.0.0-pre.3" + "com.unity.2d.animation.tests": "9.0.0", + "com.unity.2d.common.tests": "8.0.0", + "com.unity.2d.psdimporter": "8.0.0" }, "samples": [ { @@ -28,15 +28,12 @@ "path": "Samples~/AnimationSamples" } ], - "_upm": { - "changelog": "### Changed\n- Update dependency package version." - }, "upmCi": { - "footprint": "48a997150b9f09c6910a63c9873dd866bdfdeeb5" + "footprint": "40f8174d7ca26ac59a7648f5de5d8628328b719c" }, "repository": { "url": "https://github.cds.internal.unity3d.com/unity/2d.git", "type": "git", - "revision": "d97a6f27300ed84e1bdc517d87f4ad1f60a9a77f" + "revision": "fe67218b0ec831b0675f9a28f140da4a0bc88bda" } }