testss
281
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/CHANGELOG.md
Normal file
@@ -0,0 +1,281 @@
|
||||
# Changelog
|
||||
|
||||
## [6.0.0] - 2021-03-17
|
||||
### Changed
|
||||
- Update version for release
|
||||
|
||||
## [6.0.0-pre.3] - 2021-02-28
|
||||
### Fixed
|
||||
- 1294930 Exception thrown continuously on creating Range in the preset of Sprite Shape when Undo/Redo operation is performed earlier
|
||||
- 1303998 Enabling Fill Tessellation on controller and setting the profile's fill offset to negative causes errors
|
||||
- 1293760 Sprite Shape generates Edge Colliders with deformed corners
|
||||
- 1305867 Sprite shape edge collider has a gap at end point if optimise collider is disabled
|
||||
- 1286378 Sprite Shape incorrect normal generation
|
||||
|
||||
## [6.0.0-pre.2] - 2020-11-25
|
||||
### Changed
|
||||
- Update license file
|
||||
|
||||
### Fixed
|
||||
- 1273635 Fixed error when adding AngleRange to SpriteShapeProfile Preset that was reset before.
|
||||
- 1287237 Fixed ArgumentException when tangents and cache geometry are enabled on SpriteShapeController component.
|
||||
- 1240514 Fixed InvalidOperationException thrown continuously on adding SpriteShapeController component to a GameObject with SpriteRenderer.
|
||||
- 1284920 Fixed PolygonCollider2D generated with a single vertex when a GameObject has a SpriteShapeController with just 3 vertices.
|
||||
|
||||
## [6.0.0-pre.1] - 2020-10-30
|
||||
### Changed
|
||||
- Version bump for Unity 2021.1
|
||||
- Height is interpolated linearly between control points that are both linear and smoothly if otherwise.
|
||||
|
||||
## [5.1.0] - 2020-09-24
|
||||
### Added
|
||||
- Added C# Job Tessellation support for Fill Area of SpriteShape.
|
||||
|
||||
### Fixed
|
||||
- 1274400 SpriteShape Bounding Box does not take into account certain vertices
|
||||
- 1273705 Assertion failed exception is thrown on undoing after clicking on Create Range button
|
||||
- 1273635 Errors occurs when adding range on Reset-ed Preset of the SpriteShapeProfile
|
||||
- 1271817 Icon is missing on creating SpriteShapeProfile at the time of creating
|
||||
- 1280016 Unable to create Sprite Shape Profile along with ArgumentNullException thrown in the Project window
|
||||
- 1274776 NullReferenceException thrown on performing Redo operation after creating Range property in the SpriteShape profiler preset
|
||||
|
||||
## [5.0.2] - 2020-08-31
|
||||
### Fixed
|
||||
- 1267542 Sprite Variant Window does not appear in Sprite Shape Controller Component when selecting a Spline pivot point.
|
||||
- 1265846 Dragging Sprite Shape Profile to Hierarchy creates a Game Object in main Scene when being in Prefab Mode
|
||||
|
||||
## [5.0.1] - 2020-07-17
|
||||
### Changed
|
||||
- If Geometry is baked using SpriteShapeGeometryCache, do not check for dirty once data is updated to prevent GC.
|
||||
- Updated Help Section to point to the correct URLs.
|
||||
|
||||
### Fixed
|
||||
- 1242910 Unable to add item on Resetting the Preset of the SpriteShapeProfile
|
||||
- 1256914 Exception thrown continuously when Undo operation is performed with sprites are assigned earlier
|
||||
- 1263266 BakeCollider requires GC every frame even when there are no changes in SpriteShape
|
||||
|
||||
## [5.0.0] - 2020-05-28
|
||||
### Added
|
||||
- Sample script GenerateSpriteShapes.cs to demonstrate force generating invisible SpriteShapes on runtime scene load.
|
||||
|
||||
### Changed
|
||||
- Version bump for Unity 2020.2
|
||||
|
||||
### Fixed
|
||||
- 1246133 Error occurs when unselecting Cache Geometry for Sprite Shape prefab
|
||||
- 1240380 OnGUI in SpriteShapeController creates GC allocs.
|
||||
- 1235972 "A Native Collection has not been disposed, resulting in a memory leak" is thrown when 2D Sprite Shape Controller is disabled
|
||||
- 1240514 InvalidOperationException thrown continuously on adding "Sprite Shape Controller" Component to a Sprite object
|
||||
- 1241841 Disabled corner option does not work on existing spriteshape upgraded from a previous version
|
||||
|
||||
## [4.1.1] - 2020-04-20
|
||||
### Added
|
||||
- Added BakeMesh to save generated geometry data.
|
||||
- Added warning when a valid SpriteShape profile is not set.
|
||||
|
||||
## [4.1.0] - 2020-03-16
|
||||
### Added
|
||||
- Stretched Corners.
|
||||
|
||||
### Fixed
|
||||
- 1226841 Fix when Collider generation allocation.
|
||||
- 1226856 SpriteShape Edge Collider does not extend to End-point even if Edges dont overlap.
|
||||
- 1226847 SpriteShape Corner Threshold does not work.
|
||||
|
||||
|
||||
## [4.0.3] - 2020-03-09
|
||||
### Fixed
|
||||
- 1220091 SpriteShapeController leaks memory when zero control points are used
|
||||
- 1216990 Colliders should also respect Pivot property of Edge Sprites.
|
||||
- 1225366 Ensure SpriteShape are generated when not in view on Runtime.
|
||||
|
||||
## [4.0.2] - 2020-02-11
|
||||
### Changed
|
||||
- Improved Memory Allocations.
|
||||
|
||||
### Fixed
|
||||
- Fixed OnDrawGizmos to Get/Release RenderTexture through CommandBuffer.
|
||||
|
||||
## [4.0.1] - 2019-11-26
|
||||
### Changed
|
||||
- Updated License file
|
||||
- Updated Third Party Notices file
|
||||
- Changed how Samples are installed into user's project
|
||||
|
||||
### Fixed
|
||||
- Fixed where the last point of the Sprite Shape does not behave correctly when using Continuous Points in a closed shape (case 1184721)
|
||||
|
||||
## [4.0.0] - 2019-11-06
|
||||
### Changed
|
||||
- Update version number for Unity 2020.1
|
||||
|
||||
## [3.0.7] - 2019-10-27
|
||||
### Fixed
|
||||
- Added missing meta file
|
||||
|
||||
### Changed
|
||||
- Update com.unity.2d.path package dependency
|
||||
|
||||
## [3.0.6] - 2019-09-27
|
||||
### Added
|
||||
- Added support to set CornerAngleThreshold.
|
||||
- Burst is now enabled for performance boost.
|
||||
### Fixed
|
||||
- Fix (Case 1041062) Inputting Point Position manually causes mesh to not conform to the spline
|
||||
- Fix GC in confirming Spline Extras sample.
|
||||
- Fix hash Validation errors.
|
||||
- Removed resources from Packages.
|
||||
|
||||
## [3.0.5] - 2019-09-05
|
||||
### Fixed
|
||||
- Fix (Case 1159767) Error generated when using a default sprite for Corner sprite or Angle Range sprite in Sprite Shape Profile
|
||||
- Fix (Case 1178579) "ArgumentOutofRangeException" is thrown and SpriteShapeProfile freezes on reset
|
||||
|
||||
## [3.0.4] - 2019-08-09
|
||||
### Added
|
||||
- Added tangent channel support for proper 2D lighting in URP.
|
||||
|
||||
## [3.0.3] - 2019-07-24
|
||||
### Added
|
||||
- Add related test packages
|
||||
|
||||
## [3.0.2] - 2019-07-13
|
||||
### Changed
|
||||
- Update to latest Mathematics package version
|
||||
|
||||
## [3.0.1] - 2019-07-13
|
||||
### Changed
|
||||
- Mark package to support Unity 2019.3.0a10 onwards.
|
||||
|
||||
## [3.0.0] - 2019-06-19
|
||||
### Changed
|
||||
- Stable Version.
|
||||
- Remove experimental namespace.
|
||||
|
||||
## [2.1.0-preview.8] - 2019-06-12
|
||||
### Changed
|
||||
- Fix (Case 1152342) The first point of the Sprite Shape does not behave correctly when using Continuous Points
|
||||
- Fix (Case 1160009) Edge and Polygon Collider does not seem to follow the spriteshape for some broken mirrored tangent points
|
||||
- Fix (Case 1157201) Edge Sprite Material changed when using a fill texture that is already an edge sprite on spriteshape
|
||||
- Fix (Case 1162134) Open ended Spriteshape renders the fill texture instead of the range sprite
|
||||
|
||||
## [2.1.0-preview.7] - 2019-06-02
|
||||
### Changed
|
||||
- Fix Variant Selection.
|
||||
|
||||
## [2.1.0-preview.6] - 2019-06-02
|
||||
### Changed
|
||||
- Fix Null reference exception caused by SplineEditorCache changes.
|
||||
- Fill Inspector changes due to Path integration.
|
||||
|
||||
## [2.1.0-preview.4] - 2019-05-28
|
||||
### Changed
|
||||
- Upgrade Mathematics package.
|
||||
- Use path editor.
|
||||
|
||||
## [2.1.0-preview.2] - 2019-05-13
|
||||
### Changed
|
||||
- Initial version for 2019.2
|
||||
- Update for common package.
|
||||
|
||||
## [2.0.0-preview.8] - 2019-05-16
|
||||
### Fixed
|
||||
- Fixed issue when sprites are re-ordered in Angle Range.
|
||||
- Updated Samples.
|
||||
|
||||
## [2.0.0-preview.7] - 2019-05-10
|
||||
### Fixed
|
||||
- Version Update and fixes.
|
||||
|
||||
## [2.0.0-preview.6] - 2019-05-08
|
||||
### Fixed
|
||||
- Added Sprite Variant Selector.
|
||||
- Fix Variant Bug (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-6#post-4480936)
|
||||
- Fix (Case 1146747) SpriteShape generating significant GC allocations every frame (OnWillRenderObject)
|
||||
|
||||
## [2.0.0-preview.5] - 2019-04-18
|
||||
### Fixed
|
||||
- Shape angle does not show the accurate sprite on certain parts of the shape.
|
||||
- SpriteShape - Unable to use the Depth buffer (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-6#post-4413142)
|
||||
|
||||
## [2.0.0-preview.4] - 2019-03-28
|
||||
### Changed
|
||||
- Disable burst for now until we have a final release.
|
||||
|
||||
## [2.0.0-preview.3] - 2019-03-25
|
||||
### Fixed
|
||||
- Update Common version.
|
||||
|
||||
## [2.0.0-preview.2] - 2019-03-08
|
||||
### Fixed
|
||||
- Fix Edge Case Scenario where Vertices along Continuous segment could be duplicated..
|
||||
- Ensure that Collider uses a valid Sprite on Generation.
|
||||
|
||||
## [2.0.0-preview.1] - 2019-02-27
|
||||
### Changed
|
||||
- Updated version.
|
||||
|
||||
## [1.1.0-preview.1] - 2019-02-10
|
||||
### Added
|
||||
- Spriteshape tessellation code is re-implemented in C# Jobs and utilizes Burst for Performance.
|
||||
- Added Mirrored and Non-Mirrored continous Tangent mode.
|
||||
- Simplified Collider Generation support and is part of C# Job/Burst for performance.
|
||||
- Added Shortcut Keys (for setting Tangentmode, Sprite Variant and Mirror Tangent).
|
||||
- Ability to drag Spriteshape Profile form Project view to Hierarchy to create Sprite Shape in Scene.
|
||||
- Simplified Corner mode for Points and is now enabled by default.
|
||||
- Added Stretch UV support for Fill Area.
|
||||
- Added Color property to SpriteShapeRenderer.
|
||||
|
||||
### Fixed
|
||||
- SpriteShapeController shows wrong Sprites after deleting a sprite from the top angle range.
|
||||
- Empty SpriteShapeController still seem to show the previous Spriteshape drawcalls
|
||||
- Streched Sprites are generated in between non Linked Points
|
||||
- Corners sprites are no longer usable if user only sets the corners for the bottom
|
||||
- Sprites in SpriteShape still shows even after user deletes the SpriteShape Profile
|
||||
- SpriteShape doesn't update Point Positions visually at runtime for Builds
|
||||
- Spriteshape Colliders does not update in scene immediately
|
||||
- Fixed constant Mesh baking (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-4#post-3925789)
|
||||
- Fixed Bounds generation issue (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-5#post-4079857)
|
||||
- Sprite Shape Profile component breaks when creating range
|
||||
- Fixed when sprite is updated in the sprite editor, the spriteshape is not updated.
|
||||
- Fixed cases where Spline Edit is disabled even when points are selected. (https://forum.unity.com/threads/spriteshape-preview-package.522575/#post-3436940)
|
||||
- Sprite with SpriteShapeBody Shader gets graphical artifacts when rotating the camera.
|
||||
- When multiple SpriteShapes are selected, Edit Spline button is now disabled. (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-3#post-3764413)
|
||||
- Fixed texelSize property (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-4#post-3877081)
|
||||
- Fixed Collider generation for different quality levels. (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-4#post-3956062)
|
||||
- Fixed Framing Issues (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-5#post-4137214)
|
||||
- Fixed Collider generation for Offsets (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-5#post-4149841)
|
||||
- Fixed Collider generation for different Heights (https://forum.unity.com/threads/spriteshape-preview-package.522575/page-5#post-4190116)
|
||||
|
||||
### Changed
|
||||
- SpriteShape Asset parameters WorldSpace UV, PixelPerUnit have been moved to SpriteShapeController properties.
|
||||
- Collider generation has been simplified and aligns well with the generated geometry (different height, corners etc.)
|
||||
|
||||
### Removed
|
||||
- Remove redundant parameters BevelCutoff and BevelSize that can be done by simply modifying source spline.
|
||||
|
||||
## [1.0.12-preview.1] - 2018-08-03
|
||||
### Added
|
||||
- Fix issue where Point Positions do not update visually at runtime for Builds
|
||||
|
||||
## [1.0.11-preview] - 2018-06-20
|
||||
### Added
|
||||
- Fix Spriteshape does not update when Sprites are reimported.
|
||||
- Fix SpriteShapeController in Scene view shows a different sprite when user reapplies a Sprite import settings
|
||||
- Fix Editor Crashed when user adjusts the "Bevel Cutoff" value
|
||||
- Fix Crash when changing Spline Control Points for a Sprite Shape Controller in debug Inspector
|
||||
- Fix SpriteShape generation when End-points are Broken.
|
||||
- Fix cases where the UV continuity is broken even when the Control point is continous.
|
||||
|
||||
## [1.0.10-preview] - 2018-04-12
|
||||
### Added
|
||||
- Version number format changed to -preview
|
||||
|
||||
## [0.1.0] - 2017-11-20
|
||||
### Added
|
||||
- Bezier Spline Shape
|
||||
- Corner Sprites
|
||||
- Edge variations
|
||||
- Point scale
|
||||
- SpriteShapeRenderer with support for masking
|
||||
- Auto update collision shape
|
@@ -0,0 +1,400 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.U2D.SpriteShape
|
||||
{
|
||||
public interface IAngleRangeCache
|
||||
{
|
||||
List<AngleRange> angleRanges { get; }
|
||||
int selectedIndex { get; set; }
|
||||
float previewAngle { get; set; }
|
||||
void RegisterUndo(string name);
|
||||
}
|
||||
|
||||
public class AngleRangeController
|
||||
{
|
||||
public event Action selectionChanged = () => { };
|
||||
public IAngleRangeCache cache { get; set; }
|
||||
public IAngleRangeView view { get; set; }
|
||||
public float angleOffset { get; set; }
|
||||
public float radius { get; set; }
|
||||
public Rect rect { get; set; }
|
||||
public bool snap { get; set; }
|
||||
public Color gradientMin { get; set; }
|
||||
public Color gradientMid { get; set; }
|
||||
public Color gradientMax { get; set; }
|
||||
|
||||
public AngleRange selectedAngleRange
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(cache != null);
|
||||
|
||||
AngleRange angleRange = null;
|
||||
|
||||
if (cache.selectedIndex >= 0 && cache.selectedIndex < cache.angleRanges.Count)
|
||||
return cache.angleRanges[cache.selectedIndex];
|
||||
|
||||
return angleRange;
|
||||
}
|
||||
}
|
||||
|
||||
private AngleRange hoveredAngleRange
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(cache != null);
|
||||
Debug.Assert(view != null);
|
||||
|
||||
AngleRange angleRange = null;
|
||||
|
||||
if (view.hoveredRangeIndex >= 0 && view.hoveredRangeIndex < cache.angleRanges.Count)
|
||||
return cache.angleRanges[view.hoveredRangeIndex];
|
||||
|
||||
return angleRange;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
Debug.Assert(cache != null);
|
||||
|
||||
view.SetupLayout(rect, angleOffset, radius);
|
||||
|
||||
DoAngleRanges();
|
||||
HandleSelectAngleRange();
|
||||
HandleCreateRange();
|
||||
HandlePreviewSelector();
|
||||
HandleRemoveRange();
|
||||
}
|
||||
|
||||
private void DoAngleRanges()
|
||||
{
|
||||
var removeInvalid = false;
|
||||
|
||||
if (view.IsActionTriggering(AngleRangeAction.ModifyRange))
|
||||
cache.RegisterUndo("Modify Range");
|
||||
|
||||
if (view.IsActionFinishing(AngleRangeAction.ModifyRange))
|
||||
removeInvalid = true;
|
||||
|
||||
var index = 0;
|
||||
foreach (var angleRange in cache.angleRanges)
|
||||
{
|
||||
var start = angleRange.start;
|
||||
var end = angleRange.end;
|
||||
var isSelected = selectedAngleRange != null && selectedAngleRange == angleRange;
|
||||
|
||||
if (view.DoAngleRange(index, rect, radius, angleOffset, ref start, ref end, snap, !isSelected, gradientMin, gradientMid, gradientMax))
|
||||
SetRange(angleRange, start, end);
|
||||
|
||||
++index;
|
||||
}
|
||||
|
||||
if (removeInvalid)
|
||||
RemoveInvalidRanges();
|
||||
}
|
||||
|
||||
public void RemoveInvalidRanges()
|
||||
{
|
||||
var toDelete = new List<AngleRange>();
|
||||
|
||||
foreach (var angleRange in cache.angleRanges)
|
||||
{
|
||||
var start = angleRange.start;
|
||||
var end = angleRange.end;
|
||||
|
||||
if (start >= end)
|
||||
toDelete.Add(angleRange);
|
||||
}
|
||||
|
||||
foreach (var angleRange in toDelete)
|
||||
cache.angleRanges.Remove(angleRange);
|
||||
|
||||
if (toDelete.Count > 0)
|
||||
{
|
||||
SetSelectedIndexFromPreviewAngle();
|
||||
view.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelectAngleRange()
|
||||
{
|
||||
int newSelected;
|
||||
if (view.DoSelectAngleRange(cache.selectedIndex, out newSelected))
|
||||
{
|
||||
cache.RegisterUndo("Select Angle Range");
|
||||
cache.previewAngle = view.GetAngleFromPosition(rect, angleOffset);
|
||||
SelectIndex(newSelected);
|
||||
}
|
||||
|
||||
if (view.IsActionActive(AngleRangeAction.SelectRange))
|
||||
{
|
||||
if (hoveredAngleRange == null)
|
||||
return;
|
||||
|
||||
view.DrawAngleRangeOutline(rect, hoveredAngleRange.start, hoveredAngleRange.end, angleOffset, radius);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCreateRange()
|
||||
{
|
||||
if (!view.IsActionActive(AngleRangeAction.CreateRange))
|
||||
return;
|
||||
|
||||
var angle = view.GetAngleFromPosition(rect, angleOffset);
|
||||
var start = 0f;
|
||||
var end = 0f;
|
||||
var canCreate = GetNewRangeBounds(angle, out start, out end);
|
||||
|
||||
if (canCreate && view.DoCreateRange(rect, radius, angleOffset, start, end))
|
||||
{
|
||||
CreateRangeAtAngle(angle);
|
||||
cache.previewAngle = angle;
|
||||
SetSelectedIndexFromPreviewAngle();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePreviewSelector()
|
||||
{
|
||||
if (view.IsActionTriggering(AngleRangeAction.ModifySelector))
|
||||
cache.RegisterUndo("Set Preview Angle");
|
||||
|
||||
float newAngle;
|
||||
if (view.DoSelector(rect, angleOffset, radius, cache.previewAngle, out newAngle))
|
||||
{
|
||||
if (selectedAngleRange == null)
|
||||
newAngle = Mathf.Repeat(newAngle + 180f, 360f) - 180f;
|
||||
|
||||
cache.previewAngle = newAngle;
|
||||
SetSelectedIndexFromPreviewAngle();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSelectedIndexFromPreviewAngle()
|
||||
{
|
||||
var index = SpriteShapeEditorUtility.GetRangeIndexFromAngle(cache.angleRanges, cache.previewAngle);
|
||||
SelectIndex(index);
|
||||
}
|
||||
|
||||
private void SelectIndex(int index)
|
||||
{
|
||||
view.RequestFocusIndex(index);
|
||||
|
||||
if (cache.selectedIndex == index)
|
||||
return;
|
||||
|
||||
cache.selectedIndex = index;
|
||||
selectionChanged();
|
||||
}
|
||||
|
||||
private void ClampPreviewAngle(float start, float end, float prevStart, float prevEnd)
|
||||
{
|
||||
var angle = cache.previewAngle;
|
||||
|
||||
if (prevStart < start)
|
||||
{
|
||||
var a1 = Mathf.Repeat(angle - prevStart, 360f);
|
||||
var a2 = Mathf.Repeat(angle - start, 360f);
|
||||
|
||||
if (a1 < a2)
|
||||
angle = Mathf.Min(start, end);
|
||||
}
|
||||
else if (prevEnd > end)
|
||||
{
|
||||
var a1 = Mathf.Repeat(prevEnd - angle, 360f);
|
||||
var a2 = Mathf.Repeat(end - angle, 360f);
|
||||
|
||||
if (a1 < a2)
|
||||
angle = Mathf.Max(start, end);
|
||||
}
|
||||
|
||||
cache.previewAngle = angle;
|
||||
}
|
||||
|
||||
private void HandleRemoveRange()
|
||||
{
|
||||
if (view.DoRemoveRange())
|
||||
{
|
||||
cache.RegisterUndo("Remove Range");
|
||||
cache.angleRanges.RemoveAt(cache.selectedIndex);
|
||||
SelectIndex(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateRange()
|
||||
{
|
||||
CreateRangeAtAngle(cache.previewAngle);
|
||||
}
|
||||
|
||||
private void CreateRangeAtAngle(float angle)
|
||||
{
|
||||
var start = 0f;
|
||||
var end = 0f;
|
||||
|
||||
if (GetNewRangeBounds(angle, out start, out end))
|
||||
{
|
||||
cache.RegisterUndo("Create Range");
|
||||
|
||||
var angleRange = new AngleRange();
|
||||
angleRange.start = start;
|
||||
angleRange.end = end;
|
||||
|
||||
cache.angleRanges.Add(angleRange);
|
||||
|
||||
ValidateRange(angleRange);
|
||||
SetSelectedIndexFromPreviewAngle();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRange(AngleRange angleRange, float start, float end)
|
||||
{
|
||||
var prevStart = angleRange.start;
|
||||
var prevEnd = angleRange.end;
|
||||
|
||||
angleRange.start = start;
|
||||
angleRange.end = end;
|
||||
|
||||
ValidateRange(angleRange, prevStart, prevEnd);
|
||||
|
||||
if (angleRange == selectedAngleRange)
|
||||
ClampPreviewAngle(start, end, prevStart, prevEnd);
|
||||
}
|
||||
|
||||
private bool GetNewRangeBounds(float angle, out float emptyRangeStart, out float emptyRangeEnd)
|
||||
{
|
||||
angle = Mathf.Repeat(angle + 180f, 360f) - 180f;
|
||||
|
||||
emptyRangeStart = float.MinValue;
|
||||
emptyRangeEnd = float.MaxValue;
|
||||
|
||||
if (GetAngleRangeAt(angle) != null)
|
||||
return false;
|
||||
|
||||
FindMinMax(out emptyRangeEnd, out emptyRangeStart);
|
||||
|
||||
if (angle < emptyRangeStart)
|
||||
emptyRangeStart -= 360f;
|
||||
|
||||
if (angle > emptyRangeEnd)
|
||||
emptyRangeEnd += 360f;
|
||||
|
||||
foreach (var angleRange in cache.angleRanges)
|
||||
{
|
||||
var start = angleRange.start;
|
||||
var end = angleRange.end;
|
||||
|
||||
if (angle > end)
|
||||
emptyRangeStart = Mathf.Max(emptyRangeStart, end);
|
||||
|
||||
if (angle < start)
|
||||
emptyRangeEnd = Mathf.Min(emptyRangeEnd, start);
|
||||
}
|
||||
|
||||
var rangeLength = emptyRangeEnd - emptyRangeStart;
|
||||
|
||||
if (rangeLength > 90f)
|
||||
{
|
||||
emptyRangeStart = Mathf.Max(angle - 45f, emptyRangeStart);
|
||||
emptyRangeEnd = Mathf.Min(angle + 45f, emptyRangeEnd);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private AngleRange GetAngleRangeAt(float angle)
|
||||
{
|
||||
foreach (var angleRange in cache.angleRanges)
|
||||
{
|
||||
var start = angleRange.start;
|
||||
var end = angleRange.end;
|
||||
var range = end - start;
|
||||
|
||||
var angle2 = Mathf.Repeat(angle - start, 360f);
|
||||
|
||||
if (angle2 >= 0f && angle2 <= range)
|
||||
return angleRange;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void FindMinMax(out float min, out float max)
|
||||
{
|
||||
min = float.MaxValue;
|
||||
max = float.MinValue;
|
||||
|
||||
foreach (var angleRange in cache.angleRanges)
|
||||
{
|
||||
min = Mathf.Min(angleRange.start, min);
|
||||
max = Mathf.Max(angleRange.end, max);
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateRange(AngleRange range)
|
||||
{
|
||||
ValidateRange(range, range.start, range.end);
|
||||
}
|
||||
|
||||
private void ValidateRange(AngleRange angleRange, float prevStart, float prevEnd)
|
||||
{
|
||||
var start = angleRange.start;
|
||||
var end = angleRange.end;
|
||||
|
||||
foreach (var otherRange in cache.angleRanges)
|
||||
{
|
||||
var otherStart = otherRange.start;
|
||||
var otherEnd = otherRange.end;
|
||||
|
||||
if (otherRange == angleRange)
|
||||
{
|
||||
if ((start > 180f && end > 180f) || (start < -180f && end < -180f))
|
||||
{
|
||||
start = Mathf.Repeat(start + 180f, 360f) - 180f;
|
||||
end = Mathf.Repeat(end + 180f, 360f) - 180f;
|
||||
}
|
||||
|
||||
otherStart = start + 360f;
|
||||
otherEnd = end - 360f;
|
||||
}
|
||||
|
||||
ValidateRangeStartEnd(ref start, ref end, prevStart, prevEnd, otherStart, otherEnd);
|
||||
}
|
||||
|
||||
angleRange.start = start;
|
||||
angleRange.end = end;
|
||||
}
|
||||
|
||||
private void ValidateRangeStartEnd(ref float start, ref float end, float prevStart, float prevEnd, float otherStart, float otherEnd)
|
||||
{
|
||||
var min = Mathf.Min(start, otherStart);
|
||||
var max = Mathf.Max(end, otherEnd);
|
||||
|
||||
start -= min;
|
||||
end -= min;
|
||||
otherStart -= min;
|
||||
otherEnd -= min;
|
||||
prevStart -= min;
|
||||
prevEnd -= min;
|
||||
|
||||
if (prevEnd != end)
|
||||
end = Mathf.Clamp(end, start, otherStart >= start ? otherStart : 360f);
|
||||
|
||||
start += min - max;
|
||||
end += min - max;
|
||||
otherStart += min - max;
|
||||
otherEnd += min - max;
|
||||
prevStart += min - max;
|
||||
prevEnd += min - max;
|
||||
|
||||
if (prevStart != start)
|
||||
start = Mathf.Clamp(start, otherEnd <= end ? otherEnd : -360f, end);
|
||||
|
||||
start += max;
|
||||
end += max;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Sprites;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public class AngleRangeGUI
|
||||
{
|
||||
public static readonly int kLeftHandleHashCode = "LeftHandle".GetHashCode();
|
||||
public static readonly int kRightHandleHashCode = "RightHandle".GetHashCode();
|
||||
public const float kRangeWidth = 10f;
|
||||
|
||||
public static void AngleRangeField(Rect rect, ref float start, ref float end, float angleOffset, float radius, bool snap, bool drawLine, bool drawCircle, Color rangeColor)
|
||||
{
|
||||
var leftId = GUIUtility.GetControlID(kLeftHandleHashCode, FocusType.Passive);
|
||||
var rightId = GUIUtility.GetControlID(kRightHandleHashCode, FocusType.Passive);
|
||||
AngleRangeField(rect, leftId, rightId, ref start, ref end, angleOffset, radius, snap, drawLine, drawCircle, rangeColor);
|
||||
}
|
||||
|
||||
public static void AngleRangeField(Rect rect, int leftHandleID, int rightHandleID, ref float start, ref float end, float angleOffset, float radius, bool snap, bool drawLine, bool drawCircle, Color rangeColor)
|
||||
{
|
||||
Color activeColor = Handles.color;
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
float range = end - start;
|
||||
|
||||
Color color = Handles.color;
|
||||
Handles.color = rangeColor;
|
||||
if (range < 0f)
|
||||
Handles.color = Color.red;
|
||||
|
||||
SpriteShapeHandleUtility.DrawSolidArc(rect.center, Vector3.forward, Quaternion.AngleAxis(start + angleOffset, Vector3.forward) * Vector3.right, range, radius, kRangeWidth);
|
||||
Handles.color = color;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
float handleSize = 15f;
|
||||
|
||||
start = AngleField(rect, leftHandleID, start, angleOffset, new Vector2(-3.5f, -7.5f), start + angleOffset + 90f, handleSize, radius, snap, drawLine, drawCircle, SpriteShapeHandleUtility.RangeLeftCap);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
start = Mathf.Clamp(start, end - 360f, end);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
end = AngleField(rect, rightHandleID, end, angleOffset, new Vector2(3.5f, -7.5f), end + angleOffset + 90f, handleSize, radius, snap, drawLine, false, SpriteShapeHandleUtility.RangeRightCap);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
end = Mathf.Clamp(end, end, start + 360f);
|
||||
|
||||
Handles.color = activeColor;
|
||||
}
|
||||
|
||||
public static void AngleRangeField(Rect rect, SerializedProperty startProperty, SerializedProperty endProperty, float angleOffset, float radius, bool snap, bool drawLine, bool drawCircle, Color rangeColor)
|
||||
{
|
||||
var start = startProperty.floatValue;
|
||||
var end = endProperty.floatValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
AngleRangeField(rect, ref start, ref end, angleOffset, radius, snap, drawLine, drawCircle, rangeColor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
startProperty.floatValue = start;
|
||||
endProperty.floatValue = end;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AngleField(int id, SerializedProperty property, float angleOffset, Vector2 handleOffset, float handleAngle, float handleSize, float radius, bool snap, bool drawLine, bool drawCircle, Handles.CapFunction drawCapFunction)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
float value = AngleField(id, property.floatValue, angleOffset, handleOffset, handleAngle, handleSize, radius, snap, drawLine, drawCircle, drawCapFunction);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.floatValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AngleField(Rect r, int id, SerializedProperty property, float angleOffset, Vector2 handleOffset, float handleAngle, float handleSize, float radius, bool snap, bool drawLine, bool drawCircle, Handles.CapFunction drawCapFunction)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
float value = AngleField(r, id, property.floatValue, angleOffset, handleOffset, handleAngle, handleSize, radius, snap, drawLine, drawCircle, drawCapFunction);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
property.floatValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static float AngleField(int id, float angle, float angleOffset, Vector2 handleOffset, float handleAngle, float radius, float handleSize, bool snap, bool drawLine, bool drawCircle, Handles.CapFunction drawCapFunction)
|
||||
{
|
||||
Rect rect = EditorGUILayout.GetControlRect(false, radius * 2f);
|
||||
return AngleField(rect, id, angle, angleOffset, handleOffset, handleAngle, radius, handleSize, snap, drawLine, drawCircle, drawCapFunction);
|
||||
}
|
||||
|
||||
public static float AngleField(Rect rect, int id, float angle, float angleOffset, Vector2 handleOffset, float handleAngle, float handleSize, float radius, bool snap, bool drawLine, bool drawCircle, Handles.CapFunction drawCapFunction)
|
||||
{
|
||||
float offsetedAngle = angle + angleOffset;
|
||||
Vector2 pos = new Vector2(Mathf.Cos(offsetedAngle * Mathf.Deg2Rad), Mathf.Sin(offsetedAngle * Mathf.Deg2Rad)) * radius + rect.center;
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
if (drawCircle)
|
||||
Handles.DrawWireDisc(rect.center, Vector3.forward, radius);
|
||||
|
||||
if (drawLine)
|
||||
Handles.DrawLine(rect.center, pos);
|
||||
}
|
||||
|
||||
if (GUI.enabled)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
Quaternion rotation = Quaternion.AngleAxis(handleAngle, Vector3.forward);
|
||||
Vector2 posNew = SpriteShapeHandleUtility.Slider2D(id, pos, rotation * handleOffset, rotation, handleSize, drawCapFunction);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Vector2 v1 = pos - rect.center;
|
||||
Vector2 v2 = posNew - rect.center;
|
||||
|
||||
float angleDirection = Mathf.Sign(Vector3.Dot(Vector3.forward, Vector3.Cross(v1, v2)));
|
||||
float angleIncrement = Vector2.Angle(v1, v2);
|
||||
|
||||
angle += angleIncrement * angleDirection;
|
||||
|
||||
if (snap)
|
||||
angle = Mathf.RoundToInt(angle);
|
||||
}
|
||||
}
|
||||
|
||||
return angle;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,336 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Sprites;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public enum AngleRangeAction
|
||||
{
|
||||
SelectRange,
|
||||
ModifyRange,
|
||||
CreateRange,
|
||||
ModifySelector,
|
||||
RemoveRange,
|
||||
}
|
||||
|
||||
public interface IAngleRangeView
|
||||
{
|
||||
int hoveredRangeIndex { get; }
|
||||
|
||||
void RequestFocusIndex(int index);
|
||||
float GetAngleFromPosition(Rect rect, float angleOffset);
|
||||
void Repaint();
|
||||
void SetupLayout(Rect rect, float angleOffset, float radius);
|
||||
bool DoAngleRange(int index, Rect rect, float radius, float angleOffset, ref float start, ref float end, bool snap, bool disabled, Color gradientMin, Color gradientMid, Color gradientMax);
|
||||
bool DoSelectAngleRange(int currentSelected, out int newSelected);
|
||||
bool DoCreateRange(Rect rect, float radius, float angleOffset, float start, float end);
|
||||
void DoCreateRangeTooltip();
|
||||
bool DoSelector(Rect rect, float angleOffset, float radius, float angle, out float newAngle);
|
||||
bool DoRemoveRange();
|
||||
bool IsActionActive(AngleRangeAction action);
|
||||
bool IsActionTriggering(AngleRangeAction action);
|
||||
bool IsActionFinishing(AngleRangeAction action);
|
||||
void DrawAngleRangeOutline(Rect rect, float start, float end, float angleOffset, float radius);
|
||||
}
|
||||
|
||||
public class AngleRangeView : IAngleRangeView
|
||||
{
|
||||
const string kDeleteCommandName = "Delete";
|
||||
const string kSoftDeleteCommandName = "SoftDelete";
|
||||
static readonly int kAngleRangeHashCode = "AngleRange".GetHashCode();
|
||||
static readonly int kCreateRangeHashCode = "CreateRange".GetHashCode();
|
||||
static readonly int kSelectorHashCode = "Selector".GetHashCode();
|
||||
|
||||
private static Color kHightlightColor = new Color(0.25f, 0.5f, 1.0f);
|
||||
private static Color kNoKeboardFocusColor = new Color(0.3f, 0.5f, 0.65f);
|
||||
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent addRangeTooltip = new GUIContent("", "Click to add a new range");
|
||||
}
|
||||
|
||||
private int m_FocusedRangeControlID = -1;
|
||||
private int m_HoveredRangeIndex = -1;
|
||||
private int m_HoveredRangeID = -1;
|
||||
private int m_HoveredHandleID = -1;
|
||||
private int m_HotHandleID = -1;
|
||||
private int m_CreateRangeControlID = -1;
|
||||
private int m_SelectorControlID = -1;
|
||||
private int m_RequestFocusIndex = -1;
|
||||
|
||||
public int hoveredRangeIndex { get { return m_HoveredRangeIndex; } }
|
||||
|
||||
public void RequestFocusIndex(int index)
|
||||
{
|
||||
GUIUtility.keyboardControl = 0;
|
||||
m_RequestFocusIndex = index;
|
||||
}
|
||||
|
||||
public float GetAngleFromPosition(Rect rect, float angleOffset)
|
||||
{
|
||||
return Mathf.RoundToInt(SpriteShapeHandleUtility.PosToAngle(Event.current.mousePosition, rect.center, -angleOffset));
|
||||
}
|
||||
|
||||
public void Repaint()
|
||||
{
|
||||
HandleUtility.Repaint();
|
||||
}
|
||||
|
||||
public void SetupLayout(Rect rect, float angleOffset, float radius)
|
||||
{
|
||||
m_CreateRangeControlID = GUIUtility.GetControlID(kCreateRangeHashCode, FocusType.Passive);
|
||||
m_SelectorControlID = GUIUtility.GetControlID(kSelectorHashCode, FocusType.Passive);
|
||||
|
||||
LayoutCreateRange(rect, angleOffset, radius);
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
m_HoveredRangeIndex = -1;
|
||||
m_HoveredRangeID = -1;
|
||||
m_HoveredHandleID = -1;
|
||||
|
||||
if (GUIUtility.hotControl == 0)
|
||||
{
|
||||
m_HotHandleID = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LayoutCreateRange(Rect rect, float angleOffset, float radius)
|
||||
{
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
var mousePosition = Event.current.mousePosition;
|
||||
var distance = SpriteShapeHandleUtility.DistanceToArcWidth(mousePosition, rect.center, 0f, 360f, radius, AngleRangeGUI.kRangeWidth, angleOffset);
|
||||
|
||||
HandleUtility.AddControl(m_CreateRangeControlID, distance);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoAngleRange(int index, Rect rect, float radius, float angleOffset, ref float start, ref float end, bool snap, bool disabled, Color gradientMin, Color gradientMid, Color gradientMax)
|
||||
{
|
||||
var changed = false;
|
||||
|
||||
var controlID = GUIUtility.GetControlID(kAngleRangeHashCode, FocusType.Passive);
|
||||
var leftHandleId = GUIUtility.GetControlID(AngleRangeGUI.kLeftHandleHashCode, FocusType.Passive);
|
||||
var rightHandleId = GUIUtility.GetControlID(AngleRangeGUI.kRightHandleHashCode, FocusType.Passive);
|
||||
|
||||
if (Event.current.type == EventType.Layout)
|
||||
{
|
||||
var distance = SpriteShapeHandleUtility.DistanceToArcWidth(Event.current.mousePosition, rect.center, start, end, radius, AngleRangeGUI.kRangeWidth, angleOffset);
|
||||
HandleUtility.AddControl(controlID, distance);
|
||||
|
||||
if (HandleUtility.nearestControl == controlID)
|
||||
{
|
||||
m_HoveredRangeIndex = index;
|
||||
m_HoveredRangeID = controlID;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsActionTriggering(AngleRangeAction.ModifyRange))
|
||||
{
|
||||
m_HotHandleID = m_HoveredHandleID;
|
||||
GrabKeyboardFocus(controlID);
|
||||
}
|
||||
|
||||
if (m_RequestFocusIndex == index)
|
||||
{
|
||||
GrabKeyboardFocus(controlID);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_RequestFocusIndex = -1;
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(disabled))
|
||||
{
|
||||
var midAngle = (end - start) * 0.5f + start;
|
||||
var t = 2f * (midAngle + 180f) / 360f;
|
||||
var color = gradientMin;
|
||||
|
||||
if (t < 1f)
|
||||
color = Color.Lerp(gradientMin, gradientMid, t);
|
||||
else
|
||||
color = Color.Lerp(gradientMid, gradientMax, t - 1f);
|
||||
|
||||
if (!disabled)
|
||||
{
|
||||
color = kNoKeboardFocusColor;
|
||||
|
||||
if (HasKeyboardFocus())
|
||||
color = kHightlightColor;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
AngleRangeGUI.AngleRangeField(rect, leftHandleId, rightHandleId, ref start, ref end, angleOffset, radius, snap, false, false, color);
|
||||
|
||||
changed = EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
//Extra Layout from handles
|
||||
if (Event.current.type == EventType.Layout &&
|
||||
(HandleUtility.nearestControl == leftHandleId || HandleUtility.nearestControl == rightHandleId))
|
||||
{
|
||||
m_HoveredRangeIndex = index;
|
||||
m_HoveredRangeID = controlID;
|
||||
m_HoveredHandleID = HandleUtility.nearestControl;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
public bool DoSelectAngleRange(int currentSelected, out int newSelected)
|
||||
{
|
||||
newSelected = currentSelected;
|
||||
|
||||
if (IsActionTriggering(AngleRangeAction.SelectRange))
|
||||
{
|
||||
newSelected = m_HoveredRangeIndex;
|
||||
GUI.changed = true;
|
||||
Repaint();
|
||||
|
||||
HandleUtility.nearestControl = m_SelectorControlID;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool DoCreateRange(Rect rect, float radius, float angleOffset, float start, float end)
|
||||
{
|
||||
if (IsActionTriggering(AngleRangeAction.CreateRange))
|
||||
{
|
||||
GUI.changed = true;
|
||||
HandleUtility.nearestControl = m_SelectorControlID;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (IsActionActive(AngleRangeAction.CreateRange))
|
||||
{
|
||||
DrawAngleRangeOutline(rect, start, end, angleOffset, radius);
|
||||
|
||||
if (Event.current.type == EventType.MouseMove)
|
||||
Repaint();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DoCreateRangeTooltip()
|
||||
{
|
||||
if (IsActionActive(AngleRangeAction.CreateRange))
|
||||
{
|
||||
var mousePosition = Event.current.mousePosition;
|
||||
EditorGUI.LabelField(new Rect(mousePosition, new Vector2(1f, 20f)), Contents.addRangeTooltip);
|
||||
}
|
||||
}
|
||||
|
||||
public bool DoSelector(Rect rect, float angleOffset, float radius, float angle, out float newAngle)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
newAngle = AngleRangeGUI.AngleField(rect, m_SelectorControlID, angle, angleOffset, Vector2.down * 7.5f, angle, 15f, radius - AngleRangeGUI.kRangeWidth, true, true, false, SpriteShapeHandleUtility.PlayHeadCap);
|
||||
return EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
public bool DoRemoveRange()
|
||||
{
|
||||
EventType eventType = Event.current.type;
|
||||
|
||||
if (IsActionTriggering(AngleRangeAction.RemoveRange))
|
||||
{
|
||||
Event.current.Use();
|
||||
GUI.changed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsActionActive(AngleRangeAction action)
|
||||
{
|
||||
if (GUIUtility.hotControl != 0)
|
||||
return false;
|
||||
|
||||
if (action == AngleRangeAction.SelectRange)
|
||||
return HandleUtility.nearestControl == m_HoveredRangeID;
|
||||
|
||||
if (action == AngleRangeAction.ModifyRange)
|
||||
return HandleUtility.nearestControl == m_HoveredHandleID;
|
||||
|
||||
if (action == AngleRangeAction.CreateRange)
|
||||
return HandleUtility.nearestControl == m_CreateRangeControlID;
|
||||
|
||||
if (action == AngleRangeAction.ModifySelector)
|
||||
return HandleUtility.nearestControl == m_SelectorControlID;
|
||||
|
||||
if (action == AngleRangeAction.RemoveRange)
|
||||
return HasKeyboardFocus();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsActionHot(AngleRangeAction action)
|
||||
{
|
||||
if (GUIUtility.hotControl == 0)
|
||||
return false;
|
||||
|
||||
if (action == AngleRangeAction.ModifyRange)
|
||||
return GUIUtility.hotControl == m_HotHandleID;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsActionTriggering(AngleRangeAction action)
|
||||
{
|
||||
if (!IsActionActive(action))
|
||||
return false;
|
||||
|
||||
EventType eventType = Event.current.type;
|
||||
|
||||
if (action == AngleRangeAction.RemoveRange)
|
||||
{
|
||||
if ((eventType == EventType.ValidateCommand || eventType == EventType.ExecuteCommand)
|
||||
&& (Event.current.commandName == kSoftDeleteCommandName || Event.current.commandName == kDeleteCommandName))
|
||||
{
|
||||
if (eventType == EventType.ExecuteCommand)
|
||||
return true;
|
||||
|
||||
Event.current.Use();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return eventType == EventType.MouseDown && Event.current.button == 0;
|
||||
}
|
||||
|
||||
public bool IsActionFinishing(AngleRangeAction action)
|
||||
{
|
||||
if (!IsActionHot(action))
|
||||
return false;
|
||||
|
||||
return (Event.current.type == EventType.MouseUp && Event.current.button == 0) || Event.current.type == EventType.Ignore;
|
||||
}
|
||||
|
||||
public void DrawAngleRangeOutline(Rect rect, float start, float end, float angleOffset, float radius)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
SpriteShapeHandleUtility.DrawRangeOutline(start, end, angleOffset, rect.center, radius, AngleRangeGUI.kRangeWidth - 1f);
|
||||
}
|
||||
|
||||
private void GrabKeyboardFocus(int controlID)
|
||||
{
|
||||
m_FocusedRangeControlID = controlID;
|
||||
GUIUtility.keyboardControl = controlID;
|
||||
}
|
||||
|
||||
private bool HasKeyboardFocus()
|
||||
{
|
||||
return GUIUtility.keyboardControl == m_FocusedRangeControlID;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,2 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Tests.EditorTests")]
|
@@ -0,0 +1,43 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor.U2D.SpriteShape;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public class ContextMenu
|
||||
{
|
||||
private static AngleRange CreateAngleRange(float start, float end, int order)
|
||||
{
|
||||
AngleRange angleRange = new AngleRange();
|
||||
|
||||
angleRange.start = start;
|
||||
angleRange.end = end;
|
||||
angleRange.order = order;
|
||||
|
||||
return angleRange;
|
||||
}
|
||||
|
||||
//[MenuItem("Assets/Create/Sprite Shape Profile/Open Shape", false, 358)]
|
||||
public static void CreateNewSpriteStrip()
|
||||
{
|
||||
UnityEngine.U2D.SpriteShape newSpriteShape = SpriteShapeEditorUtility.CreateSpriteShapeAsset();
|
||||
newSpriteShape.angleRanges.Add(CreateAngleRange(-180.0f, 180.0f, 0));
|
||||
}
|
||||
|
||||
//[MenuItem("Assets/Create/Sprite Shape Profile/Closed Shape", false, 359)]
|
||||
public static void CreateNewSpriteShape()
|
||||
{
|
||||
UnityEngine.U2D.SpriteShape newSpriteShape = SpriteShapeEditorUtility.CreateSpriteShapeAsset();
|
||||
newSpriteShape.angleRanges.Add(CreateAngleRange(-45.0f, 45.0f, 4));
|
||||
newSpriteShape.angleRanges.Add(CreateAngleRange(-135.0f, -45.0f, 3));
|
||||
newSpriteShape.angleRanges.Add(CreateAngleRange(135.0f, 225.0f, 2));
|
||||
newSpriteShape.angleRanges.Add(CreateAngleRange(45.0f, 135.0f, 1));
|
||||
}
|
||||
|
||||
//[MenuItem("GameObject/2D Object/Sprite Shape")]
|
||||
internal static void CreateSpriteShapeEmpty()
|
||||
{
|
||||
SpriteShapeEditorUtility.SetShapeFromAsset(SpriteShapeEditorUtility.CreateSpriteShapeControllerFromSelection());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Sprites;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public static class EditorSpriteGUIUtility
|
||||
{
|
||||
public enum FitMode
|
||||
{
|
||||
BestFit,
|
||||
FitHorizontal,
|
||||
FitVertical,
|
||||
Fill,
|
||||
Tiled
|
||||
}
|
||||
|
||||
private static Material s_SpriteMaterial;
|
||||
public static Material spriteMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_SpriteMaterial == null)
|
||||
{
|
||||
s_SpriteMaterial = new Material(Shader.Find("Hidden/InternalSpritesInspector"));
|
||||
s_SpriteMaterial.hideFlags = HideFlags.DontSave;
|
||||
}
|
||||
s_SpriteMaterial.SetFloat("_AdjustLinearForGamma", PlayerSettings.colorSpace == ColorSpace.Linear ? 1.0f : 0.0f);
|
||||
return s_SpriteMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
public static Texture GetOriginalSpriteTexture(Sprite sprite)
|
||||
{
|
||||
return UnityEditor.Sprites.SpriteUtility.GetSpriteTexture(sprite, false);
|
||||
}
|
||||
|
||||
public static Vector2[] GetOriginalSpriteUvs(Sprite sprite)
|
||||
{
|
||||
return UnityEditor.Sprites.SpriteUtility.GetSpriteUVs(sprite, false);
|
||||
}
|
||||
|
||||
public static void DrawSpriteInRectPrepare(Rect rect, Sprite sprite, FitMode fitMode, bool excludeBorders, bool forceQuad, Mesh mesh)
|
||||
{
|
||||
var vertices = new List<Vector3>();
|
||||
var uvs = new List<Vector2>();
|
||||
var indices = new List<int>();
|
||||
mesh.Clear();
|
||||
|
||||
if (!sprite)
|
||||
{
|
||||
mesh.UploadMeshData(false);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 scale = Vector2.one;
|
||||
Rect spriteRect = sprite.textureRect;
|
||||
Vector2 bottomLeftBorderOffset = sprite.rect.position + new Vector2(sprite.border.x, sprite.border.y) - spriteRect.position;
|
||||
Vector2 topRightBorderOffset = new Vector2(sprite.border.z, sprite.border.w) + (spriteRect.position + spriteRect.size) - (sprite.rect.position + sprite.rect.size);
|
||||
|
||||
if (excludeBorders)
|
||||
{
|
||||
forceQuad = true;
|
||||
spriteRect.position = spriteRect.position + bottomLeftBorderOffset;
|
||||
spriteRect.size = spriteRect.size - bottomLeftBorderOffset - topRightBorderOffset;
|
||||
}
|
||||
|
||||
bool tiled = false;
|
||||
|
||||
if (fitMode == FitMode.Tiled)
|
||||
{
|
||||
tiled = true;
|
||||
forceQuad = true;
|
||||
fitMode = FitMode.BestFit;
|
||||
}
|
||||
|
||||
if (fitMode == FitMode.BestFit)
|
||||
{
|
||||
float spriteRatio = spriteRect.width / spriteRect.height;
|
||||
float frameRatio = rect.width / rect.height;
|
||||
|
||||
if (spriteRatio < frameRatio)
|
||||
fitMode = FitMode.FitVertical;
|
||||
else
|
||||
fitMode = FitMode.FitHorizontal;
|
||||
}
|
||||
|
||||
if (fitMode == FitMode.FitHorizontal)
|
||||
scale = Vector2.one * (rect.width / spriteRect.width);
|
||||
|
||||
if (fitMode == FitMode.FitVertical)
|
||||
scale = Vector2.one * (rect.height / spriteRect.height);
|
||||
|
||||
if (fitMode == FitMode.Fill)
|
||||
{
|
||||
scale.x = rect.width / spriteRect.width;
|
||||
scale.y = rect.height / spriteRect.height;
|
||||
}
|
||||
|
||||
Texture spriteTexture = GetOriginalSpriteTexture(sprite);
|
||||
if (spriteTexture == null)
|
||||
return;
|
||||
|
||||
if (forceQuad)
|
||||
{
|
||||
Vector2 uvScale = new Vector2(1f / spriteTexture.width, 1f / spriteTexture.height);
|
||||
Vector2 uvPos = Vector2.Scale(spriteRect.position, uvScale);
|
||||
Vector2 uvSize = Vector2.Scale(spriteRect.size, uvScale);
|
||||
Vector2 uv0 = uvPos;
|
||||
Vector2 uv1 = uvPos + Vector2.up * uvSize.y;
|
||||
Vector2 uv2 = uvPos + Vector2.right * uvSize.x;
|
||||
Vector2 uv3 = uvPos + uvSize;
|
||||
Vector3 v0 = new Vector3(uv0.x * spriteTexture.width - spriteRect.position.x - spriteRect.width * 0.5f, uv0.y * spriteTexture.height - spriteRect.position.y - spriteRect.height * 0.5f, 0f);
|
||||
Vector3 v1 = new Vector3(uv1.x * spriteTexture.width - spriteRect.position.x - spriteRect.width * 0.5f, uv1.y * spriteTexture.height - spriteRect.position.y - spriteRect.height * 0.5f, 0f);
|
||||
Vector3 v2 = new Vector3(uv2.x * spriteTexture.width - spriteRect.position.x - spriteRect.width * 0.5f, uv2.y * spriteTexture.height - spriteRect.position.y - spriteRect.height * 0.5f, 0f);
|
||||
Vector3 v3 = new Vector3(uv3.x * spriteTexture.width - spriteRect.position.x - spriteRect.width * 0.5f, uv3.y * spriteTexture.height - spriteRect.position.y - spriteRect.height * 0.5f, 0f);
|
||||
v0 = Vector3.Scale(v0, scale);
|
||||
v1 = Vector3.Scale(v1, scale);
|
||||
v2 = Vector3.Scale(v2, scale);
|
||||
v3 = Vector3.Scale(v3, scale);
|
||||
|
||||
//TODO: Support vertical tiling when horizontal fitted
|
||||
if (tiled && fitMode == FitMode.FitVertical)
|
||||
{
|
||||
Vector2 scaledRectSize = Vector2.Scale(rect.size, new Vector2(1f / scale.x, 1f / scale.y));
|
||||
float halfDistanceToFill = (scaledRectSize.x - spriteRect.width) * 0.5f;
|
||||
int halfFillSegmentCount = (int)Mathf.Ceil(halfDistanceToFill / spriteRect.width);
|
||||
int segmentCount = halfFillSegmentCount * 2 + 1;
|
||||
int vertexCount = segmentCount * 4;
|
||||
|
||||
vertices.Capacity = vertexCount;
|
||||
uvs.Capacity = vertexCount;
|
||||
indices.Capacity = vertexCount;
|
||||
|
||||
Vector3 offset = Vector3.zero;
|
||||
Vector3 offsetStep = Vector3.Scale(Vector3.right * spriteRect.width, scale);
|
||||
|
||||
float distanceStep = spriteRect.width;
|
||||
float distanceToFill = halfDistanceToFill + distanceStep;
|
||||
|
||||
int vertexIndex = 0;
|
||||
|
||||
for (int i = 0; i <= halfFillSegmentCount; ++i)
|
||||
{
|
||||
float t = Mathf.Clamp01(distanceToFill / spriteRect.width);
|
||||
|
||||
uvs.Add(uv0);
|
||||
uvs.Add(uv1);
|
||||
uvs.Add(Vector3.Lerp(uv0, uv2, t));
|
||||
uvs.Add(Vector3.Lerp(uv1, uv3, t));
|
||||
|
||||
vertices.Add(v0 + offset);
|
||||
vertices.Add(v1 + offset);
|
||||
vertices.Add(Vector3.Lerp(v0, v2, t) + offset);
|
||||
vertices.Add(Vector3.Lerp(v1, v3, t) + offset);
|
||||
|
||||
indices.Add(vertexIndex);
|
||||
indices.Add(vertexIndex + 2);
|
||||
indices.Add(vertexIndex + 1);
|
||||
indices.Add(vertexIndex + 2);
|
||||
indices.Add(vertexIndex + 3);
|
||||
indices.Add(vertexIndex + 1);
|
||||
|
||||
vertexIndex += 4;
|
||||
|
||||
if (i > 0)
|
||||
{
|
||||
uvs.Add(Vector2.Lerp(uv0, uv2, 1f - t));
|
||||
uvs.Add(Vector2.Lerp(uv1, uv3, 1f - t));
|
||||
uvs.Add(uv2);
|
||||
uvs.Add(uv3);
|
||||
|
||||
vertices.Add(Vector3.Lerp(v0, v2, 1f - t) - offset);
|
||||
vertices.Add(Vector3.Lerp(v1, v3, 1f - t) - offset);
|
||||
vertices.Add(v2 - offset);
|
||||
vertices.Add(v3 - offset);
|
||||
|
||||
indices.Add(vertexIndex);
|
||||
indices.Add(vertexIndex + 2);
|
||||
indices.Add(vertexIndex + 1);
|
||||
indices.Add(vertexIndex + 2);
|
||||
indices.Add(vertexIndex + 3);
|
||||
indices.Add(vertexIndex + 1);
|
||||
|
||||
vertexIndex += 4;
|
||||
}
|
||||
|
||||
offset += offsetStep;
|
||||
distanceToFill -= distanceStep;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
vertices.AddRange(new Vector3[] { v0, v1, v2, v3 });
|
||||
uvs.AddRange(new Vector2[] { uv0, uv1, uv2, uv3 });
|
||||
indices.AddRange(new int[] { 0, 2, 1, 2, 3, 1 });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ushort[] triangles = sprite.triangles;
|
||||
indices.Capacity = triangles.Length;
|
||||
|
||||
for (int i = 0; i < triangles.Length; ++i)
|
||||
indices.Add((int)triangles[i]);
|
||||
|
||||
uvs.AddRange(GetOriginalSpriteUvs(sprite));
|
||||
vertices.Capacity = uvs.Count;
|
||||
|
||||
for (int i = 0; i < uvs.Count; ++i)
|
||||
{
|
||||
Vector3 v = new Vector3(uvs[i].x * spriteTexture.width - spriteRect.position.x - spriteRect.width * 0.5f, uvs[i].y * spriteTexture.height - spriteRect.position.y - spriteRect.height * 0.5f, 0f);
|
||||
vertices.Add(Vector3.Scale(v, scale));
|
||||
}
|
||||
}
|
||||
|
||||
mesh.SetVertices(vertices);
|
||||
mesh.SetUVs(0, uvs);
|
||||
mesh.SetTriangles(indices, 0);
|
||||
mesh.UploadMeshData(false);
|
||||
}
|
||||
|
||||
public static void DrawMesh(Mesh mesh, Material material, Vector3 position, Quaternion rotation, Vector3 scale)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
Matrix4x4 matrix = new Matrix4x4();
|
||||
matrix.SetTRS(position, rotation, scale);
|
||||
material.SetPass(0);
|
||||
Graphics.DrawMeshNow(mesh, matrix);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
// Texture is forced to be in Gamma space regardless of the active ColorSpace.
|
||||
Shader "Hidden/InternalSpritesInspector"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
[PerRendererData] _MainTex("Sprite Texture", 2D) = "white" {}
|
||||
_Color("Tint", Color) = (1, 1, 1, 1)
|
||||
[MaterialToggle] PixelSnap("Pixel snap", Float) = 0
|
||||
}
|
||||
|
||||
SubShader
|
||||
{
|
||||
Tags
|
||||
{
|
||||
"Queue" = "Transparent"
|
||||
"IgnoreProjector" = "True"
|
||||
"RenderType" = "Transparent"
|
||||
"PreviewType" = "Plane"
|
||||
"CanUseSpriteAtlas" = "True"
|
||||
}
|
||||
|
||||
Cull Off
|
||||
Lighting Off
|
||||
ZWrite Off
|
||||
Fog{ Mode Off }
|
||||
Blend SrcAlpha OneMinusSrcAlpha
|
||||
|
||||
Pass
|
||||
{
|
||||
|
||||
CGPROGRAM
|
||||
#pragma vertex vert
|
||||
#pragma fragment frag
|
||||
#pragma multi_compile DUMMY PIXELSNAP_ON
|
||||
#include "UnityCG.cginc"
|
||||
|
||||
uniform bool _AdjustLinearForGamma;
|
||||
|
||||
struct appdata_t
|
||||
{
|
||||
float4 vertex : POSITION;
|
||||
float4 color : COLOR;
|
||||
float2 texcoord : TEXCOORD0;
|
||||
};
|
||||
|
||||
struct v2f
|
||||
{
|
||||
float4 vertex : SV_POSITION;
|
||||
fixed4 color : COLOR;
|
||||
half2 texcoord : TEXCOORD0;
|
||||
float2 clipUV : TEXCOORD1;
|
||||
};
|
||||
|
||||
fixed4 _Color;
|
||||
uniform float4x4 unity_GUIClipTextureMatrix;
|
||||
|
||||
v2f vert(appdata_t IN)
|
||||
{
|
||||
float3 screenUV = UnityObjectToViewPos(IN.vertex);
|
||||
v2f OUT;
|
||||
OUT.vertex = UnityObjectToClipPos(IN.vertex);
|
||||
OUT.texcoord = IN.texcoord;
|
||||
OUT.color = IN.color * _Color;
|
||||
OUT.clipUV = mul(unity_GUIClipTextureMatrix, float4(screenUV.xy, 0, 1.0));
|
||||
return OUT;
|
||||
}
|
||||
|
||||
sampler2D _MainTex;
|
||||
sampler2D _GUIClipTexture;
|
||||
|
||||
fixed4 frag(v2f IN) : COLOR
|
||||
{
|
||||
fixed4 col = tex2D(_MainTex, IN.texcoord);
|
||||
fixed alpha = col.a;
|
||||
if (_AdjustLinearForGamma)
|
||||
col.rgb = LinearToGammaSpace(col.rgb);
|
||||
col.a = alpha * tex2D(_GUIClipTexture, IN.clipUV).a;
|
||||
return col * IN.color;
|
||||
}
|
||||
ENDCG
|
||||
}
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.Presets;
|
||||
using UnityEditor.U2D.Common;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D.SpriteShape
|
||||
{
|
||||
internal static class AssetCreation
|
||||
{
|
||||
const int k_SpriteShapeAssetMenuPriority = 9;
|
||||
static internal Action<int, ProjectWindowCallback.EndNameEditAction, string, Texture2D, string> StartNewAssetNameEditingDelegate = ProjectWindowUtil.StartNameEditingIfProjectWindowExists;
|
||||
|
||||
[MenuItem("Assets/Create/2D/Sprite Shape Profile", priority = k_SpriteShapeAssetMenuPriority)]
|
||||
static void MenuItem_AssetsCreate2DSpriteShapeProfile(MenuCommand menuCommand)
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<UnityEngine.U2D.SpriteShape>("Packages/com.unity.2d.spriteshape/Editor/ObjectMenuCreation/DefaultAssets/Sprite Shape Profiles/Sprite Shape Profile.asset");
|
||||
var preset = new PresetType(asset);
|
||||
var defaults = Preset.GetDefaultPresetsForType(preset).Count(x => x.enabled);
|
||||
if (defaults == 0)
|
||||
CreateAssetObject(asset);
|
||||
else
|
||||
CreateAssetObject<UnityEngine.U2D.SpriteShape>(null);
|
||||
}
|
||||
|
||||
static public T CreateAssetObject<T>(T obj) where T : UnityEngine.Object
|
||||
{
|
||||
var assetSelectionPath = AssetDatabase.GetAssetPath(Selection.activeObject);
|
||||
var isFolder = false;
|
||||
if(!string.IsNullOrEmpty(assetSelectionPath))
|
||||
isFolder = File.GetAttributes(assetSelectionPath).HasFlag(FileAttributes.Directory);
|
||||
var path = InternalEditorBridge.GetProjectWindowActiveFolderPath();
|
||||
if (isFolder)
|
||||
{
|
||||
path = assetSelectionPath;
|
||||
}
|
||||
|
||||
string resourceFile = "";
|
||||
string destName = "";
|
||||
int instanceId = 0;
|
||||
string fileName = "";
|
||||
if (obj != null)
|
||||
{
|
||||
resourceFile = AssetDatabase.GetAssetPath(obj);
|
||||
fileName = System.IO.Path.GetFileName(resourceFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = ObjectFactory.CreateInstance<T>();
|
||||
instanceId = obj.GetInstanceID();
|
||||
fileName = "Sprite Shape Profile.asset";
|
||||
}
|
||||
destName = AssetDatabase.GenerateUniqueAssetPath(System.IO.Path.Combine(path, fileName));
|
||||
var icon = AssetPreview.GetMiniThumbnail(obj);
|
||||
StartNewAssetNameEditing(resourceFile, destName, icon, instanceId);
|
||||
return Selection.activeObject as T;
|
||||
}
|
||||
|
||||
static private void StartNewAssetNameEditing(string source, string dest, Texture2D icon, int instanceId)
|
||||
{
|
||||
CreateAssetEndNameEditAction action = ScriptableObject.CreateInstance<CreateAssetEndNameEditAction>();
|
||||
StartNewAssetNameEditingDelegate(instanceId, action, dest, icon, source);
|
||||
}
|
||||
|
||||
internal class CreateAssetEndNameEditAction : ProjectWindowCallback.EndNameEditAction
|
||||
{
|
||||
public override void Action(int instanceId, string pathName, string resourceFile)
|
||||
{
|
||||
var uniqueName = AssetDatabase.GenerateUniqueAssetPath(pathName);
|
||||
// ProjectWindowUtil.StartNameEditingIfProjectWindowExists(int instanceID, EndNameEditAction endAction, string pathName, Texture2D icon, string resourceFile)
|
||||
// will reset the instanceId to Int32.MaxValue - 1 if its 0. Looks like a new trunk change ?
|
||||
var validInstanceId = (instanceId != 0 && instanceId != InternalEditorBridge.GetAssetCreationInstanceID_ForNonExistingAssets());
|
||||
if (!validInstanceId && !string.IsNullOrEmpty(resourceFile))
|
||||
{
|
||||
AssetDatabase.CopyAsset(resourceFile, uniqueName);
|
||||
}
|
||||
else
|
||||
{
|
||||
var obj = EditorUtility.InstanceIDToObject(instanceId);
|
||||
AssetDatabase.CreateAsset(obj, uniqueName);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: af7181f404f1447c0a7a17b3070b952b, type: 3}
|
||||
m_Name: Sprite Shape Profile
|
||||
m_EditorClassIdentifier:
|
||||
m_Angles:
|
||||
- m_Start: -180
|
||||
m_End: 180
|
||||
m_Order: 0
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 0ae2b74b1edd14bb18dd7b65ae916a54, type: 3}
|
||||
m_FillTexture: {fileID: 2800000, guid: b281b91a70a624a0da1c43adc1c30c7b, type: 3}
|
||||
m_CornerSprites:
|
||||
- m_CornerType: 0
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 1
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 2
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 3
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 4
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 5
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 6
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- m_CornerType: 7
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
m_FillOffset: 0
|
||||
m_UseSpriteBorders: 1
|
@@ -0,0 +1,161 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &791884194307863000
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 2651882290663359782}
|
||||
- component: {fileID: 4687501738149120438}
|
||||
- component: {fileID: 2153311463896626545}
|
||||
m_Layer: 0
|
||||
m_Name: Closed Sprite Shape
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &2651882290663359782
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 791884194307863000}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1971053207 &4687501738149120438
|
||||
SpriteShapeRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 791884194307863000}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 0
|
||||
m_RayTraceProcedural: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 0
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_MaskInteraction: 0
|
||||
m_ShapeTexture: {fileID: 2800000, guid: b281b91a70a624a0da1c43adc1c30c7b, type: 3}
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 0ae2b74b1edd14bb18dd7b65ae916a54, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
- {fileID: 21300000, guid: 608e61deb05c54660bebf5a4dd2ee02d, type: 3}
|
||||
m_LocalAABB:
|
||||
m_Center: {x: 0.00027251244, y: -0.0002720356, z: -0.00005}
|
||||
m_Extent: {x: 2.589231, y: 1.2500671, z: 0.00005}
|
||||
--- !u!114 &2153311463896626545
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 791884194307863000}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 90539df1cd5704abcb25fec9f3f5f84b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Spline:
|
||||
m_IsOpenEnded: 0
|
||||
m_ControlPoints:
|
||||
- position: {x: -1, y: -1, z: 0}
|
||||
leftTangent: {x: 0.53597367, y: -0.00048214197, z: -0}
|
||||
rightTangent: {x: -0.53597367, y: 0.00048214197, z: 0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
- position: {x: -2, y: 1, z: 0}
|
||||
leftTangent: {x: -1.0023332, y: 0.0010559559, z: 0}
|
||||
rightTangent: {x: 1.0023332, y: -0.0010559559, z: -0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
- position: {x: 2, y: 1, z: 0}
|
||||
leftTangent: {x: -1.0005913, y: -0.0016920567, z: -0}
|
||||
rightTangent: {x: 1.0005913, y: 0.0016920567, z: 0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
- position: {x: 1, y: -1, z: 0}
|
||||
leftTangent: {x: 0.54391515, y: 0.0004222989, z: 0}
|
||||
rightTangent: {x: -0.54391515, y: -0.0004222989, z: -0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
m_SpriteShape: {fileID: 11400000, guid: f24cddf7c09a6442abbce710a6fd7397, type: 2}
|
||||
m_FillPixelPerUnit: 256
|
||||
m_StretchTiling: 1
|
||||
m_SplineDetail: 16
|
||||
m_AdaptiveUV: 1
|
||||
m_StretchUV: 0
|
||||
m_WorldSpaceUV: 0
|
||||
m_CornerAngleThreshold: 30
|
||||
m_ColliderDetail: 16
|
||||
m_ColliderOffset: 0.5
|
||||
m_UpdateCollider: 1
|
||||
m_OptimizeCollider: 1
|
||||
m_OptimizeGeometry: 1
|
||||
m_EnableTangents: 0
|
||||
m_GeometryCached: 0
|
@@ -0,0 +1,143 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &6890171417896752708
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 8407649417597309679}
|
||||
- component: {fileID: 3074921751109605677}
|
||||
- component: {fileID: 7273541427232933439}
|
||||
m_Layer: 0
|
||||
m_Name: Open Sprite Shape
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!4 &8407649417597309679
|
||||
Transform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6890171417896752708}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_Children: []
|
||||
m_Father: {fileID: 0}
|
||||
m_RootOrder: 0
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
--- !u!1971053207 &3074921751109605677
|
||||
SpriteShapeRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6890171417896752708}
|
||||
m_Enabled: 1
|
||||
m_CastShadows: 1
|
||||
m_ReceiveShadows: 1
|
||||
m_DynamicOccludee: 1
|
||||
m_MotionVectors: 1
|
||||
m_LightProbeUsage: 1
|
||||
m_ReflectionProbeUsage: 1
|
||||
m_RayTracingMode: 0
|
||||
m_RayTraceProcedural: 0
|
||||
m_RenderingLayerMask: 1
|
||||
m_RendererPriority: 0
|
||||
m_Materials:
|
||||
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
- {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_StaticBatchInfo:
|
||||
firstSubMesh: 0
|
||||
subMeshCount: 0
|
||||
m_StaticBatchRoot: {fileID: 0}
|
||||
m_ProbeAnchor: {fileID: 0}
|
||||
m_LightProbeVolumeOverride: {fileID: 0}
|
||||
m_ScaleInLightmap: 1
|
||||
m_ReceiveGI: 1
|
||||
m_PreserveUVs: 0
|
||||
m_IgnoreNormalsForChartDetection: 0
|
||||
m_ImportantGI: 0
|
||||
m_StitchLightmapSeams: 1
|
||||
m_SelectedEditorRenderState: 0
|
||||
m_MinimumChartSize: 4
|
||||
m_AutoUVMaxDistance: 0.5
|
||||
m_AutoUVMaxAngle: 89
|
||||
m_LightmapParameters: {fileID: 0}
|
||||
m_SortingLayerID: 0
|
||||
m_SortingLayer: 0
|
||||
m_SortingOrder: 0
|
||||
m_Color: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_MaskInteraction: 0
|
||||
m_ShapeTexture: {fileID: 2800000, guid: cb35359504814441a895fe5e3ce2f9fe, type: 3}
|
||||
m_Sprites:
|
||||
- {fileID: 21300000, guid: 2f3056e08c07b4cfabadcfee59be0309, type: 3}
|
||||
m_LocalAABB:
|
||||
m_Center: {x: 0.00037574768, y: 0.13772076, z: -0.00005}
|
||||
m_Extent: {x: 2.3613043, y: 0.483122, z: 0.00005}
|
||||
--- !u!114 &7273541427232933439
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6890171417896752708}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 90539df1cd5704abcb25fec9f3f5f84b, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
m_Spline:
|
||||
m_IsOpenEnded: 1
|
||||
m_ControlPoints:
|
||||
- position: {x: -2, y: 0, z: 0}
|
||||
leftTangent: {x: -0.9738884, y: -0.9367374, z: -0}
|
||||
rightTangent: {x: 0.8658923, y: 0.83286107, z: 0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
- position: {x: 0, y: 0, z: 0}
|
||||
leftTangent: {x: -0.92568946, y: 0.003089726, z: -0}
|
||||
rightTangent: {x: 0.92568946, y: -0.003089726, z: 0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
- position: {x: 2, y: 0, z: 0}
|
||||
leftTangent: {x: -0.78340197, y: 0.751823, z: 0}
|
||||
rightTangent: {x: 0.6651766, y: -0.6383633, z: -0}
|
||||
mode: 1
|
||||
height: 1
|
||||
bevelCutoff: 0
|
||||
bevelSize: 0
|
||||
spriteIndex: 0
|
||||
corner: 1
|
||||
m_CornerMode: 1
|
||||
m_SpriteShape: {fileID: 11400000, guid: f24cddf7c09a6442abbce710a6fd7397, type: 2}
|
||||
m_FillPixelPerUnit: 256
|
||||
m_StretchTiling: 1
|
||||
m_SplineDetail: 16
|
||||
m_AdaptiveUV: 1
|
||||
m_StretchUV: 0
|
||||
m_WorldSpaceUV: 0
|
||||
m_CornerAngleThreshold: 30
|
||||
m_ColliderDetail: 16
|
||||
m_ColliderOffset: 0
|
||||
m_UpdateCollider: 1
|
||||
m_OptimizeCollider: 1
|
||||
m_OptimizeGeometry: 1
|
||||
m_EnableTangents: 0
|
||||
m_GeometryCached: 0
|
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 415 B |
After Width: | Height: | Size: 854 B |
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.Presets;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.U2D.SpriteShape
|
||||
{
|
||||
class GameObjectCreation
|
||||
{
|
||||
const int k_MenuPriority = 4;
|
||||
[MenuItem("GameObject/2D Object/Sprite Shape/Open Shape", false, k_MenuPriority)]
|
||||
static void MenuItem_GameObject2DObjectSpriteShapeOpenShape(MenuCommand menuCommand)
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<GameObject>("Packages/com.unity.2d.spriteshape/Editor/ObjectMenuCreation/DefaultAssets/Sprite Shapes/Open Sprite Shape.prefab") as GameObject;
|
||||
var preset = new PresetType(asset.GetComponent<SpriteShapeController>());
|
||||
var defaults = Preset.GetDefaultPresetsForType(preset).Count(x => x.enabled);
|
||||
if(defaults == 0)
|
||||
CreateGameObjectFromTemplate(asset, menuCommand);
|
||||
else
|
||||
{
|
||||
var go = CreateGameObject("Open Sprite Shape", menuCommand, new []{typeof(SpriteShapeController)});
|
||||
go.GetComponent<SpriteShapeController>().spline.isOpenEnded = true;
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("GameObject/2D Object/Sprite Shape/Closed Shape", false, k_MenuPriority)]
|
||||
static void MenuItem_GameObject2DObjectSpriteShapeClosedShape(MenuCommand menuCommand)
|
||||
{
|
||||
var asset = AssetDatabase.LoadAssetAtPath<GameObject>("Packages/com.unity.2d.spriteshape/Editor/ObjectMenuCreation/DefaultAssets/Sprite Shapes/Closed Sprite Shape.prefab") as GameObject;
|
||||
var preset = new PresetType(asset.GetComponent<SpriteShapeController>());
|
||||
var defaults = Preset.GetDefaultPresetsForType(preset).Count(x => x.enabled);
|
||||
if(defaults == 0)
|
||||
CreateGameObjectFromTemplate(asset, menuCommand);
|
||||
else
|
||||
{
|
||||
var go = CreateGameObject("Closed Sprite Shape", menuCommand, new []{typeof(SpriteShapeController)});
|
||||
go.GetComponent<SpriteShapeController>().spline.isOpenEnded = false;
|
||||
}
|
||||
}
|
||||
|
||||
static public GameObject CreateGameObjectFromTemplate(GameObject template, MenuCommand menuCommand)
|
||||
{
|
||||
var parent = menuCommand.context as GameObject;
|
||||
var fileName = System.IO.Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(template));
|
||||
var newGO = GameObject.Instantiate(template);
|
||||
newGO.name = fileName;
|
||||
Selection.activeObject = newGO;
|
||||
Place(newGO, parent);
|
||||
Undo.RegisterCreatedObjectUndo(newGO, string.Format("Create {0}", fileName));
|
||||
return newGO;
|
||||
}
|
||||
|
||||
static public GameObject CreateGameObject(string name, MenuCommand menuCommand, params Type[] components)
|
||||
{
|
||||
var parent = menuCommand.context as GameObject;
|
||||
var newGO = ObjectFactory.CreateGameObject(name, components);
|
||||
newGO.name = name;
|
||||
Selection.activeObject = newGO;
|
||||
Place(newGO, parent);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(newGO, string.Format("Create {0}", name));
|
||||
return newGO;
|
||||
}
|
||||
|
||||
internal static void Place(GameObject go, GameObject parentTransform)
|
||||
{
|
||||
if (parentTransform != null)
|
||||
{
|
||||
var transform = go.transform;
|
||||
Undo.SetTransformParent(transform, parentTransform.transform, "Reparenting");
|
||||
transform.localPosition = Vector3.zero;
|
||||
transform.localRotation = Quaternion.identity;
|
||||
transform.localScale = Vector3.one;
|
||||
go.layer = parentTransform.gameObject.layer;
|
||||
|
||||
if (parentTransform.GetComponent<RectTransform>())
|
||||
ObjectFactory.AddComponent<RectTransform>(go);
|
||||
}
|
||||
else
|
||||
{
|
||||
PlaceGameObjectInFrontOfSceneView(go);
|
||||
|
||||
StageUtility.PlaceGameObjectInCurrentStage(go); // may change parent
|
||||
}
|
||||
|
||||
// Only at this point do we know the actual parent of the object and can modify its name accordingly.
|
||||
GameObjectUtility.EnsureUniqueNameForSibling(go);
|
||||
Undo.SetCurrentGroupName("Create " + go.name);
|
||||
Selection.activeGameObject = go;
|
||||
if (EditorSettings.defaultBehaviorMode == EditorBehaviorMode.Mode2D)
|
||||
{
|
||||
var position = go.transform.position;
|
||||
position.z = 0;
|
||||
go.transform.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void PlaceGameObjectInFrontOfSceneView(GameObject go)
|
||||
{
|
||||
var view = SceneView.lastActiveSceneView;
|
||||
if (view != null)
|
||||
{
|
||||
view.MoveToView(go.transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,244 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Experimental.SceneManagement;
|
||||
using UnityEditor.U2D.SpriteShape;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
public static class SceneDragAndDrop
|
||||
{
|
||||
static SceneDragAndDrop()
|
||||
{
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
EditorApplication.hierarchyWindowItemOnGUI += OnHierarchyGUI;
|
||||
#else
|
||||
SceneView.onSceneGUIDelegate += OnSceneGUI;
|
||||
#endif
|
||||
}
|
||||
|
||||
static class Contents
|
||||
{
|
||||
public static readonly string createString = "Create Sprite Shape";
|
||||
}
|
||||
|
||||
static List<Object> s_SceneDragObjects;
|
||||
static DragType s_DragType;
|
||||
enum DragType { NotInitialized, CreateMultiple }
|
||||
|
||||
public delegate string ShowFileDialogDelegate(string title, string defaultName, string extension, string message, string defaultPath);
|
||||
|
||||
static void OnSceneGUI(SceneView sceneView)
|
||||
{
|
||||
HandleSceneDrag(sceneView, Event.current, DragAndDrop.objectReferences, DragAndDrop.paths);
|
||||
}
|
||||
|
||||
public static GameObject Create(UnityEngine.U2D.SpriteShape shape, Vector3 position, SceneView sceneView)
|
||||
{
|
||||
string name = string.IsNullOrEmpty(shape.name) ? "New SpriteShapeController" : shape.name;
|
||||
name = GameObjectUtility.GetUniqueNameForSibling(null, name);
|
||||
GameObject go = new GameObject(name);
|
||||
|
||||
SpriteShapeController shapeController = go.AddComponent<SpriteShapeController>();
|
||||
shapeController.spriteShape = shape;
|
||||
go.transform.position = position;
|
||||
go.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
return go;
|
||||
}
|
||||
|
||||
static void OnHierarchyGUI(int instanceID, Rect rect)
|
||||
{
|
||||
HandleSceneDrag(null, Event.current, DragAndDrop.objectReferences, null);
|
||||
}
|
||||
|
||||
static List<UnityEngine.U2D.SpriteShape> GetSpriteShapeFromPathsOrObjects(Object[] objects, string[] paths, EventType currentEventType)
|
||||
{
|
||||
List<UnityEngine.U2D.SpriteShape> result = new List<UnityEngine.U2D.SpriteShape>();
|
||||
|
||||
foreach (Object obj in objects)
|
||||
{
|
||||
if (AssetDatabase.Contains(obj))
|
||||
{
|
||||
if (obj is UnityEngine.U2D.SpriteShape)
|
||||
result.Add(obj as UnityEngine.U2D.SpriteShape);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void HandleSceneDrag(SceneView sceneView, Event evt, Object[] objectReferences, string[] paths)
|
||||
{
|
||||
if (evt.type != EventType.DragUpdated && evt.type != EventType.DragPerform && evt.type != EventType.DragExited)
|
||||
return;
|
||||
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.DragUpdated:
|
||||
{
|
||||
DragType newDragType = DragType.CreateMultiple;
|
||||
|
||||
if (s_DragType != newDragType || s_SceneDragObjects == null)
|
||||
// Either this is first time we are here OR evt.alt changed during drag
|
||||
{
|
||||
if (ExistingAssets(objectReferences)) // External drag with images that are not in the project
|
||||
{
|
||||
List<UnityEngine.U2D.SpriteShape> assets = GetSpriteShapeFromPathsOrObjects(objectReferences, paths,
|
||||
evt.type);
|
||||
|
||||
if (assets.Count == 0)
|
||||
return;
|
||||
|
||||
if (s_DragType != DragType.NotInitialized)
|
||||
// evt.alt changed during drag, so we need to cleanup and start over
|
||||
CleanUp(true);
|
||||
|
||||
s_DragType = newDragType;
|
||||
CreateSceneDragObjects(assets);
|
||||
}
|
||||
}
|
||||
|
||||
if (s_SceneDragObjects != null)
|
||||
{
|
||||
if (sceneView != null)
|
||||
PositionSceneDragObjects(s_SceneDragObjects, sceneView, evt.mousePosition);
|
||||
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType.DragPerform:
|
||||
{
|
||||
List<UnityEngine.U2D.SpriteShape> assets = GetSpriteShapeFromPathsOrObjects(objectReferences, paths, evt.type);
|
||||
|
||||
if (assets.Count > 0 && s_SceneDragObjects != null)
|
||||
{
|
||||
// For external drags, we have delayed all creation to DragPerform because only now we have the imported sprite assets
|
||||
if (s_SceneDragObjects.Count == 0)
|
||||
{
|
||||
CreateSceneDragObjects(assets);
|
||||
if (sceneView != null)
|
||||
PositionSceneDragObjects(s_SceneDragObjects, sceneView, evt.mousePosition);
|
||||
}
|
||||
|
||||
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
|
||||
foreach (GameObject dragGO in s_SceneDragObjects)
|
||||
{
|
||||
if (prefabStage != null)
|
||||
{
|
||||
var parTransform = Selection.activeTransform != null
|
||||
? Selection.activeTransform
|
||||
: prefabStage.prefabContentsRoot.transform;
|
||||
dragGO.transform.SetParent(parTransform, true);
|
||||
}
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(dragGO, "Create Shape");
|
||||
dragGO.hideFlags = HideFlags.None;
|
||||
}
|
||||
|
||||
Selection.objects = s_SceneDragObjects.ToArray();
|
||||
|
||||
CleanUp(false);
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType.DragExited:
|
||||
{
|
||||
if (s_SceneDragObjects != null)
|
||||
{
|
||||
CleanUp(true);
|
||||
evt.Use();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void PositionSceneDragObjects(List<Object> objects, SceneView sceneView, Vector2 mousePosition)
|
||||
{
|
||||
Vector3 position = Vector3.zero;
|
||||
position = HandleUtility.GUIPointToWorldRay(mousePosition).GetPoint(10);
|
||||
if (sceneView.in2DMode)
|
||||
{
|
||||
position.z = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
object hit = HandleUtility.RaySnap(HandleUtility.GUIPointToWorldRay(mousePosition));
|
||||
if (hit != null)
|
||||
{
|
||||
RaycastHit rh = (RaycastHit)hit;
|
||||
position = rh.point;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (GameObject gameObject in objects)
|
||||
{
|
||||
gameObject.transform.position = position;
|
||||
}
|
||||
}
|
||||
|
||||
static void CreateSceneDragObjects(List<UnityEngine.U2D.SpriteShape> shapes)
|
||||
{
|
||||
if (s_SceneDragObjects == null)
|
||||
s_SceneDragObjects = new List<Object>();
|
||||
|
||||
if (s_DragType == DragType.CreateMultiple)
|
||||
{
|
||||
foreach (UnityEngine.U2D.SpriteShape sprite in shapes)
|
||||
s_SceneDragObjects.Add(CreateDragGO(sprite, Vector3.zero));
|
||||
}
|
||||
else
|
||||
{
|
||||
s_SceneDragObjects.Add(CreateDragGO(shapes[0], Vector3.zero));
|
||||
}
|
||||
}
|
||||
|
||||
static void CleanUp(bool deleteTempSceneObject)
|
||||
{
|
||||
if (deleteTempSceneObject)
|
||||
{
|
||||
foreach (GameObject gameObject in s_SceneDragObjects)
|
||||
Object.DestroyImmediate(gameObject, false);
|
||||
}
|
||||
|
||||
if (s_SceneDragObjects != null)
|
||||
{
|
||||
s_SceneDragObjects.Clear();
|
||||
s_SceneDragObjects = null;
|
||||
}
|
||||
|
||||
s_DragType = DragType.NotInitialized;
|
||||
}
|
||||
|
||||
static bool ExistingAssets(Object[] objects)
|
||||
{
|
||||
foreach (Object obj in objects)
|
||||
{
|
||||
if (AssetDatabase.Contains(obj))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static GameObject CreateDragGO(UnityEngine.U2D.SpriteShape spriteShape, Vector3 position)
|
||||
{
|
||||
SpriteShapeController spriteShapeController = SpriteShapeEditorUtility.CreateSpriteShapeController(spriteShape);
|
||||
GameObject gameObject = spriteShapeController.gameObject;
|
||||
gameObject.transform.position = position;
|
||||
gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
spriteShapeController.spriteShape = spriteShape;
|
||||
|
||||
SpriteShapeEditorUtility.SetShapeFromAsset(spriteShapeController);
|
||||
|
||||
return gameObject;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,312 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
internal class SpriteSelector
|
||||
{
|
||||
int m_SelectedSprite = 0;
|
||||
Sprite[] m_SpriteList = null;
|
||||
Texture[] m_Thumbnails;
|
||||
Vector2 m_ScrollPos;
|
||||
|
||||
public int selectedIndex
|
||||
{
|
||||
get { return m_SelectedSprite; }
|
||||
set { m_SelectedSprite = value; }
|
||||
}
|
||||
internal static class Styles
|
||||
{
|
||||
public static GUIStyle gridList = "GridList";
|
||||
public static GUIContent spriteList = EditorGUIUtility.TrTextContent("Sprite Variant");
|
||||
public static GUIContent missingSprites = EditorGUIUtility.TrTextContent("No brushes defined.");
|
||||
public static GUIStyle localGrid = null;
|
||||
}
|
||||
public SpriteSelector()
|
||||
{
|
||||
m_SpriteList = null;
|
||||
}
|
||||
|
||||
public void UpdateSprites(Sprite[] sprites)
|
||||
{
|
||||
m_SpriteList = sprites;
|
||||
UpdateSelection(0);
|
||||
}
|
||||
|
||||
public void UpdateSelection(int newSelectedBrush)
|
||||
{
|
||||
m_SelectedSprite = newSelectedBrush;
|
||||
}
|
||||
|
||||
private static int CalcTotalHorizSpacing(int xCount, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle)
|
||||
{
|
||||
if (xCount < 2)
|
||||
return 0;
|
||||
if (xCount == 2)
|
||||
return Mathf.Max(firstStyle.margin.right, lastStyle.margin.left);
|
||||
|
||||
int internalSpace = Mathf.Max(midStyle.margin.left, midStyle.margin.right);
|
||||
return Mathf.Max(firstStyle.margin.right, midStyle.margin.left) + Mathf.Max(midStyle.margin.right, lastStyle.margin.left) + internalSpace * (xCount - 3);
|
||||
}
|
||||
|
||||
// Helper function: Get all mouse rects
|
||||
private static Rect[] CalcMouseRects(Rect position, GUIContent[] contents, int xCount, float elemWidth, float elemHeight, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, bool addBorders, GUI.ToolbarButtonSize buttonSize)
|
||||
{
|
||||
int count = contents.Length;
|
||||
int x = 0;
|
||||
float xPos = position.xMin, yPos = position.yMin;
|
||||
GUIStyle currentStyle = style;
|
||||
Rect[] retval = new Rect[count];
|
||||
if (count > 1)
|
||||
currentStyle = firstStyle;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float w = 0;
|
||||
switch (buttonSize)
|
||||
{
|
||||
case GUI.ToolbarButtonSize.Fixed:
|
||||
w = elemWidth;
|
||||
break;
|
||||
case GUI.ToolbarButtonSize.FitToContents:
|
||||
w = currentStyle.CalcSize(contents[i]).x;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!addBorders)
|
||||
retval[i] = new Rect(xPos, yPos, w, elemHeight);
|
||||
else
|
||||
retval[i] = currentStyle.margin.Add(new Rect(xPos, yPos, w, elemHeight));
|
||||
|
||||
//we round the values to the dpi-aware pixel grid
|
||||
retval[i] = GUIUtility.AlignRectToDevice(retval[i]);
|
||||
|
||||
GUIStyle nextStyle = midStyle;
|
||||
if (i == count - 2 || i == xCount - 2)
|
||||
nextStyle = lastStyle;
|
||||
|
||||
xPos = retval[i].xMax + Mathf.Max(currentStyle.margin.right, nextStyle.margin.left);
|
||||
|
||||
x++;
|
||||
if (x >= xCount)
|
||||
{
|
||||
x = 0;
|
||||
yPos += elemHeight + Mathf.Max(style.margin.top, style.margin.bottom);
|
||||
xPos = position.xMin;
|
||||
nextStyle = firstStyle;
|
||||
}
|
||||
|
||||
currentStyle = nextStyle;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void DrawRectangleOutline(Rect rect, Color color)
|
||||
{
|
||||
Color currentColor = Handles.color;
|
||||
Handles.color = color;
|
||||
|
||||
// Draw viewport outline
|
||||
Vector3[] points = new Vector3[5];
|
||||
points[0] = new Vector3(rect.x, rect.y, 0.0f);
|
||||
points[1] = new Vector3(rect.x + rect.width, rect.y, 0.0f);
|
||||
points[2] = new Vector3(rect.x + rect.width, rect.y + rect.height, 0.0f);
|
||||
points[3] = new Vector3(rect.x, rect.y + rect.height, 0.0f);
|
||||
points[4] = new Vector3(rect.x, rect.y, 0.0f);
|
||||
Handles.DrawPolyLine(points);
|
||||
|
||||
Handles.color = currentColor;
|
||||
}
|
||||
|
||||
// Make a button grid
|
||||
private static int DoButtonGrid(Rect position, int selected, GUIContent[] contents, string[] controlNames, int xCount, GUIStyle style, GUIStyle firstStyle, GUIStyle midStyle, GUIStyle lastStyle, GUI.ToolbarButtonSize buttonSize, bool[] contentsEnabled = null)
|
||||
{
|
||||
int count = contents.Length;
|
||||
if (count == 0)
|
||||
return selected;
|
||||
if (xCount <= 0)
|
||||
{
|
||||
Debug.LogWarning("You are trying to create a SelectionGrid with zero or less elements to be displayed in the horizontal direction. Set xCount to a positive value.");
|
||||
return selected;
|
||||
}
|
||||
|
||||
if (contentsEnabled != null && contentsEnabled.Length != count)
|
||||
throw new ArgumentException("contentsEnabled");
|
||||
|
||||
// Figure out how large each element should be
|
||||
int rows = count / xCount;
|
||||
if (count % xCount != 0)
|
||||
rows++;
|
||||
|
||||
float totalHorizSpacing = CalcTotalHorizSpacing(xCount, style, firstStyle, midStyle, lastStyle);
|
||||
float totalVerticalSpacing = Mathf.Max(style.margin.top, style.margin.bottom) * (rows - 1);
|
||||
float elemWidth = (position.width - totalHorizSpacing) / xCount;
|
||||
float elemHeight = (position.height - totalVerticalSpacing) / rows;
|
||||
|
||||
if (style.fixedWidth != 0)
|
||||
elemWidth = style.fixedWidth;
|
||||
if (style.fixedHeight != 0)
|
||||
elemHeight = style.fixedHeight;
|
||||
|
||||
Rect[] buttonRects = CalcMouseRects(position, contents, xCount, elemWidth, elemHeight, style, firstStyle, midStyle, lastStyle, false, buttonSize);
|
||||
GUIStyle selectedButtonStyle = null;
|
||||
int selectedButtonID = 0;
|
||||
for (int buttonIndex = 0; buttonIndex < count; ++buttonIndex)
|
||||
{
|
||||
bool wasEnabled = GUI.enabled;
|
||||
GUI.enabled &= (contentsEnabled == null || contentsEnabled[buttonIndex]);
|
||||
var buttonRect = buttonRects[buttonIndex];
|
||||
var content = contents[buttonIndex];
|
||||
|
||||
if (controlNames != null)
|
||||
GUI.SetNextControlName(controlNames[buttonIndex]);
|
||||
var id = GUIUtility.GetControlID("ButtonGrid".GetHashCode(), FocusType.Passive, buttonRect);
|
||||
if (buttonIndex == selected)
|
||||
selectedButtonID = id;
|
||||
|
||||
switch (Event.current.GetTypeForControl(id))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (buttonRect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
GUIUtility.hotControl = id;
|
||||
Event.current.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if (GUIUtility.hotControl == id)
|
||||
Event.current.Use();
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == id)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
Event.current.Use();
|
||||
|
||||
GUI.changed = true;
|
||||
return buttonIndex;
|
||||
}
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
var buttonStyle = count == 1 ? style : (buttonIndex == 0 ? firstStyle : (buttonIndex == count - 1 ? lastStyle : midStyle));
|
||||
var isSelected = selected == buttonIndex;
|
||||
|
||||
if (!isSelected)
|
||||
{
|
||||
GUI.DrawTexture(buttonRect, content.image, ScaleMode.ScaleToFit, true);
|
||||
GUI.Label(new Rect(buttonRect.x, buttonRect.y, 32, 32), buttonIndex.ToString());
|
||||
}
|
||||
else
|
||||
selectedButtonStyle = buttonStyle;
|
||||
break;
|
||||
}
|
||||
|
||||
GUI.enabled = wasEnabled;
|
||||
}
|
||||
|
||||
// draw selected button at the end so it overflows nicer
|
||||
if (selectedButtonStyle != null)
|
||||
{
|
||||
var buttonRect = buttonRects[selected];
|
||||
var content = contents[selected];
|
||||
var wasEnabled = GUI.enabled;
|
||||
GUI.enabled &= (contentsEnabled == null || contentsEnabled[selected]);
|
||||
|
||||
GUI.DrawTexture(new Rect(buttonRect.x + 4, buttonRect.y + 4, buttonRect.width - 8, buttonRect.height - 8), content.image, ScaleMode.ScaleToFit, true);
|
||||
DrawRectangleOutline(buttonRect, GUI.skin.settings.selectionColor);
|
||||
GUI.Label(new Rect(buttonRect.x, buttonRect.y, 32, 32), selected.ToString());
|
||||
GUI.enabled = wasEnabled;
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
// Get Temp Texture Contents for Sprites.
|
||||
private static GUIContent[] Temp(Texture[] images)
|
||||
{
|
||||
GUIContent[] retval = new GUIContent[images.Length];
|
||||
for (int i = 0; i < images.Length; i++)
|
||||
{
|
||||
retval[i] = new GUIContent(images[i]);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
public Sprite GetActiveSprite()
|
||||
{
|
||||
if (m_SelectedSprite >= m_SpriteList.Length)
|
||||
m_SelectedSprite = 0;
|
||||
return m_SpriteList[m_SelectedSprite];
|
||||
}
|
||||
|
||||
public bool ShowGUI(int selectedIndex)
|
||||
{
|
||||
bool repaint = false;
|
||||
if (m_SpriteList == null || m_SpriteList.Length == 0)
|
||||
return false;
|
||||
int approxSize = 64;
|
||||
int approxHolderSize = 66;
|
||||
|
||||
if (Styles.localGrid == null)
|
||||
{
|
||||
Styles.localGrid = new GUIStyle(Styles.gridList);
|
||||
Styles.localGrid.fixedWidth = approxSize;
|
||||
Styles.localGrid.fixedHeight = approxSize;
|
||||
}
|
||||
m_SelectedSprite = (selectedIndex > m_SpriteList.Length) ? 0 : selectedIndex;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
{
|
||||
GUILayout.Label(Styles.spriteList, EditorStyles.label, GUILayout.Width(EditorGUIUtility.labelWidth - 5));
|
||||
|
||||
int cviewwidth = (int)(EditorGUIUtility.currentViewWidth - EditorGUIUtility.labelWidth - approxSize);
|
||||
int columns = (int)(cviewwidth) / approxSize;
|
||||
columns = columns == 0 ? 1 : columns;
|
||||
int rows = (int)Mathf.Ceil((m_SpriteList.Length + columns - 1) / columns);
|
||||
int lyColumns = (rows == 1) ? (approxHolderSize): (approxHolderSize * 2);
|
||||
|
||||
GUILayout.BeginVertical("box", new GUILayoutOption[] { GUILayout.Height(lyColumns) } );
|
||||
{
|
||||
m_ScrollPos = EditorGUILayout.BeginScrollView(m_ScrollPos, new GUILayoutOption[] { GUILayout.Height(lyColumns) });
|
||||
int newBrush = SpriteSelectionGrid(m_SelectedSprite, m_SpriteList, approxSize, Styles.localGrid, Styles.missingSprites, columns, rows);
|
||||
if (newBrush != m_SelectedSprite)
|
||||
{
|
||||
UpdateSelection(newBrush);
|
||||
repaint = true;
|
||||
}
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
return repaint;
|
||||
}
|
||||
|
||||
int SpriteSelectionGrid(int selected, Sprite[] sprites, int approxSize, GUIStyle style, GUIContent emptyString, int columns, int rows)
|
||||
{
|
||||
int retval = 0;
|
||||
if (sprites.Length != 0)
|
||||
{
|
||||
Rect r = GUILayoutUtility.GetRect((float)columns * approxSize, (float)rows * approxSize);
|
||||
Event evt = Event.current;
|
||||
if (evt.type == EventType.MouseDown && evt.clickCount == 2 && r.Contains(evt.mousePosition))
|
||||
evt.Use();
|
||||
|
||||
m_Thumbnails = PreviewTexturesFromSprites(sprites);
|
||||
retval = DoButtonGrid(r, selected, Temp(m_Thumbnails), null, (int)columns, style, style, style, style, GUI.ToolbarButtonSize.FitToContents);
|
||||
}
|
||||
else
|
||||
GUILayout.Label(emptyString);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
||||
internal static Texture[] PreviewTexturesFromSprites(Sprite[] sprites)
|
||||
{
|
||||
Texture[] retval = new Texture[sprites.Length];
|
||||
for (int i = 0; i < sprites.Length; i++)
|
||||
retval[i] = AssetPreview.GetAssetPreview(sprites[i]) ?? Texture2D.whiteTexture;
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
} //namespace
|
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Analytics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
|
||||
internal class SpriteShapeAnalyticsEvents
|
||||
{
|
||||
public class SpriteShapeEvent : UnityEvent<UnityEngine.U2D.SpriteShape> { }
|
||||
public class SpriteShapeRendererEvent : UnityEvent<SpriteShapeRenderer> { }
|
||||
|
||||
private SpriteShapeEvent m_SpriteShape = new SpriteShapeEvent();
|
||||
private SpriteShapeRendererEvent m_SpriteShapeRenderer = new SpriteShapeRendererEvent();
|
||||
|
||||
public virtual SpriteShapeEvent spriteShapeEvent { get { return m_SpriteShape; } }
|
||||
public virtual SpriteShapeRendererEvent spriteShapeRendererEvent { get { return m_SpriteShapeRenderer; } }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
enum SpriteShapeAnalyticsEventType
|
||||
{
|
||||
SpriteShapeProfileCreated = 0,
|
||||
SpriteShapeRendererCreated = 1
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
struct SpriteShapeAnalyticsEvent
|
||||
{
|
||||
[SerializeField]
|
||||
public SpriteShapeAnalyticsEventType sub_type;
|
||||
[SerializeField]
|
||||
public string data;
|
||||
}
|
||||
|
||||
internal interface ISpriteShapeAnalyticsStorage
|
||||
{
|
||||
AnalyticsResult SendUsageEvent(SpriteShapeAnalyticsEvent evt);
|
||||
void Dispose();
|
||||
}
|
||||
|
||||
internal static class SpriteShapeAnalyticConstant
|
||||
{
|
||||
public const int k_MaxEventsPerHour = 1000;
|
||||
public const int k_MaxNumberOfElements = 1000;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SpriteShapeAnalytics
|
||||
{
|
||||
const int k_SpriteShapeEventElementCount = 2;
|
||||
ISpriteShapeAnalyticsStorage m_AnalyticsStorage;
|
||||
[SerializeField]
|
||||
SpriteShapeAnalyticsEvents m_EventBus = null;
|
||||
|
||||
internal SpriteShapeAnalyticsEvents eventBus
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_EventBus == null)
|
||||
m_EventBus = new SpriteShapeAnalyticsEvents();
|
||||
return m_EventBus;
|
||||
}
|
||||
}
|
||||
|
||||
public SpriteShapeAnalytics(ISpriteShapeAnalyticsStorage analyticsStorage)
|
||||
{
|
||||
m_AnalyticsStorage = analyticsStorage;
|
||||
eventBus.spriteShapeEvent.AddListener(OnSpriteShapeCreated);
|
||||
eventBus.spriteShapeRendererEvent.AddListener(OnSpriteShapeRendererCreated);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
eventBus.spriteShapeEvent.RemoveListener(OnSpriteShapeCreated);
|
||||
eventBus.spriteShapeRendererEvent.RemoveListener(OnSpriteShapeRendererCreated);
|
||||
m_AnalyticsStorage.Dispose();
|
||||
}
|
||||
|
||||
void OnSpriteShapeCreated(UnityEngine.U2D.SpriteShape shape)
|
||||
{
|
||||
SendUsageEvent(new SpriteShapeAnalyticsEvent()
|
||||
{
|
||||
sub_type = SpriteShapeAnalyticsEventType.SpriteShapeProfileCreated,
|
||||
data = ""
|
||||
});
|
||||
}
|
||||
|
||||
void OnSpriteShapeRendererCreated(SpriteShapeRenderer renderer)
|
||||
{
|
||||
SendUsageEvent(new SpriteShapeAnalyticsEvent()
|
||||
{
|
||||
sub_type = SpriteShapeAnalyticsEventType.SpriteShapeRendererCreated,
|
||||
data = ""
|
||||
});
|
||||
}
|
||||
|
||||
public void SendUsageEvent(SpriteShapeAnalyticsEvent evt)
|
||||
{
|
||||
m_AnalyticsStorage.SendUsageEvent(evt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// For testing.
|
||||
internal class SpriteShapeJsonAnalyticsStorage : ISpriteShapeAnalyticsStorage
|
||||
{
|
||||
[Serializable]
|
||||
struct SpriteShapeToolEvents
|
||||
{
|
||||
[SerializeField]
|
||||
public List<SpriteShapeAnalyticsEvent> events;
|
||||
}
|
||||
|
||||
SpriteShapeToolEvents m_TotalEvents = new SpriteShapeToolEvents()
|
||||
{
|
||||
events = new List<SpriteShapeAnalyticsEvent>()
|
||||
};
|
||||
|
||||
public AnalyticsResult SendUsageEvent(SpriteShapeAnalyticsEvent evt)
|
||||
{
|
||||
m_TotalEvents.events.Add(evt);
|
||||
return AnalyticsResult.Ok;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
try
|
||||
{
|
||||
string file = string.Format("analytics_{0}.json", System.DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss"));
|
||||
if (System.IO.File.Exists(file))
|
||||
System.IO.File.Delete(file);
|
||||
System.IO.File.WriteAllText(file, JsonUtility.ToJson(m_TotalEvents, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.Log(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_TotalEvents.events.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
internal class SpriteShapeUnityAnalyticsStorage : ISpriteShapeAnalyticsStorage
|
||||
{
|
||||
const string k_VendorKey = "unity.2d.spriteshape";
|
||||
const int k_Version = 1;
|
||||
|
||||
public SpriteShapeUnityAnalyticsStorage()
|
||||
{
|
||||
EditorAnalytics.RegisterEventWithLimit("u2dSpriteShapeToolUsage", SpriteShapeAnalyticConstant.k_MaxEventsPerHour, SpriteShapeAnalyticConstant.k_MaxNumberOfElements, k_VendorKey, k_Version);
|
||||
}
|
||||
|
||||
public AnalyticsResult SendUsageEvent(SpriteShapeAnalyticsEvent evt)
|
||||
{
|
||||
return EditorAnalytics.SendEventWithLimit("u2dSpriteShapeToolUsage", evt, k_Version);
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public class SpriteShapeAssetPostProcessor : AssetPostprocessor
|
||||
{
|
||||
static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
|
||||
{
|
||||
if (importedAssets.Length > 0)
|
||||
{
|
||||
GameObject[] allGOs = UnityEngine.Object.FindObjectsOfType<GameObject>();
|
||||
foreach (GameObject go in allGOs)
|
||||
{
|
||||
if (!go.activeInHierarchy)
|
||||
continue;
|
||||
SpriteShapeController sc = go.GetComponent<SpriteShapeController>();
|
||||
if (sc != null)
|
||||
sc.RefreshSpriteShape();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,605 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor;
|
||||
using UnityEditor.U2D.SpriteShapeInternal;
|
||||
using UnityEditor.U2D.Common;
|
||||
using UnityEditor.AnimatedValues;
|
||||
using UnityEditor.U2D.Path;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
|
||||
[CustomEditor(typeof(SpriteShapeController))]
|
||||
[CanEditMultipleObjects]
|
||||
internal class SpriteShapeControllerEditor : PathComponentEditor<CustomPath>
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent splineLabel = new GUIContent("Spline");
|
||||
public static readonly string editSplineLabel = "Edit Spline";
|
||||
public static readonly GUIContent fillLabel = new GUIContent("Fill");
|
||||
public static readonly GUIContent colliderLabel = new GUIContent("Collider");
|
||||
public static readonly GUIContent fillPixelPerUnitLabel = new GUIContent("Pixel Per Unit", "Pixel Per Unit for fill texture.");
|
||||
public static readonly GUIContent spriteShapeProfile = new GUIContent("Profile", "The SpriteShape Profile to render");
|
||||
public static readonly GUIContent materialLabel = new GUIContent("Material", "Material to be used by SpriteRenderer");
|
||||
public static readonly GUIContent colorLabel = new GUIContent("Color", "Rendering color for the Sprite graphic");
|
||||
public static readonly GUIContent metaDataLabel = new GUIContent("Meta Data", "SpriteShape specific controlpoint data");
|
||||
public static readonly GUIContent showComponentsLabel = new GUIContent("Show Render Stuff", "Show Renderer Components.");
|
||||
public static readonly GUIContent[] splineDetailOptions = { new GUIContent("High Quality"), new GUIContent("Medium Quality"), new GUIContent("Low Quality") };
|
||||
public static readonly GUIContent splineDetail = new GUIContent("Detail", "Tessellation Quality for rendering.");
|
||||
public static readonly GUIContent openEndedLabel = new GUIContent("Open Ended", "Is the path open ended or closed.");
|
||||
public static readonly GUIContent adaptiveUVLabel = new GUIContent("Adaptive UV", "Allow Adaptive UV Generation");
|
||||
public static readonly GUIContent enableTangentsLabel = new GUIContent("Enable Tangents", "Enable Tangents for 2D Lighting.");
|
||||
public static readonly GUIContent worldUVLabel = new GUIContent("Worldspace UV", "Generate UV for world space.");
|
||||
public static readonly GUIContent stretchUVLabel = new GUIContent("Stretch UV", "Stretch the Fill UV to full Rect.");
|
||||
public static readonly GUIContent stretchTilingLabel = new GUIContent("Stretch Tiling", "Stretch Tiling Count.");
|
||||
public static readonly GUIContent colliderDetail = new GUIContent("Detail", "Tessellation Quality on the collider.");
|
||||
public static readonly GUIContent cornerThresholdDetail = new GUIContent("Corner Threshold", "Corner angle threshold below which corners wont be placed.");
|
||||
public static readonly GUIContent colliderOffset = new GUIContent("Offset", "Extrude collider distance.");
|
||||
public static readonly GUIContent updateColliderLabel = new GUIContent("Update Collider", "Update Collider as you edit SpriteShape");
|
||||
public static readonly GUIContent optimizeColliderLabel = new GUIContent("Optimize Collider", "Cleanup planar self-intersections and optimize collider points");
|
||||
public static readonly GUIContent optimizeGeometryLabel = new GUIContent("Optimize Geometry", "Simplify geometry");
|
||||
public static readonly GUIContent cacheGeometryLabel = new GUIContent("Cache Geometry", "Bake geometry data. This will save geometry data on editor and load it on runtime instead of generating.");
|
||||
public static readonly GUIContent uTess2DLabel = new GUIContent("Fill Tessellation (C# Job)", "Use C# Jobs to generate Fill Geometry. (Edge geometry always uses C# Jobs)");
|
||||
}
|
||||
|
||||
private SerializedProperty m_SpriteShapeProp;
|
||||
private SerializedProperty m_SplineDetailProp;
|
||||
private SerializedProperty m_IsOpenEndedProp;
|
||||
private SerializedProperty m_AdaptiveUVProp;
|
||||
private SerializedProperty m_StretchUVProp;
|
||||
private SerializedProperty m_StretchTilingProp;
|
||||
private SerializedProperty m_WorldSpaceUVProp;
|
||||
private SerializedProperty m_FillPixelPerUnitProp;
|
||||
private SerializedProperty m_CornerAngleThresholdProp;
|
||||
|
||||
private SerializedProperty m_ColliderAutoUpdate;
|
||||
private SerializedProperty m_ColliderDetailProp;
|
||||
private SerializedProperty m_ColliderOffsetProp;
|
||||
|
||||
private SerializedProperty m_OptimizeColliderProp;
|
||||
private SerializedProperty m_OptimizeGeometryProp;
|
||||
private SerializedProperty m_EnableTangentsProp;
|
||||
private SerializedProperty m_GeometryCachedProp;
|
||||
private SerializedProperty m_UTess2DGeometryProp;
|
||||
|
||||
private int m_CollidersCount = 0;
|
||||
private int[] m_QualityValues = new int[] { (int)QualityDetail.High, (int)QualityDetail.Mid, (int)QualityDetail.Low };
|
||||
readonly AnimBool m_ShowStretchOption = new AnimBool();
|
||||
readonly AnimBool m_ShowNonStretchOption = new AnimBool();
|
||||
private struct ShapeSegment
|
||||
{
|
||||
public int start;
|
||||
public int end;
|
||||
public int angleRange;
|
||||
};
|
||||
|
||||
private struct ShapeAngleRange
|
||||
{
|
||||
public float start;
|
||||
public float end;
|
||||
public int order;
|
||||
public int index;
|
||||
};
|
||||
|
||||
int m_SelectedPoint = -1;
|
||||
int m_SelectedAngleRange = -1;
|
||||
int m_SpriteShapeHashCode = 0;
|
||||
int m_SplineHashCode = 0;
|
||||
List<ShapeSegment> m_ShapeSegments = new List<ShapeSegment>();
|
||||
SpriteSelector spriteSelector = new SpriteSelector();
|
||||
|
||||
private SpriteShapeController m_SpriteShapeController
|
||||
{
|
||||
get { return target as SpriteShapeController; }
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (target == null)
|
||||
return;
|
||||
|
||||
m_SpriteShapeProp = serializedObject.FindProperty("m_SpriteShape");
|
||||
m_SplineDetailProp = serializedObject.FindProperty("m_SplineDetail");
|
||||
m_IsOpenEndedProp = serializedObject.FindProperty("m_Spline").FindPropertyRelative("m_IsOpenEnded");
|
||||
m_AdaptiveUVProp = serializedObject.FindProperty("m_AdaptiveUV");
|
||||
m_StretchUVProp = serializedObject.FindProperty("m_StretchUV");
|
||||
m_StretchTilingProp = serializedObject.FindProperty("m_StretchTiling");
|
||||
m_WorldSpaceUVProp = serializedObject.FindProperty("m_WorldSpaceUV");
|
||||
m_FillPixelPerUnitProp = serializedObject.FindProperty("m_FillPixelPerUnit");
|
||||
m_CornerAngleThresholdProp = serializedObject.FindProperty("m_CornerAngleThreshold");
|
||||
|
||||
m_ColliderAutoUpdate = serializedObject.FindProperty("m_UpdateCollider");
|
||||
m_ColliderDetailProp = serializedObject.FindProperty("m_ColliderDetail");
|
||||
m_ColliderOffsetProp = serializedObject.FindProperty("m_ColliderOffset");
|
||||
m_OptimizeColliderProp = serializedObject.FindProperty("m_OptimizeCollider");
|
||||
m_OptimizeGeometryProp = serializedObject.FindProperty("m_OptimizeGeometry");
|
||||
m_EnableTangentsProp = serializedObject.FindProperty("m_EnableTangents");
|
||||
m_GeometryCachedProp = serializedObject.FindProperty("m_GeometryCached");
|
||||
m_UTess2DGeometryProp = serializedObject.FindProperty("m_UTess2D");
|
||||
|
||||
m_ShowStretchOption.valueChanged.AddListener(Repaint);
|
||||
m_ShowStretchOption.value = ShouldShowStretchOption();
|
||||
|
||||
m_ShowNonStretchOption.valueChanged.AddListener(Repaint);
|
||||
m_ShowNonStretchOption.value = !ShouldShowStretchOption();
|
||||
}
|
||||
|
||||
private bool OnCollidersAddedOrRemoved()
|
||||
{
|
||||
PolygonCollider2D polygonCollider = m_SpriteShapeController.polygonCollider;
|
||||
EdgeCollider2D edgeCollider = m_SpriteShapeController.edgeCollider;
|
||||
int collidersCount = 0;
|
||||
|
||||
if (polygonCollider != null)
|
||||
collidersCount = collidersCount + 1;
|
||||
if (edgeCollider != null)
|
||||
collidersCount = collidersCount + 1;
|
||||
|
||||
if (collidersCount != m_CollidersCount)
|
||||
{
|
||||
m_CollidersCount = collidersCount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DrawHeader(GUIContent content)
|
||||
{
|
||||
EditorGUILayout.LabelField(content, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
private bool ShouldShowStretchOption()
|
||||
{
|
||||
return m_StretchUVProp.boolValue;
|
||||
}
|
||||
|
||||
static bool WithinRange(ShapeAngleRange angleRange, float inputAngle)
|
||||
{
|
||||
float range = angleRange.end - angleRange.start;
|
||||
float angle = Mathf.Repeat(inputAngle - angleRange.start, 360f);
|
||||
angle = (angle == 360.0f) ? 0 : angle;
|
||||
return (angle >= 0f && angle <= range);
|
||||
}
|
||||
|
||||
static int RangeFromAngle(List<ShapeAngleRange> angleRanges, float angle)
|
||||
{
|
||||
foreach (var range in angleRanges)
|
||||
{
|
||||
if (WithinRange(range, angle))
|
||||
return range.index;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private List<ShapeAngleRange> GetAngleRangeSorted(UnityEngine.U2D.SpriteShape ss)
|
||||
{
|
||||
List<ShapeAngleRange> angleRanges = new List<ShapeAngleRange>();
|
||||
int i = 0;
|
||||
foreach (var angleRange in ss.angleRanges)
|
||||
{
|
||||
ShapeAngleRange sar = new ShapeAngleRange() { start = angleRange.start, end = angleRange.end, order = angleRange.order, index = i };
|
||||
angleRanges.Add(sar);
|
||||
i++;
|
||||
}
|
||||
angleRanges.Sort((a, b) => a.order.CompareTo(b.order));
|
||||
return angleRanges;
|
||||
}
|
||||
|
||||
private void GenerateSegments(SpriteShapeController sc, List<ShapeAngleRange> angleRanges)
|
||||
{
|
||||
var controlPointCount = sc.spline.GetPointCount();
|
||||
var angleRangeIndices = new int[controlPointCount];
|
||||
ShapeSegment activeSegment = new ShapeSegment() { start = -1, end = -1, angleRange = -1 };
|
||||
m_ShapeSegments.Clear();
|
||||
|
||||
for (int i = 0; i < controlPointCount; ++i)
|
||||
{
|
||||
var actv = i;
|
||||
var next = SplineUtility.NextIndex(actv, controlPointCount);
|
||||
var pos1 = sc.spline.GetPosition(actv);
|
||||
var pos2 = sc.spline.GetPosition(next);
|
||||
bool continueStrip = (sc.spline.GetTangentMode(actv) == ShapeTangentMode.Continuous), edgeUpdated = false;
|
||||
float angle = 0;
|
||||
if (false == continueStrip || activeSegment.start == -1)
|
||||
angle = SplineUtility.SlopeAngle(pos1, pos2) + 90.0f;
|
||||
|
||||
next = (!sc.spline.isOpenEnded && next == 0) ? (actv + 1) : next;
|
||||
int mn = (actv < next) ? actv : next;
|
||||
int mx = (actv > next) ? actv : next;
|
||||
|
||||
var anglerange = RangeFromAngle(angleRanges, angle);
|
||||
angleRangeIndices[actv] = anglerange;
|
||||
if (anglerange == -1)
|
||||
{
|
||||
activeSegment = new ShapeSegment() { start = mn, end = mx, angleRange = anglerange };
|
||||
m_ShapeSegments.Add(activeSegment);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for Segments. Also check if the Segment Start has been resolved. Otherwise simply start with the next one
|
||||
if (activeSegment.start != -1)
|
||||
continueStrip = continueStrip && (angleRangeIndices[activeSegment.start] != -1);
|
||||
|
||||
bool canContinue = (actv != (controlPointCount - 1)) || (!sc.spline.isOpenEnded && (actv == (controlPointCount - 1)));
|
||||
if (continueStrip && canContinue)
|
||||
{
|
||||
for (int s = 0; s < m_ShapeSegments.Count; ++s)
|
||||
{
|
||||
activeSegment = m_ShapeSegments[s];
|
||||
if (activeSegment.start - mn == 1)
|
||||
{
|
||||
edgeUpdated = true;
|
||||
activeSegment.start = mn;
|
||||
m_ShapeSegments[s] = activeSegment;
|
||||
break;
|
||||
}
|
||||
if (mx - activeSegment.end == 1)
|
||||
{
|
||||
edgeUpdated = true;
|
||||
activeSegment.end = mx;
|
||||
m_ShapeSegments[s] = activeSegment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!edgeUpdated)
|
||||
{
|
||||
activeSegment.start = mn;
|
||||
activeSegment.end = mx;
|
||||
activeSegment.angleRange = anglerange;
|
||||
m_ShapeSegments.Add(activeSegment);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private int GetAngleRange(SpriteShapeController sc, int point, ref int startPoint)
|
||||
{
|
||||
int angleRange = -1;
|
||||
startPoint = point;
|
||||
for (int i = 0; i < m_ShapeSegments.Count; ++i)
|
||||
{
|
||||
if (point >= m_ShapeSegments[i].start && point < m_ShapeSegments[i].end)
|
||||
{
|
||||
angleRange = m_ShapeSegments[i].angleRange;
|
||||
startPoint = point; // m_ShapeSegments[i].start;
|
||||
if (angleRange >= sc.spriteShape.angleRanges.Count)
|
||||
angleRange = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return angleRange;
|
||||
}
|
||||
|
||||
private void UpdateSegments()
|
||||
{
|
||||
var sc = target as SpriteShapeController;
|
||||
|
||||
// Either SpriteShape Asset or SpriteShape Data has changed.
|
||||
if (m_SpriteShapeHashCode != sc.spriteShapeHashCode || m_SplineHashCode != sc.splineHashCode)
|
||||
{
|
||||
List<ShapeAngleRange> angleRanges = GetAngleRangeSorted(sc.spriteShape);
|
||||
GenerateSegments(sc, angleRanges);
|
||||
m_SpriteShapeHashCode = sc.spriteShapeHashCode;
|
||||
m_SplineHashCode = sc.splineHashCode;
|
||||
m_SelectedPoint = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private int ResolveSpriteIndex(List<int> spriteIndices, ISelection<int> selection, ref List<int> startPoints)
|
||||
{
|
||||
var spriteIndexValue = spriteIndices.FirstOrDefault();
|
||||
var sc = target as SpriteShapeController;
|
||||
var spline = sc.spline;
|
||||
|
||||
if (sc == null || sc.spriteShape == null)
|
||||
return -1;
|
||||
UpdateSegments();
|
||||
|
||||
if (sc.spriteShape != null)
|
||||
{
|
||||
if (selection.Count == 1)
|
||||
{
|
||||
m_SelectedAngleRange = GetAngleRange(sc, selection.elements[0], ref m_SelectedPoint);
|
||||
startPoints.Add(m_SelectedPoint);
|
||||
spriteIndexValue = spline.GetSpriteIndex(m_SelectedPoint);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SelectedAngleRange = -1;
|
||||
foreach (var index in selection.elements)
|
||||
{
|
||||
int startPoint = index;
|
||||
int angleRange = GetAngleRange(sc, index, ref startPoint);
|
||||
if (m_SelectedAngleRange != -1 && angleRange != m_SelectedAngleRange)
|
||||
{
|
||||
m_SelectedAngleRange = -1;
|
||||
break;
|
||||
}
|
||||
startPoints.Add(startPoint);
|
||||
m_SelectedAngleRange = angleRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_SelectedAngleRange != -1)
|
||||
spriteSelector.UpdateSprites(sc.spriteShape.angleRanges[m_SelectedAngleRange].sprites.ToArray());
|
||||
else
|
||||
spriteIndexValue = -1;
|
||||
return spriteIndexValue;
|
||||
}
|
||||
|
||||
public int GetAngleRange(int index)
|
||||
{
|
||||
int startPoint = 0;
|
||||
var sc = target as SpriteShapeController;
|
||||
UpdateSegments();
|
||||
return GetAngleRange(sc, index, ref startPoint);
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var updateCollider = false;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_SpriteShapeProp, Contents.spriteShapeProfile);
|
||||
|
||||
var hasEditToolChanged = DoEditButton<SpriteShapeEditorTool>(PathEditorToolContents.icon, Contents.editSplineLabel);
|
||||
if (hasEditToolChanged && !UnityEditor.EditorTools.ToolManager.activeToolType.Equals(typeof(SpriteShapeEditorTool)))
|
||||
SpriteShapeUpdateCache.UpdateCache(targets);
|
||||
|
||||
DoPathInspector<SpriteShapeEditorTool>();
|
||||
var pathTool = SpriteShapeEditorTool.activeSpriteShapeEditorTool;
|
||||
|
||||
if (Selection.gameObjects.Length == 1 && pathTool != null)
|
||||
{
|
||||
|
||||
var sc = target as SpriteShapeController;
|
||||
var path = pathTool.GetPath(sc);
|
||||
if (path != null)
|
||||
{
|
||||
|
||||
var selection = path.selection;
|
||||
|
||||
if (selection.Count > 0)
|
||||
{
|
||||
|
||||
var spline = sc.spline;
|
||||
var spriteIndices = new List<int>();
|
||||
|
||||
List<int> startPoints = new List<int>();
|
||||
foreach (int index in selection.elements)
|
||||
spriteIndices.Add(spline.GetSpriteIndex(index));
|
||||
|
||||
var spriteIndexValue = ResolveSpriteIndex(spriteIndices, selection, ref startPoints);
|
||||
if (spriteIndexValue != -1)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
spriteSelector.ShowGUI(spriteIndexValue);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
foreach (var index in startPoints)
|
||||
{
|
||||
var data = path.GetData(index);
|
||||
data.spriteIndex = spriteSelector.selectedIndex;
|
||||
path.SetData(index, data);
|
||||
}
|
||||
pathTool.SetPath(target);
|
||||
}
|
||||
}
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DoSnappingInspector<SpriteShapeEditorTool>();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.splineLabel);
|
||||
|
||||
EditorGUILayout.IntPopup(m_SplineDetailProp, Contents.splineDetailOptions, m_QualityValues, Contents.splineDetail);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
DoOpenEndedInspector<SpriteShapeEditorTool>(m_IsOpenEndedProp);
|
||||
|
||||
serializedObject.Update();
|
||||
EditorGUILayout.PropertyField(m_AdaptiveUVProp, Contents.adaptiveUVLabel);
|
||||
if (!m_IsOpenEndedProp.boolValue)
|
||||
EditorGUILayout.PropertyField(m_OptimizeGeometryProp, Contents.optimizeGeometryLabel);
|
||||
EditorGUILayout.PropertyField(m_EnableTangentsProp, Contents.enableTangentsLabel);
|
||||
|
||||
if (UnityEditor.EditorTools.ToolManager.activeToolType == typeof(SpriteShapeEditorTool))
|
||||
{
|
||||
// Cache Geometry is only editable for Scene Objects or when in Prefab Isolation Mode.
|
||||
if (Selection.gameObjects.Length == 1 && Selection.transforms.Contains(Selection.gameObjects[0].transform))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUILayout.PropertyField(m_GeometryCachedProp, Contents.cacheGeometryLabel);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (m_GeometryCachedProp.boolValue)
|
||||
{
|
||||
var geometryCache = m_SpriteShapeController.spriteShapeGeometryCache;
|
||||
if (!geometryCache)
|
||||
geometryCache = m_SpriteShapeController.gameObject
|
||||
.AddComponent<SpriteShapeGeometryCache>();
|
||||
geometryCache.hideFlags = HideFlags.HideInInspector;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_SpriteShapeController.spriteShapeGeometryCache)
|
||||
Object.DestroyImmediate(m_SpriteShapeController.spriteShapeGeometryCache);
|
||||
}
|
||||
|
||||
m_SpriteShapeController.RefreshSpriteShape();
|
||||
}
|
||||
}
|
||||
SpriteShapeUpdateCache.s_cacheGeometrySet = true;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var threshold = EditorGUILayout.Slider(Contents.cornerThresholdDetail, m_CornerAngleThresholdProp.floatValue, 0.0f, 90.0f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_CornerAngleThresholdProp.floatValue = threshold;
|
||||
updateCollider = true;
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.fillLabel);
|
||||
EditorGUILayout.PropertyField(m_UTess2DGeometryProp, Contents.uTess2DLabel);
|
||||
EditorGUILayout.PropertyField(m_StretchUVProp, Contents.stretchUVLabel);
|
||||
|
||||
if (ShouldShowStretchOption())
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_StretchTilingProp, Contents.stretchTilingLabel);
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_FillPixelPerUnitProp, Contents.fillPixelPerUnitLabel);
|
||||
EditorGUILayout.PropertyField(m_WorldSpaceUVProp, Contents.worldUVLabel);
|
||||
}
|
||||
|
||||
if (m_SpriteShapeController.gameObject.GetComponent<PolygonCollider2D>() != null || m_SpriteShapeController.gameObject.GetComponent<EdgeCollider2D>() != null)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.colliderLabel);
|
||||
EditorGUILayout.PropertyField(m_ColliderAutoUpdate, Contents.updateColliderLabel);
|
||||
if (m_ColliderAutoUpdate.boolValue)
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_ColliderOffsetProp, Contents.colliderOffset);
|
||||
EditorGUILayout.PropertyField(m_OptimizeColliderProp, Contents.optimizeColliderLabel);
|
||||
if (m_OptimizeColliderProp.boolValue)
|
||||
EditorGUILayout.IntPopup(m_ColliderDetailProp, Contents.splineDetailOptions, m_QualityValues, Contents.colliderDetail);
|
||||
}
|
||||
}
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
updateCollider = true;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (updateCollider || OnCollidersAddedOrRemoved())
|
||||
BakeCollider();
|
||||
}
|
||||
|
||||
void BakeCollider()
|
||||
{
|
||||
if (m_SpriteShapeController.autoUpdateCollider == false)
|
||||
return;
|
||||
|
||||
PolygonCollider2D polygonCollider = m_SpriteShapeController.polygonCollider;
|
||||
if (polygonCollider)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(polygonCollider, Undo.GetCurrentGroupName());
|
||||
EditorUtility.SetDirty(polygonCollider);
|
||||
m_SpriteShapeController.RefreshSpriteShape();
|
||||
}
|
||||
|
||||
EdgeCollider2D edgeCollider = m_SpriteShapeController.edgeCollider;
|
||||
if (edgeCollider)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(edgeCollider, Undo.GetCurrentGroupName());
|
||||
EditorUtility.SetDirty(edgeCollider);
|
||||
m_SpriteShapeController.RefreshSpriteShape();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void ShowMaterials(bool show)
|
||||
{
|
||||
HideFlags hideFlags = HideFlags.HideInInspector;
|
||||
|
||||
if (show)
|
||||
hideFlags = HideFlags.None;
|
||||
|
||||
Material[] materials = m_SpriteShapeController.spriteShapeRenderer.sharedMaterials;
|
||||
|
||||
foreach (Material material in materials)
|
||||
{
|
||||
material.hideFlags = hideFlags;
|
||||
EditorUtility.SetDirty(material);
|
||||
}
|
||||
}
|
||||
|
||||
[DrawGizmo(GizmoType.InSelectionHierarchy)]
|
||||
static void RenderSpline(SpriteShapeController m_SpriteShapeController, GizmoType gizmoType)
|
||||
{
|
||||
if (UnityEditor.EditorTools.ToolManager.activeToolType == typeof(SpriteShapeEditorTool))
|
||||
return;
|
||||
|
||||
var m_Spline = m_SpriteShapeController.spline;
|
||||
var oldMatrix = Handles.matrix;
|
||||
var oldColor = Handles.color;
|
||||
Handles.matrix = m_SpriteShapeController.transform.localToWorldMatrix;
|
||||
Handles.color = Color.grey;
|
||||
var pointCount = m_Spline.GetPointCount();
|
||||
for (var i = 0; i < (m_Spline.isOpenEnded ? pointCount - 1 : pointCount); ++i)
|
||||
{
|
||||
Vector3 p1 = m_Spline.GetPosition(i);
|
||||
Vector3 p2 = m_Spline.GetPosition((i + 1) % pointCount);
|
||||
var t1 = p1 + m_Spline.GetRightTangent(i);
|
||||
var t2 = p2 + m_Spline.GetLeftTangent((i + 1) % pointCount);
|
||||
Vector3[] bezierPoints = Handles.MakeBezierPoints(p1, p2, t1, t2, m_SpriteShapeController.splineDetail);
|
||||
Handles.DrawAAPolyLine(bezierPoints);
|
||||
}
|
||||
Handles.matrix = oldMatrix;
|
||||
Handles.color = oldColor;
|
||||
}
|
||||
}
|
||||
|
||||
[UnityEditor.InitializeOnLoad]
|
||||
internal static class SpriteShapeUpdateCache
|
||||
{
|
||||
|
||||
internal static bool s_cacheGeometrySet = false;
|
||||
static SpriteShapeUpdateCache()
|
||||
{
|
||||
UnityEditor.EditorApplication.playModeStateChanged += change =>
|
||||
{
|
||||
if (change == UnityEditor.PlayModeStateChange.ExitingEditMode)
|
||||
UpdateSpriteShapeCacheInOpenScenes();
|
||||
};
|
||||
}
|
||||
|
||||
static void UpdateSpriteShapeCacheInOpenScenes()
|
||||
{
|
||||
for (int i = 0; s_cacheGeometrySet && (i < SceneManager.sceneCount); ++i)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
var gos = scene.GetRootGameObjects();
|
||||
foreach (var go in gos)
|
||||
{
|
||||
var scs = go.GetComponentsInChildren<SpriteShapeController>();
|
||||
foreach (var sc in scs)
|
||||
if (sc.spriteShapeGeometryCache)
|
||||
sc.spriteShapeGeometryCache.UpdateGeometryCache();
|
||||
}
|
||||
}
|
||||
|
||||
s_cacheGeometrySet = false;
|
||||
}
|
||||
|
||||
internal static void UpdateCache(UnityEngine.Object[] targets)
|
||||
{
|
||||
foreach (var t in targets)
|
||||
{
|
||||
var s = t as SpriteShapeController;
|
||||
if (s)
|
||||
if (s.spriteShapeGeometryCache)
|
||||
s.spriteShapeGeometryCache.UpdateGeometryCache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,672 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor;
|
||||
using UnityEditor.AnimatedValues;
|
||||
using UnityEditorInternal;
|
||||
using UnityEditor.U2D.Common;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.U2D.SpriteShape;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
[CustomEditor(typeof(UnityEngine.U2D.SpriteShape)), CanEditMultipleObjects]
|
||||
public class SpriteShapeEditor : Editor, IAngleRangeCache
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent fillTextureLabel = new GUIContent("Texture", "Fill texture used for Shape Fill.");
|
||||
public static readonly GUIContent fillScaleLabel = new GUIContent("Offset", "Determines Border Offset for Shape.");
|
||||
public static readonly GUIContent useSpriteBorderLabel = new GUIContent("Use Sprite Borders", "Draw Sprite Borders on discontinuities");
|
||||
public static readonly GUIContent cornerTypeLabel = new GUIContent("Corner Type", "Corner type sprite used.");
|
||||
public static readonly GUIContent controlPointsLabel = new GUIContent("Control Points");
|
||||
public static readonly GUIContent fillLabel = new GUIContent("Fill");
|
||||
public static readonly GUIContent cornerLabel = new GUIContent("Corners");
|
||||
public static readonly GUIContent cornerListLabel = new GUIContent("Corner List");
|
||||
public static readonly GUIContent cornerSpriteTypeLabel = new GUIContent("Corner Sprite");
|
||||
public static readonly GUIContent angleRangesLabel = new GUIContent("Angle Ranges");
|
||||
public static readonly GUIContent spritesLabel = new GUIContent("Sprites");
|
||||
public static readonly GUIContent angleRangeLabel = new GUIContent("Angle Range ({0})");
|
||||
public static readonly GUIContent wrapModeErrorLabel = new GUIContent("Fill texture must have wrap modes set to Repeat. Please re-import.");
|
||||
public static readonly GUIContent createRangeButtonLabel = new GUIContent("Create Range");
|
||||
public static readonly Color proBackgroundColor = new Color32(49, 77, 121, 255);
|
||||
public static readonly Color proBackgroundRangeColor = new Color32(25, 25, 25, 128);
|
||||
public static readonly Color proColor1 = new Color32(10, 46, 42, 255);
|
||||
public static readonly Color proColor2 = new Color32(33, 151, 138, 255);
|
||||
public static readonly Color defaultColor1 = new Color32(25, 61, 57, 255);
|
||||
public static readonly Color defaultColor2 = new Color32(47, 166, 153, 255);
|
||||
public static readonly Color defaultBackgroundColor = new Color32(64, 92, 136, 255);
|
||||
}
|
||||
|
||||
private SerializedProperty m_FillTextureProp;
|
||||
private SerializedProperty m_AngleRangesProp;
|
||||
private SerializedProperty m_CornerSpritesProp;
|
||||
private SerializedProperty m_FillOffsetProp;
|
||||
private SerializedProperty m_UseSpriteBordersProp;
|
||||
|
||||
private ReorderableList m_AngleRangeSpriteList = null;
|
||||
private ReorderableList m_EmptySpriteList = null;
|
||||
|
||||
[SerializeField]
|
||||
private float m_PreviewAngle = 0f;
|
||||
[SerializeField]
|
||||
private int m_SelectedIndex;
|
||||
private const int kInvalidMinimum = -1;
|
||||
private Rect m_AngleRangeRect;
|
||||
private AngleRangeController controller;
|
||||
private AngleRange m_CurrentAngleRange;
|
||||
private Dictionary<int, int> m_SpriteSelection = new Dictionary<int, int>();
|
||||
|
||||
|
||||
private Sprite m_PreviewSprite;
|
||||
private Mesh m_PreviewSpriteMesh;
|
||||
private Mesh previewSpriteMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_PreviewSpriteMesh == null)
|
||||
{
|
||||
m_PreviewSpriteMesh = new Mesh();
|
||||
m_PreviewSpriteMesh.MarkDynamic();
|
||||
m_PreviewSpriteMesh.hideFlags = HideFlags.DontSave;
|
||||
}
|
||||
|
||||
return m_PreviewSpriteMesh;
|
||||
}
|
||||
}
|
||||
|
||||
public List<AngleRange> angleRanges
|
||||
{
|
||||
get
|
||||
{
|
||||
if (spriteShape == null)
|
||||
return new List<AngleRange>();
|
||||
Debug.Assert(spriteShape != null);
|
||||
return spriteShape.angleRanges;
|
||||
}
|
||||
}
|
||||
|
||||
public int selectedIndex
|
||||
{
|
||||
get { return m_SelectedIndex; }
|
||||
set { m_SelectedIndex = value; }
|
||||
}
|
||||
|
||||
bool isSelectedIndexValid
|
||||
{
|
||||
get { return (selectedIndex != kInvalidMinimum && selectedIndex < angleRanges.Count); }
|
||||
}
|
||||
|
||||
public float previewAngle
|
||||
{
|
||||
get { return m_PreviewAngle; }
|
||||
set
|
||||
{
|
||||
m_PreviewAngle = value;
|
||||
SessionState.SetFloat("SpriteShape/PreviewAngle/" + target.GetInstanceID(), value);
|
||||
}
|
||||
}
|
||||
|
||||
public UnityEngine.U2D.SpriteShape spriteShape
|
||||
{
|
||||
get
|
||||
{
|
||||
if (target == null)
|
||||
return null;
|
||||
return target as UnityEngine.U2D.SpriteShape;
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterUndo(string name)
|
||||
{
|
||||
Undo.RegisterCompleteObjectUndo(spriteShape, name);
|
||||
Undo.RegisterCompleteObjectUndo(this, name);
|
||||
EditorUtility.SetDirty(spriteShape);
|
||||
}
|
||||
|
||||
void OnReset(UnityEngine.U2D.SpriteShape obj)
|
||||
{
|
||||
InitCache();
|
||||
}
|
||||
|
||||
void InitCache()
|
||||
{
|
||||
selectedIndex = SpriteShapeEditorUtility.GetRangeIndexFromAngle(angleRanges, m_PreviewAngle);
|
||||
SetupAngleRangeController();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
if (targets == null || targets.Length == 0)
|
||||
return;
|
||||
|
||||
m_PreviewAngle = SessionState.GetFloat("SpriteShape/PreviewAngle/" + target.GetInstanceID(), m_PreviewAngle);
|
||||
|
||||
m_FillTextureProp = this.serializedObject.FindProperty("m_FillTexture");
|
||||
m_UseSpriteBordersProp = serializedObject.FindProperty("m_UseSpriteBorders");
|
||||
m_AngleRangesProp = this.serializedObject.FindProperty("m_Angles");
|
||||
m_CornerSpritesProp = this.serializedObject.FindProperty("m_CornerSprites");
|
||||
m_FillOffsetProp = this.serializedObject.FindProperty("m_FillOffset");
|
||||
|
||||
InitCache();
|
||||
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
UnityEngine.U2D.SpriteShape.onReset += OnReset;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
UnityEngine.U2D.SpriteShape.onReset -= OnReset;
|
||||
}
|
||||
|
||||
public override bool RequiresConstantRepaint()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetupAngleRangeController()
|
||||
{
|
||||
var radius = 125f;
|
||||
var angleOffset = -90f;
|
||||
var color1 = Contents.defaultColor1;
|
||||
var color2 = Contents.defaultColor2;
|
||||
|
||||
if (!EditorGUIUtility.isProSkin)
|
||||
{
|
||||
color1 = Contents.proColor1;
|
||||
color2 = Contents.proColor2;
|
||||
}
|
||||
|
||||
controller = new AngleRangeController();
|
||||
controller.view = new AngleRangeView();
|
||||
controller.cache = this;
|
||||
controller.radius = radius;
|
||||
controller.angleOffset = angleOffset;
|
||||
controller.gradientMin = color1;
|
||||
controller.gradientMid = color2;
|
||||
controller.gradientMax = color1;
|
||||
controller.snap = true;
|
||||
controller.selectionChanged += OnSelectionChange;
|
||||
|
||||
OnSelectionChange();
|
||||
}
|
||||
|
||||
private void OnSelectionChange()
|
||||
{
|
||||
CreateReorderableSpriteList();
|
||||
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
m_CurrentAngleRange = controller.selectedAngleRange;
|
||||
};
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (m_PreviewSpriteMesh)
|
||||
UnityEngine.Object.DestroyImmediate(m_PreviewSpriteMesh);
|
||||
|
||||
Undo.undoRedoPerformed -= UndoRedoPerformed;
|
||||
}
|
||||
|
||||
private void UndoRedoPerformed()
|
||||
{
|
||||
InitCache();
|
||||
OnSelectionChange();
|
||||
}
|
||||
|
||||
private void OnSelelectSpriteCallback(ReorderableList list)
|
||||
{
|
||||
if (selectedIndex >= 0)
|
||||
{
|
||||
SetPreviewSpriteIndex(selectedIndex, list.index);
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnCanAddCallback(ReorderableList list)
|
||||
{
|
||||
return (list.count < 64);
|
||||
}
|
||||
|
||||
private void OnRemoveSprite(ReorderableList list)
|
||||
{
|
||||
var count = list.count;
|
||||
var index = list.index;
|
||||
|
||||
ReorderableList.defaultBehaviours.DoRemoveButton(list);
|
||||
|
||||
if (list.count < count && list.count > 0)
|
||||
{
|
||||
list.index = Mathf.Clamp(index, 0, list.count - 1);
|
||||
OnSelelectSpriteCallback(list);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSpriteListHeader(Rect rect)
|
||||
{
|
||||
EditorGUI.LabelField(rect, Contents.spritesLabel);
|
||||
HandleAngleSpriteListGUI(rect);
|
||||
}
|
||||
|
||||
private void DrawSpriteListElement(Rect rect, int index, bool selected, bool focused)
|
||||
{
|
||||
rect.y += 2f;
|
||||
rect.height = EditorGUIUtility.singleLineHeight;
|
||||
var sprite = m_AngleRangesProp.GetArrayElementAtIndex(selectedIndex).FindPropertyRelative("m_Sprites").GetArrayElementAtIndex(index);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.PropertyField(rect, sprite, GUIContent.none);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_AngleRangeSpriteList.index = index;
|
||||
OnSelelectSpriteCallback(m_AngleRangeSpriteList);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawHeader(GUIContent content)
|
||||
{
|
||||
EditorGUILayout.LabelField(content, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
private void SetPreviewSpriteIndex(int rangeIndex, int index)
|
||||
{
|
||||
m_SpriteSelection[rangeIndex] = index;
|
||||
}
|
||||
|
||||
private int GetPreviewSpriteIndex(int rangeIndex)
|
||||
{
|
||||
int index;
|
||||
m_SpriteSelection.TryGetValue(rangeIndex, out index);
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.controlPointsLabel);
|
||||
EditorGUILayout.PropertyField(m_UseSpriteBordersProp, Contents.useSpriteBorderLabel);
|
||||
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.fillLabel);
|
||||
EditorGUILayout.PropertyField(m_FillTextureProp, Contents.fillTextureLabel);
|
||||
EditorGUILayout.Slider(m_FillOffsetProp, -0.5f, 0.5f, Contents.fillScaleLabel);
|
||||
|
||||
|
||||
if (m_FillTextureProp.objectReferenceValue != null)
|
||||
{
|
||||
var fillTex = m_FillTextureProp.objectReferenceValue as Texture2D;
|
||||
if (fillTex.wrapModeU != TextureWrapMode.Repeat || fillTex.wrapModeV != TextureWrapMode.Repeat)
|
||||
EditorGUILayout.HelpBox(Contents.wrapModeErrorLabel.text, MessageType.Warning);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.angleRangesLabel);
|
||||
DoRangesGUI();
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
DoRangeInspector();
|
||||
DoCreateRangeButton();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
DrawHeader(Contents.cornerLabel);
|
||||
|
||||
HashSet<Sprite> tightMeshSprites = new HashSet<Sprite>();
|
||||
|
||||
for (int i = 0; i < angleRanges.Count; ++i)
|
||||
{
|
||||
AngleRange angleRange = angleRanges[i];
|
||||
foreach (Sprite sprite in angleRange.sprites)
|
||||
{
|
||||
if (sprite != null)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(sprite);
|
||||
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
||||
if (importer != null)
|
||||
{
|
||||
TextureImporterSettings textureSettings = new TextureImporterSettings();
|
||||
importer.ReadTextureSettings(textureSettings);
|
||||
if (textureSettings.spriteMeshType == SpriteMeshType.Tight)
|
||||
tightMeshSprites.Add(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUIUtility.labelWidth = EditorGUIUtility.labelWidth + 20f;
|
||||
|
||||
for (int i = 0; i < m_CornerSpritesProp.arraySize; ++i)
|
||||
{
|
||||
var m_CornerProp = m_CornerSpritesProp.GetArrayElementAtIndex(i);
|
||||
var m_CornerType = m_CornerProp.FindPropertyRelative("m_CornerType");
|
||||
var m_CornerSprite = m_CornerProp.FindPropertyRelative("m_Sprites").GetArrayElementAtIndex(0);
|
||||
|
||||
EditorGUILayout.PropertyField(m_CornerSprite, new GUIContent(m_CornerType.enumDisplayNames[m_CornerType.intValue]));
|
||||
|
||||
var sprite = m_CornerSprite.objectReferenceValue as Sprite;
|
||||
if (sprite != null)
|
||||
{
|
||||
string assetPath = AssetDatabase.GetAssetPath(sprite);
|
||||
TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
|
||||
if (importer != null)
|
||||
{
|
||||
TextureImporterSettings textureSettings = new TextureImporterSettings();
|
||||
importer.ReadTextureSettings(textureSettings);
|
||||
if (textureSettings.spriteMeshType == SpriteMeshType.Tight)
|
||||
tightMeshSprites.Add(sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
EditorGUIUtility.labelWidth = 0;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (tightMeshSprites.Count > 0)
|
||||
{
|
||||
int i = 0;
|
||||
string tightSpriteWarning = "The following sprites ( ";
|
||||
foreach (var sprite in tightMeshSprites)
|
||||
{
|
||||
string appendString = (i < tightMeshSprites.Count - 1) ? ", " : " ) ";
|
||||
tightSpriteWarning += (sprite.name + appendString);
|
||||
++i;
|
||||
}
|
||||
tightSpriteWarning += "are imported as Sprites using Tight mesh. This can lead to Rendering Artifacts. Please use Full Rect.";
|
||||
EditorGUILayout.HelpBox(tightSpriteWarning, MessageType.Warning);
|
||||
}
|
||||
|
||||
controller.view.DoCreateRangeTooltip();
|
||||
}
|
||||
|
||||
private void DoRangeInspector()
|
||||
{
|
||||
var start = 0f;
|
||||
var end = 0f;
|
||||
var order = 0;
|
||||
|
||||
if (m_CurrentAngleRange != null)
|
||||
{
|
||||
start = m_CurrentAngleRange.start;
|
||||
end = m_CurrentAngleRange.end;
|
||||
order = m_CurrentAngleRange.order;
|
||||
}
|
||||
|
||||
var arSize = m_AngleRangesProp.arraySize;
|
||||
|
||||
using (new EditorGUI.DisabledGroupScope(m_CurrentAngleRange == null))
|
||||
{
|
||||
DrawHeader(new GUIContent(string.Format(Contents.angleRangeLabel.text, (end - start))));
|
||||
|
||||
EditorGUIUtility.labelWidth = 0f;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
RangeField(ref start, ref end, ref order);
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && m_CurrentAngleRange != null)
|
||||
{
|
||||
RegisterUndo("Set Range");
|
||||
|
||||
m_CurrentAngleRange.order = order;
|
||||
controller.SetRange(m_CurrentAngleRange, start, end);
|
||||
|
||||
if (start >= end)
|
||||
controller.RemoveInvalidRanges();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
if (m_AngleRangeSpriteList != null && arSize > 0)
|
||||
m_AngleRangeSpriteList.DoLayoutList();
|
||||
else
|
||||
m_EmptySpriteList.DoLayoutList();
|
||||
}
|
||||
}
|
||||
|
||||
private void DoCreateRangeButton()
|
||||
{
|
||||
if (selectedIndex != kInvalidMinimum && angleRanges.Count != 0)
|
||||
return;
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button(Contents.createRangeButtonLabel, GUILayout.MaxWidth(100f)))
|
||||
{
|
||||
RegisterUndo("Create Range");
|
||||
controller.CreateRange();
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void RangeField(ref float start, ref float end, ref int order)
|
||||
{
|
||||
var values = new int[] { Mathf.RoundToInt(-start), Mathf.RoundToInt(-end), order };
|
||||
var labels = new GUIContent[] { new GUIContent("Start"), new GUIContent("End"), new GUIContent("Order") };
|
||||
|
||||
var position = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
|
||||
EditorGUI.BeginChangeCheck();
|
||||
SpriteShapeEditorGUI.MultiDelayedIntField(position, labels, values, 40f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
start = -1f * values[0];
|
||||
end = -1f * values[1];
|
||||
order = values[2];
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAngleSpriteListGUI(Rect rect)
|
||||
{
|
||||
if (m_CurrentAngleRange == null || !isSelectedIndexValid)
|
||||
return;
|
||||
|
||||
var currentEvent = Event.current;
|
||||
var usedEvent = false;
|
||||
var sprites = m_AngleRangesProp.GetArrayElementAtIndex(selectedIndex).FindPropertyRelative("m_Sprites");
|
||||
switch (currentEvent.type)
|
||||
{
|
||||
case EventType.DragExited:
|
||||
if (GUI.enabled)
|
||||
HandleUtility.Repaint();
|
||||
break;
|
||||
|
||||
case EventType.DragUpdated:
|
||||
case EventType.DragPerform:
|
||||
if (rect.Contains(currentEvent.mousePosition) && GUI.enabled)
|
||||
{
|
||||
// Check each single object, so we can add multiple objects in a single drag.
|
||||
var didAcceptDrag = false;
|
||||
var references = DragAndDrop.objectReferences;
|
||||
foreach (var obj in references)
|
||||
{
|
||||
if (obj is Sprite)
|
||||
{
|
||||
Sprite spr = obj as Sprite;
|
||||
if (spr.texture != null)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
if (currentEvent.type == EventType.DragPerform && sprites.arraySize < 64)
|
||||
{
|
||||
sprites.InsertArrayElementAtIndex(sprites.arraySize);
|
||||
var spriteProp = sprites.GetArrayElementAtIndex(sprites.arraySize - 1);
|
||||
spriteProp.objectReferenceValue = obj;
|
||||
didAcceptDrag = true;
|
||||
DragAndDrop.activeControlID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
if (didAcceptDrag)
|
||||
{
|
||||
GUI.changed = true;
|
||||
DragAndDrop.AcceptDrag();
|
||||
usedEvent = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (usedEvent)
|
||||
currentEvent.Use();
|
||||
}
|
||||
|
||||
private void DoRangesGUI()
|
||||
{
|
||||
var radius = controller.radius;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
|
||||
var rect = EditorGUILayout.GetControlRect(false, radius * 2f);
|
||||
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
m_AngleRangeRect = rect;
|
||||
|
||||
{ //Draw background
|
||||
var backgroundColor = Contents.proBackgroundColor;
|
||||
var backgroundRangeColor = Contents.proBackgroundRangeColor;
|
||||
|
||||
if (!EditorGUIUtility.isProSkin)
|
||||
{
|
||||
backgroundColor = Contents.defaultBackgroundColor;
|
||||
backgroundRangeColor.a = 0.1f;
|
||||
}
|
||||
var c = Handles.color;
|
||||
Handles.color = backgroundRangeColor;
|
||||
SpriteShapeHandleUtility.DrawSolidArc(rect.center, Vector3.forward, Vector3.right, 360f, radius, AngleRangeGUI.kRangeWidth);
|
||||
Handles.color = backgroundColor;
|
||||
Handles.DrawSolidDisc(rect.center, Vector3.forward, radius - AngleRangeGUI.kRangeWidth + 1f);
|
||||
Handles.color = c;
|
||||
}
|
||||
|
||||
if (targets.Length == 1)
|
||||
{
|
||||
{ //Draw fill texture and sprite preview
|
||||
SpriteShapeHandleUtility.DrawTextureArc(
|
||||
m_FillTextureProp.objectReferenceValue as Texture, 100.0f,
|
||||
rect.center, Vector3.forward, Quaternion.AngleAxis(m_PreviewAngle, Vector3.forward) * Vector3.right, 180f,
|
||||
radius - AngleRangeGUI.kRangeWidth);
|
||||
|
||||
var rectSize = Vector2.one * (radius - AngleRangeGUI.kRangeWidth) * 2f;
|
||||
rectSize.y *= 0.33f;
|
||||
var spriteRect = new Rect(rect.center - rectSize * 0.5f, rectSize);
|
||||
DrawSpritePreview(spriteRect);
|
||||
HandleSpritePreviewCycle(spriteRect);
|
||||
}
|
||||
|
||||
controller.rect = m_AngleRangeRect;
|
||||
controller.OnGUI();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
private void CreateReorderableSpriteList()
|
||||
{
|
||||
if (m_EmptySpriteList == null)
|
||||
{
|
||||
m_EmptySpriteList = new ReorderableList(new List<Sprite>(), typeof(Sprite), false, true, false, false)
|
||||
{
|
||||
drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, Contents.spritesLabel); }
|
||||
};
|
||||
}
|
||||
|
||||
m_AngleRangeSpriteList = null;
|
||||
|
||||
serializedObject.UpdateIfRequiredOrScript();
|
||||
|
||||
Debug.Assert(angleRanges.Count == m_AngleRangesProp.arraySize);
|
||||
Debug.Assert(selectedIndex < angleRanges.Count || selectedIndex == 0);
|
||||
|
||||
if (targets.Length == 1 && isSelectedIndexValid)
|
||||
{
|
||||
var spritesProp = m_AngleRangesProp.GetArrayElementAtIndex(selectedIndex).FindPropertyRelative("m_Sprites");
|
||||
m_AngleRangeSpriteList = new ReorderableList(spritesProp.serializedObject, spritesProp)
|
||||
{
|
||||
drawElementCallback = DrawSpriteListElement,
|
||||
drawHeaderCallback = DrawSpriteListHeader,
|
||||
onSelectCallback = OnSelelectSpriteCallback,
|
||||
onRemoveCallback = OnRemoveSprite,
|
||||
onCanAddCallback = OnCanAddCallback,
|
||||
elementHeight = EditorGUIUtility.singleLineHeight + 6f
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSpritePreview(Rect rect)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
if (!isSelectedIndexValid)
|
||||
return;
|
||||
|
||||
var sprites = angleRanges[selectedIndex].sprites;
|
||||
|
||||
if (sprites.Count == 0)
|
||||
return;
|
||||
|
||||
var selectedSpriteIndex = GetPreviewSpriteIndex(selectedIndex);
|
||||
|
||||
if (selectedSpriteIndex == kInvalidMinimum || selectedSpriteIndex >= sprites.Count)
|
||||
return;
|
||||
|
||||
var sprite = sprites[selectedSpriteIndex];
|
||||
|
||||
if (sprite == null)
|
||||
return;
|
||||
|
||||
if (m_PreviewSprite != sprite)
|
||||
{
|
||||
m_PreviewSprite = sprite;
|
||||
EditorSpriteGUIUtility.DrawSpriteInRectPrepare(rect, sprite, EditorSpriteGUIUtility.FitMode.Tiled, true, true, previewSpriteMesh);
|
||||
}
|
||||
|
||||
var material = EditorSpriteGUIUtility.spriteMaterial;
|
||||
material.mainTexture = EditorSpriteGUIUtility.GetOriginalSpriteTexture(sprite);
|
||||
|
||||
EditorSpriteGUIUtility.DrawMesh(previewSpriteMesh, material, rect.center, Quaternion.AngleAxis(m_PreviewAngle, Vector3.forward), new Vector3(1f, -1f, 1f));
|
||||
}
|
||||
|
||||
private void HandleSpritePreviewCycle(Rect rect)
|
||||
{
|
||||
if (!isSelectedIndexValid)
|
||||
return;
|
||||
|
||||
Debug.Assert(m_AngleRangeSpriteList != null);
|
||||
|
||||
var spriteIndex = GetPreviewSpriteIndex(selectedIndex);
|
||||
var sprites = angleRanges[selectedIndex].sprites;
|
||||
|
||||
var ev = Event.current;
|
||||
if (ev.type == EventType.MouseDown && ev.button == 0 && HandleUtility.nearestControl == 0 &&
|
||||
ContainsPosition(rect, ev.mousePosition, m_PreviewAngle) && spriteIndex != kInvalidMinimum && sprites.Count > 0)
|
||||
{
|
||||
spriteIndex = Mathf.RoundToInt(Mathf.Repeat(spriteIndex + 1f, sprites.Count));
|
||||
SetPreviewSpriteIndex(selectedIndex, spriteIndex);
|
||||
|
||||
m_AngleRangeSpriteList.GrabKeyboardFocus();
|
||||
m_AngleRangeSpriteList.index = spriteIndex;
|
||||
|
||||
ev.Use();
|
||||
}
|
||||
}
|
||||
|
||||
private bool ContainsPosition(Rect rect, Vector2 position, float angle)
|
||||
{
|
||||
Vector2 delta = position - rect.center;
|
||||
position = (Vector2)(Quaternion.AngleAxis(-angle, Vector3.forward) * (Vector3)delta) + rect.center;
|
||||
return rect.Contains(position);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
internal class SpriteShapeEditorAnalytics : ScriptableSingleton<SpriteShapeEditorAnalytics>
|
||||
{
|
||||
SpriteShapeAnalytics m_Analytics = null;
|
||||
|
||||
internal SpriteShapeAnalyticsEvents eventBus
|
||||
{
|
||||
get { return analytics.eventBus; }
|
||||
}
|
||||
|
||||
private SpriteShapeAnalytics analytics
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Analytics == null)
|
||||
m_Analytics = new SpriteShapeAnalytics(new SpriteShapeUnityAnalyticsStorage());
|
||||
|
||||
return m_Analytics;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Sprites;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public class SpriteShapeEditorGUI
|
||||
{
|
||||
private const float kSpacingSubLabel = 2.0f;
|
||||
private const float kMiniLabelW = 13;
|
||||
private const int kVerticalSpacingMultiField = 0;
|
||||
private const float kIndentPerLevel = 15;
|
||||
public static int s_FoldoutHash = "Foldout".GetHashCode();
|
||||
|
||||
public static void MultiDelayedIntField(Rect position, GUIContent[] subLabels, int[] values, float labelWidth)
|
||||
{
|
||||
int eCount = values.Length;
|
||||
float w = (position.width - (eCount - 1) * kSpacingSubLabel) / eCount;
|
||||
Rect nr = new Rect(position);
|
||||
nr.width = w;
|
||||
float t = EditorGUIUtility.labelWidth;
|
||||
int l = EditorGUI.indentLevel;
|
||||
EditorGUIUtility.labelWidth = labelWidth;
|
||||
EditorGUI.indentLevel = 0;
|
||||
for (int i = 0; i < values.Length; i++)
|
||||
{
|
||||
values[i] = EditorGUI.DelayedIntField(nr, subLabels[i], values[i]);
|
||||
nr.x += w + kSpacingSubLabel;
|
||||
}
|
||||
EditorGUIUtility.labelWidth = t;
|
||||
EditorGUI.indentLevel = l;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,324 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using UnityEditor;
|
||||
using UnityEditor.U2D.Path;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEditor.U2D.Common;
|
||||
|
||||
namespace UnityEditor.U2D.SpriteShapeInternal
|
||||
{
|
||||
internal class CustomDrawer : IDrawer
|
||||
{
|
||||
private IDrawer m_Drawer = new Drawer();
|
||||
private SpriteShapeController m_SpriteShapeController;
|
||||
|
||||
public CustomDrawer(SpriteShapeController spriteShapeController)
|
||||
{
|
||||
m_SpriteShapeController = spriteShapeController;
|
||||
}
|
||||
|
||||
private int GetSubDivisionCount()
|
||||
{
|
||||
return m_SpriteShapeController.splineDetail;
|
||||
}
|
||||
|
||||
void IDrawer.DrawBezier(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float width, Color color)
|
||||
{
|
||||
Handles.color = color;
|
||||
Handles.DrawAAPolyLine(null, width, Handles.MakeBezierPoints(p1, p4, p2, p3, GetSubDivisionCount()));
|
||||
}
|
||||
|
||||
void IDrawer.DrawCreatePointPreview(Vector3 position)
|
||||
{
|
||||
m_Drawer.DrawCreatePointPreview(position);
|
||||
}
|
||||
|
||||
void IDrawer.DrawLine(Vector3 p1, Vector3 p2, float width, Color color)
|
||||
{
|
||||
m_Drawer.DrawLine(p1, p2, width, color);
|
||||
}
|
||||
|
||||
void IDrawer.DrawPoint(Vector3 position)
|
||||
{
|
||||
m_Drawer.DrawPoint(position);
|
||||
}
|
||||
|
||||
void IDrawer.DrawPointHovered(Vector3 position)
|
||||
{
|
||||
m_Drawer.DrawPointHovered(position);
|
||||
}
|
||||
|
||||
void IDrawer.DrawPointSelected(Vector3 position)
|
||||
{
|
||||
m_Drawer.DrawPointSelected(position);
|
||||
}
|
||||
|
||||
void IDrawer.DrawTangent(Vector3 position, Vector3 tangent)
|
||||
{
|
||||
m_Drawer.DrawTangent(position, tangent);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class SpriteShapeData
|
||||
{
|
||||
public float height = 1f;
|
||||
public int spriteIndex;
|
||||
public int corner = 1;
|
||||
public int cornerMode = 1;
|
||||
}
|
||||
|
||||
internal class ScriptableSpriteShapeData : ScriptableData<SpriteShapeData> { }
|
||||
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(ScriptableSpriteShapeData))]
|
||||
internal class SpriteShapeDataInspector : Editor
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly GUIContent heightLabel = new GUIContent("Height", "Height override for control point.");
|
||||
public static readonly GUIContent spriteIndexLabel = new GUIContent("Sprite Variant", "Index of the sprite variant at this control point");
|
||||
public static readonly GUIContent invalidSpriteLabel = new GUIContent("No sprite defined");
|
||||
public static readonly GUIContent cornerLabel = new GUIContent("Corner", "Set if Corner is automatic or disabled.");
|
||||
|
||||
public static readonly int[] cornerValues = { 0, 1, 2 };
|
||||
public static readonly GUIContent[] cornerOptions = { new GUIContent("Disabled"), new GUIContent("Automatic"), new GUIContent("Stretched"), };
|
||||
}
|
||||
|
||||
private SerializedProperty m_Data;
|
||||
private SerializedProperty m_SpriteIndexProperty;
|
||||
private SerializedProperty m_HeightProperty;
|
||||
private SerializedProperty m_CornerProperty;
|
||||
private SerializedProperty m_CornerModeProperty;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_Data = serializedObject.FindProperty("m_Data");
|
||||
m_SpriteIndexProperty = m_Data.FindPropertyRelative("spriteIndex");
|
||||
m_HeightProperty = m_Data.FindPropertyRelative("height");
|
||||
m_CornerProperty = m_Data.FindPropertyRelative("corner");
|
||||
m_CornerModeProperty = m_Data.FindPropertyRelative("cornerMode");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var heightValue = EditorGUILayout.Slider(Contents.heightLabel, m_HeightProperty.floatValue, 0.1f, 4.0f);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
m_HeightProperty.floatValue = heightValue;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
var cornerValue = EditorGUILayout.IntPopup(Contents.cornerLabel, m_CornerModeProperty.intValue, Contents.cornerOptions, Contents.cornerValues);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
m_CornerModeProperty.intValue = cornerValue;
|
||||
m_CornerProperty.intValue = cornerValue != 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
|
||||
internal class CustomPath : GenericScriptablePath<SpriteShapeData> { }
|
||||
|
||||
[CanEditMultipleObjects]
|
||||
[CustomEditor(typeof(CustomPath))]
|
||||
internal class CustomPathInspector : GenericScriptablePathInspector<ScriptableSpriteShapeData, SpriteShapeData> { }
|
||||
|
||||
[EditorTool("Edit SpriteShape", typeof(SpriteShapeController))]
|
||||
internal class SpriteShapeEditorTool : PathEditorTool<CustomPath>
|
||||
{
|
||||
private static InternalEditorBridge.ShortcutContext m_ShortcutContext;
|
||||
|
||||
public static SpriteShapeEditorTool activeSpriteShapeEditorTool
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ShortcutContext != null)
|
||||
return m_ShortcutContext.context as SpriteShapeEditorTool;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool GetLinearTangentIsZero(UnityEngine.Object target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override IDrawer GetCustomDrawer(UnityEngine.Object target)
|
||||
{
|
||||
return new CustomDrawer(target as SpriteShapeController);
|
||||
}
|
||||
|
||||
protected override IShape GetShape(UnityEngine.Object target)
|
||||
{
|
||||
return Polygon.empty;
|
||||
}
|
||||
|
||||
protected override void Initialize(CustomPath shapeEditor, SerializedObject serializedObject)
|
||||
{
|
||||
var controller = serializedObject.targetObject as SpriteShapeController;
|
||||
var spline = controller.spline;
|
||||
shapeEditor.shapeType = ShapeType.Spline;
|
||||
shapeEditor.isOpenEnded = spline.isOpenEnded;
|
||||
|
||||
for (var i = 0; i < spline.GetPointCount(); ++i)
|
||||
{
|
||||
var position = spline.GetPosition(i);
|
||||
|
||||
shapeEditor.AddPoint(new ControlPoint()
|
||||
{
|
||||
position = spline.GetPosition(i),
|
||||
localLeftTangent = spline.GetLeftTangent(i),
|
||||
localRightTangent = spline.GetRightTangent(i),
|
||||
tangentMode = (TangentMode)spline.GetTangentMode(i)
|
||||
});
|
||||
|
||||
shapeEditor.SetData(i, new SpriteShapeData()
|
||||
{
|
||||
spriteIndex = spline.GetSpriteIndex(i),
|
||||
height = spline.GetHeight(i),
|
||||
cornerMode = (int)spline.GetCornerMode(i),
|
||||
corner = spline.GetCorner(i) ? 1 : 0,
|
||||
});
|
||||
}
|
||||
|
||||
shapeEditor.UpdateTangentsFromMode();
|
||||
}
|
||||
|
||||
protected override void SetShape(CustomPath shapeEditor, SerializedObject serializedObject)
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
var controller = serializedObject.targetObject as SpriteShapeController;
|
||||
var splineProp = serializedObject.FindProperty("m_Spline");
|
||||
var controlPointsProp = splineProp.FindPropertyRelative("m_ControlPoints");
|
||||
|
||||
splineProp.FindPropertyRelative("m_IsOpenEnded").boolValue = shapeEditor.isOpenEnded;
|
||||
|
||||
controlPointsProp.arraySize = shapeEditor.pointCount;
|
||||
|
||||
for (var i = 0; i < shapeEditor.pointCount; ++i)
|
||||
{
|
||||
var elementProp = controlPointsProp.GetArrayElementAtIndex(i);
|
||||
var point = shapeEditor.GetPoint(i);
|
||||
var data = shapeEditor.GetData(i);
|
||||
|
||||
elementProp.FindPropertyRelative("position").vector3Value = point.position;
|
||||
elementProp.FindPropertyRelative("leftTangent").vector3Value = point.localLeftTangent;
|
||||
elementProp.FindPropertyRelative("rightTangent").vector3Value = point.localRightTangent;
|
||||
elementProp.FindPropertyRelative("mode").enumValueIndex = (int)point.tangentMode;
|
||||
elementProp.FindPropertyRelative("height").floatValue = data.height;
|
||||
elementProp.FindPropertyRelative("spriteIndex").intValue = data.spriteIndex;
|
||||
elementProp.FindPropertyRelative("corner").intValue = data.corner;
|
||||
elementProp.FindPropertyRelative("m_CornerMode").intValue = data.cornerMode;
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
protected override void OnActivate()
|
||||
{
|
||||
RegisterShortcuts();
|
||||
}
|
||||
|
||||
protected override void OnDeactivate()
|
||||
{
|
||||
UnregisterShortcuts();
|
||||
}
|
||||
|
||||
private void CycleSpriteIndex()
|
||||
{
|
||||
foreach(var target in targets)
|
||||
{
|
||||
var spriteShapeController = target as SpriteShapeController;
|
||||
var shapeEditor = GetPath(target);
|
||||
Editor cachedEditor = null;
|
||||
Editor.CreateCachedEditor(spriteShapeController, typeof(SpriteShapeControllerEditor), ref cachedEditor);
|
||||
if (spriteShapeController == null || spriteShapeController.spriteShape == null || shapeEditor.selection.Count == 0 || cachedEditor == null || shapeEditor.selection.Count > 1)
|
||||
continue;
|
||||
|
||||
var spriteShape = spriteShapeController.spriteShape;
|
||||
var scEditor = cachedEditor as SpriteShapeControllerEditor;
|
||||
var selected = shapeEditor.selection.elements[0];
|
||||
|
||||
if (shapeEditor.selection.Contains(selected))
|
||||
{
|
||||
shapeEditor.undoObject.RegisterUndo("Cycle Variant");
|
||||
var data = shapeEditor.GetData(selected);
|
||||
|
||||
var angleRangeIndex = scEditor.GetAngleRange(selected);
|
||||
if (angleRangeIndex != -1)
|
||||
{
|
||||
var angleRange = spriteShape.angleRanges[angleRangeIndex];
|
||||
if (angleRange.sprites.Count > 0)
|
||||
data.spriteIndex = (data.spriteIndex + 1) % angleRange.sprites.Count;
|
||||
shapeEditor.SetData(selected, data);
|
||||
}
|
||||
|
||||
SetPath(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float SlopeAngle(Vector2 start, Vector2 end)
|
||||
{
|
||||
var dir = start - end;
|
||||
dir.Normalize();
|
||||
var dvup = new Vector2(0, 1f);
|
||||
var dvrt = new Vector2(1f, 0);
|
||||
|
||||
var dr = Vector2.Dot(dir, dvrt);
|
||||
var du = Vector2.Dot(dir, dvup);
|
||||
var cu = Mathf.Acos(du);
|
||||
var sn = dr >= 0 ? 1.0f : -1.0f;
|
||||
var an = cu * Mathf.Rad2Deg * sn;
|
||||
|
||||
// Adjust angles when direction is parallel to Up Axis.
|
||||
an = (du != 1f) ? an : 0;
|
||||
an = (du != -1f) ? an : -180f;
|
||||
return an;
|
||||
}
|
||||
|
||||
private void RegisterShortcuts()
|
||||
{
|
||||
m_ShortcutContext = new InternalEditorBridge.ShortcutContext()
|
||||
{
|
||||
isActive = () => GUIUtility.hotControl == 0,
|
||||
context = this
|
||||
};
|
||||
|
||||
InternalEditorBridge.RegisterShortcutContext(m_ShortcutContext);
|
||||
}
|
||||
|
||||
private void UnregisterShortcuts()
|
||||
{
|
||||
InternalEditorBridge.UnregisterShortcutContext(m_ShortcutContext);
|
||||
}
|
||||
|
||||
[Shortcut("SpriteShape Editing/Cycle Tangent Mode", typeof(InternalEditorBridge.ShortcutContext), KeyCode.M)]
|
||||
private static void ShortcutCycleTangentMode(ShortcutArguments args)
|
||||
{
|
||||
if (args.context == m_ShortcutContext)
|
||||
(m_ShortcutContext.context as SpriteShapeEditorTool).CycleTangentMode();
|
||||
}
|
||||
|
||||
[Shortcut("SpriteShape Editing/Cycle Variant", typeof(InternalEditorBridge.ShortcutContext), KeyCode.N)]
|
||||
private static void ShortcutCycleSpriteIndex(ShortcutArguments args)
|
||||
{
|
||||
if (args.context == m_ShortcutContext)
|
||||
(m_ShortcutContext.context as SpriteShapeEditorTool).CycleSpriteIndex();
|
||||
}
|
||||
|
||||
[Shortcut("SpriteShape Editing/Mirror Tangent", typeof(InternalEditorBridge.ShortcutContext), KeyCode.B)]
|
||||
private static void ShortcutCycleMirrorTangent(ShortcutArguments args)
|
||||
{
|
||||
if (args.context == m_ShortcutContext)
|
||||
(m_ShortcutContext.context as SpriteShapeEditorTool).MirrorTangent();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
|
||||
namespace UnityEditor.U2D.SpriteShape
|
||||
{
|
||||
public class SpriteShapeEditorUtility
|
||||
{
|
||||
private static class Contents
|
||||
{
|
||||
public static readonly string createSpriteShapeString = "Create Sprite Shape";
|
||||
public static readonly string newSpriteShapeString = "SpriteShape";
|
||||
}
|
||||
|
||||
public const float kMaxSideSize = 2.0f;
|
||||
|
||||
public static UnityEngine.U2D.SpriteShape CreateSpriteShapeAsset()
|
||||
{
|
||||
UnityEngine.U2D.SpriteShape spriteShape = ScriptableObject.CreateInstance<UnityEngine.U2D.SpriteShape>();
|
||||
ProjectWindowUtil.CreateAsset(spriteShape, "New SpriteShapeProfile.asset");
|
||||
Selection.activeObject = spriteShape;
|
||||
|
||||
SpriteShapeEditorAnalytics.instance.eventBus.spriteShapeEvent.Invoke(spriteShape);
|
||||
return spriteShape;
|
||||
}
|
||||
|
||||
public static SpriteShapeController CreateSpriteShapeController(UnityEngine.U2D.SpriteShape shape)
|
||||
{
|
||||
var objName = "New SpriteShapeController";
|
||||
GameObject gameObject = new GameObject(objName, typeof(SpriteShapeController));
|
||||
SpriteShapeController spriteShapeController = gameObject.GetComponent<SpriteShapeController>();
|
||||
spriteShapeController.spline.Clear();
|
||||
if (shape != null)
|
||||
objName = shape.name;
|
||||
gameObject.name = GameObjectUtility.GetUniqueNameForSibling(gameObject.transform.parent, objName);
|
||||
SpriteShapeEditorAnalytics.instance.eventBus.spriteShapeRendererEvent.Invoke(gameObject.GetComponent<SpriteShapeRenderer>());
|
||||
return spriteShapeController;
|
||||
}
|
||||
|
||||
public static SpriteShapeController CreateSpriteShapeControllerFromSelection()
|
||||
{
|
||||
var objName = "New SpriteShapeController";
|
||||
GameObject gameObject = new GameObject(objName, typeof(SpriteShapeController));
|
||||
SpriteShapeController spriteShapeController = gameObject.GetComponent<SpriteShapeController>();
|
||||
if (Selection.activeObject is UnityEngine.U2D.SpriteShape)
|
||||
{
|
||||
spriteShapeController.spriteShape = (UnityEngine.U2D.SpriteShape)Selection.activeObject;
|
||||
objName = spriteShapeController.spriteShape.name;
|
||||
}
|
||||
else if (Selection.activeObject is GameObject)
|
||||
{
|
||||
var activeGO = (GameObject)Selection.activeObject;
|
||||
var prefabType = PrefabUtility.GetPrefabAssetType(activeGO);
|
||||
if (prefabType != PrefabAssetType.Regular && prefabType != PrefabAssetType.Model)
|
||||
{
|
||||
GameObjectUtility.SetParentAndAlign(gameObject, activeGO);
|
||||
}
|
||||
}
|
||||
gameObject.name = GameObjectUtility.GetUniqueNameForSibling(gameObject.transform.parent, objName);
|
||||
Undo.RegisterCreatedObjectUndo(gameObject, Contents.createSpriteShapeString);
|
||||
Selection.activeGameObject = gameObject;
|
||||
spriteShapeController.spline.Clear();
|
||||
SpriteShapeEditorAnalytics.instance.eventBus.spriteShapeRendererEvent.Invoke(gameObject.GetComponent<SpriteShapeRenderer>());
|
||||
return spriteShapeController;
|
||||
}
|
||||
|
||||
public static void SetShapeFromAsset(SpriteShapeController spriteShapeController)
|
||||
{
|
||||
UnityEngine.U2D.SpriteShape spriteShape = spriteShapeController.spriteShape;
|
||||
|
||||
if (!spriteShape)
|
||||
{
|
||||
SpriteShapeEditorUtility.SetToSquare(spriteShapeController);
|
||||
return;
|
||||
}
|
||||
|
||||
if (spriteShape.angleRanges.Count == 1 && spriteShape.angleRanges[0].end - spriteShape.angleRanges[0].start == 360f)
|
||||
SpriteShapeEditorUtility.SetToLine(spriteShapeController);
|
||||
else if (spriteShape.angleRanges.Count < 8)
|
||||
SpriteShapeEditorUtility.SetToSquare(spriteShapeController);
|
||||
else
|
||||
SpriteShapeEditorUtility.SetToOctogon(spriteShapeController);
|
||||
}
|
||||
|
||||
static void SetToSquare(SpriteShapeController spriteShapeController)
|
||||
{
|
||||
spriteShapeController.spline.Clear();
|
||||
spriteShapeController.spline.InsertPointAt(0, new Vector3(-kMaxSideSize, -kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(1, new Vector3(-kMaxSideSize, kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(2, new Vector3(kMaxSideSize, kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(3, new Vector3(kMaxSideSize, -kMaxSideSize, 0));
|
||||
}
|
||||
|
||||
static void SetToLine(SpriteShapeController spriteShapeController)
|
||||
{
|
||||
spriteShapeController.spline.Clear();
|
||||
spriteShapeController.spline.InsertPointAt(0, new Vector3(-kMaxSideSize, 0.0f, 0));
|
||||
spriteShapeController.spline.InsertPointAt(1, new Vector3(kMaxSideSize, 0.0f, 0));
|
||||
spriteShapeController.spline.isOpenEnded = true;
|
||||
}
|
||||
|
||||
static void SetToOctogon(SpriteShapeController spriteShapeController)
|
||||
{
|
||||
float kMaxSideSizeHalf = kMaxSideSize * 0.5f;
|
||||
|
||||
spriteShapeController.spline.Clear();
|
||||
spriteShapeController.spline.InsertPointAt(0, new Vector3(-kMaxSideSizeHalf, -kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(1, new Vector3(-kMaxSideSize, -kMaxSideSizeHalf, 0));
|
||||
spriteShapeController.spline.InsertPointAt(2, new Vector3(-kMaxSideSize, kMaxSideSizeHalf, 0));
|
||||
spriteShapeController.spline.InsertPointAt(3, new Vector3(-kMaxSideSizeHalf, kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(4, new Vector3(kMaxSideSizeHalf, kMaxSideSize, 0));
|
||||
spriteShapeController.spline.InsertPointAt(5, new Vector3(kMaxSideSize, kMaxSideSizeHalf, 0));
|
||||
spriteShapeController.spline.InsertPointAt(6, new Vector3(kMaxSideSize, -kMaxSideSizeHalf, 0));
|
||||
spriteShapeController.spline.InsertPointAt(7, new Vector3(kMaxSideSizeHalf, -kMaxSideSize, 0));
|
||||
}
|
||||
|
||||
public static int GetRangeIndexFromAngle(UnityEngine.U2D.SpriteShape spriteShape, float angle)
|
||||
{
|
||||
return GetRangeIndexFromAngle(spriteShape.angleRanges, angle);
|
||||
}
|
||||
|
||||
public static int GetRangeIndexFromAngle(List<AngleRange> angleRanges, float angle)
|
||||
{
|
||||
for (int i = 0; i < angleRanges.Count; ++i)
|
||||
{
|
||||
var angleRange = angleRanges[i];
|
||||
var range = angleRange.end - angleRange.start;
|
||||
var angle2 = Mathf.Repeat(angle - angleRange.start, 360f);
|
||||
|
||||
if (angle2 >= 0f && angle2 <= range)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,349 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Sprites;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.U2D
|
||||
{
|
||||
public class SpriteShapeHandleUtility
|
||||
{
|
||||
private class Styles
|
||||
{
|
||||
public Texture playheadTex;
|
||||
public Texture handRightTex;
|
||||
public Texture handLeftTex;
|
||||
}
|
||||
|
||||
private static Styles s_Styles;
|
||||
private static Styles styles
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Styles == null)
|
||||
s_Styles = new Styles();
|
||||
|
||||
return s_Styles;
|
||||
}
|
||||
}
|
||||
|
||||
static private Material s_HandleWireMaterial;
|
||||
private static Material handleWireMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!s_HandleWireMaterial)
|
||||
s_HandleWireMaterial = (Material)EditorGUIUtility.LoadRequired("SceneView/2DHandleLines.mat");
|
||||
|
||||
return s_HandleWireMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private static Material s_FillTextureMaterial;
|
||||
private static Material fillTextureMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_FillTextureMaterial == null)
|
||||
{
|
||||
s_FillTextureMaterial = new Material(Shader.Find("Hidden/InternalSpritesInspector"));
|
||||
s_FillTextureMaterial.hideFlags = HideFlags.DontSave;
|
||||
}
|
||||
s_FillTextureMaterial.SetFloat("_AdjustLinearForGamma", PlayerSettings.colorSpace == ColorSpace.Linear ? 1.0f : 0.0f);
|
||||
return s_FillTextureMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private static Mesh s_TextureCapMesh;
|
||||
private static Mesh textureCapMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_TextureCapMesh == null)
|
||||
{
|
||||
s_TextureCapMesh = new Mesh();
|
||||
s_TextureCapMesh.hideFlags = HideFlags.DontSave;
|
||||
s_TextureCapMesh.vertices = new Vector3[] {
|
||||
new Vector2(-0.5f, -0.5f),
|
||||
new Vector2(-0.5f, 0.5f),
|
||||
new Vector2(0.5f, 0.5f),
|
||||
new Vector2(-0.5f, -0.5f),
|
||||
new Vector2(0.5f, 0.5f),
|
||||
new Vector2(0.5f, -0.5f)
|
||||
};
|
||||
s_TextureCapMesh.uv = new Vector2[] {
|
||||
Vector3.zero,
|
||||
Vector3.up,
|
||||
Vector3.up + Vector3.right,
|
||||
Vector3.zero,
|
||||
Vector3.up + Vector3.right,
|
||||
Vector3.right
|
||||
};
|
||||
s_TextureCapMesh.SetTriangles(new int[] { 0, 1, 2, 3, 4, 5 }, 0);
|
||||
}
|
||||
|
||||
return s_TextureCapMesh;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Vector3[] s_WireArcPoints = new Vector3[60];
|
||||
|
||||
public static float PosToAngle(Vector2 position, Vector2 center, float angleOffset)
|
||||
{
|
||||
Vector2 dir = (Quaternion.AngleAxis(angleOffset, Vector3.forward) * (position - center)).normalized;
|
||||
return Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
|
||||
}
|
||||
|
||||
public static Vector2 Slider2D(int id, Vector2 position, Vector3 capOffset, Quaternion rotation, float size, Handles.CapFunction drawCapFunction)
|
||||
{
|
||||
return Handles.Slider2D(id, position, capOffset, Vector3.forward, rotation * Vector3.up, rotation * Vector3.right, size, drawCapFunction, Vector2.zero);
|
||||
}
|
||||
|
||||
public static void DrawRangeOutline(float start, float end, float angleOffset, Vector2 center, float radius, float width)
|
||||
{
|
||||
Vector3 startVec = Quaternion.AngleAxis(start + angleOffset, Vector3.forward) * Vector3.right;
|
||||
Vector3 endVec = Quaternion.AngleAxis(end + angleOffset, Vector3.forward) * Vector3.right;
|
||||
|
||||
Handles.DrawWireArc(center, Vector3.forward, startVec, end - start, radius - width);
|
||||
Handles.DrawWireArc(center, Vector3.forward, startVec, end - start, radius);
|
||||
Handles.DrawLine(startVec * (radius - width) + (Vector3)center, startVec * radius + (Vector3)center);
|
||||
Handles.DrawLine(endVec * (radius - width) + (Vector3)center, endVec * radius + (Vector3)center);
|
||||
}
|
||||
|
||||
private static void ApplyWireMaterial()
|
||||
{
|
||||
UnityEngine.Rendering.CompareFunction zTest = UnityEngine.Rendering.CompareFunction.Always;
|
||||
ApplyWireMaterial(zTest);
|
||||
}
|
||||
|
||||
private static void ApplyWireMaterial(UnityEngine.Rendering.CompareFunction zTest)
|
||||
{
|
||||
Material mat = handleWireMaterial;
|
||||
mat.SetInt("_HandleZTest", (int)zTest);
|
||||
mat.SetPass(0);
|
||||
}
|
||||
|
||||
static void SetDiscSectionPoints(Vector3[] dest, Vector3 center, Vector3 normal, Vector3 from, float angle, float radius)
|
||||
{
|
||||
Vector3 fromn = from.normalized;
|
||||
Quaternion r = Quaternion.AngleAxis(angle / (float)(dest.Length - 1), normal);
|
||||
Vector3 tangent = fromn * radius;
|
||||
for (int i = 0; i < dest.Length; i++)
|
||||
{
|
||||
dest[i] = center + tangent;
|
||||
tangent = r * tangent;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawSolidArc(Vector3 center, Vector3 normal, Vector3 from, float angle, float radius, float width)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
SetDiscSectionPoints(s_WireArcPoints, center, normal, from, angle, radius);
|
||||
|
||||
Shader.SetGlobalColor("_HandleColor", Handles.color);
|
||||
Shader.SetGlobalFloat("_HandleSize", 1);
|
||||
|
||||
ApplyWireMaterial(Handles.zTest);
|
||||
|
||||
float widthPercentage = 1f - Mathf.Clamp01(width / radius);
|
||||
// Draw it twice to ensure backface culling doesn't hide any of the faces
|
||||
GL.PushMatrix();
|
||||
GL.MultMatrix(Handles.matrix);
|
||||
GL.Begin(GL.TRIANGLES);
|
||||
for (int i = 1, count = s_WireArcPoints.Length; i < count; ++i)
|
||||
{
|
||||
Vector3 d1 = s_WireArcPoints[i - 1] - center;
|
||||
Vector3 d2 = s_WireArcPoints[i] - center;
|
||||
GL.Color(Handles.color);
|
||||
GL.Vertex(d1 * widthPercentage + center);
|
||||
GL.Vertex(s_WireArcPoints[i - 1]);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
GL.Vertex(d1 * widthPercentage + center);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
GL.Vertex(d2 * widthPercentage + center);
|
||||
|
||||
GL.Vertex(d1 * widthPercentage + center);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
GL.Vertex(s_WireArcPoints[i - 1]);
|
||||
GL.Vertex(d1 * widthPercentage + center);
|
||||
GL.Vertex(d2 * widthPercentage + center);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
}
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void DrawTextureArc(Texture texture, float pixelsPerRadius, Vector3 center, Vector3 normal, Vector3 from, float angle, float radius)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint || !texture)
|
||||
return;
|
||||
|
||||
SetDiscSectionPoints(s_WireArcPoints, Vector3.zero, normal, from, angle, 0.5f);
|
||||
|
||||
fillTextureMaterial.mainTexture = texture;
|
||||
fillTextureMaterial.mainTextureScale = new Vector2(1f, -1f);
|
||||
fillTextureMaterial.mainTextureOffset = Vector2.zero;
|
||||
fillTextureMaterial.SetPass(0);
|
||||
|
||||
Matrix4x4 matrix = new Matrix4x4();
|
||||
matrix.SetTRS(center, Quaternion.identity, new Vector3(radius, radius, 1) * 2f);
|
||||
Vector3 texOffset = Vector2.one * 0.5f;
|
||||
float scale = pixelsPerRadius / radius;
|
||||
|
||||
GL.PushMatrix();
|
||||
GL.LoadPixelMatrix();
|
||||
GL.MultMatrix(matrix);
|
||||
GL.Begin(GL.TRIANGLES);
|
||||
for (int i = 1, count = s_WireArcPoints.Length; i < count; ++i)
|
||||
{
|
||||
GL.Color(Handles.color);
|
||||
GL.TexCoord(texOffset);
|
||||
GL.Vertex(Vector3.zero);
|
||||
GL.TexCoord(s_WireArcPoints[i - 1] * scale + texOffset);
|
||||
GL.Vertex(s_WireArcPoints[i - 1]);
|
||||
GL.TexCoord(s_WireArcPoints[i] * scale + texOffset);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
GL.TexCoord(texOffset);
|
||||
GL.Vertex(Vector3.zero);
|
||||
GL.TexCoord(s_WireArcPoints[i] * scale + texOffset);
|
||||
GL.Vertex(s_WireArcPoints[i]);
|
||||
GL.TexCoord(s_WireArcPoints[i - 1] * scale + texOffset);
|
||||
GL.Vertex(s_WireArcPoints[i - 1]);
|
||||
}
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public static void PlayHeadCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
||||
{
|
||||
if (styles.playheadTex == null)
|
||||
styles.playheadTex = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.spriteshape/Editor/Handles/ss_playhead.png");
|
||||
GUITextureCap(controlID, styles.playheadTex, position, rotation, size, eventType);
|
||||
}
|
||||
|
||||
public static void RangeLeftCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
||||
{
|
||||
if (styles.handLeftTex == null)
|
||||
styles.handLeftTex = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.spriteshape/Editor/Handles/ss_leftrange.png");
|
||||
GUITextureCap(controlID, styles.handLeftTex, position, rotation, size, eventType);
|
||||
}
|
||||
|
||||
public static void RangeRightCap(int controlID, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
||||
{
|
||||
if (styles.handRightTex == null)
|
||||
styles.handRightTex = AssetDatabase.LoadAssetAtPath<Texture2D>("Packages/com.unity.2d.spriteshape/Editor/Handles/ss_rightrange.png");
|
||||
GUITextureCap(controlID, styles.handRightTex, position, rotation, size, eventType);
|
||||
}
|
||||
|
||||
public static void GUITextureCap(int controlID, Texture texture, Vector3 position, Quaternion rotation, float size, EventType eventType)
|
||||
{
|
||||
switch (eventType)
|
||||
{
|
||||
case (EventType.Layout):
|
||||
HandleUtility.AddControl(controlID, DistanceToRectangle(position, rotation, Vector2.one * size * 0.5f));
|
||||
break;
|
||||
case (EventType.Repaint):
|
||||
|
||||
FilterMode filterMode = texture.filterMode;
|
||||
texture.filterMode = FilterMode.Bilinear;
|
||||
|
||||
EditorSpriteGUIUtility.spriteMaterial.mainTexture = texture;
|
||||
|
||||
float w = (float)texture.width;
|
||||
float h = (float)texture.height;
|
||||
float max = Mathf.Max(w, h);
|
||||
|
||||
Vector3 scale = new Vector2(w / max, h / max) * size;
|
||||
|
||||
if (Camera.current == null)
|
||||
scale.y *= -1f;
|
||||
|
||||
EditorSpriteGUIUtility.DrawMesh(textureCapMesh, EditorSpriteGUIUtility.spriteMaterial, position, rotation, scale);
|
||||
|
||||
texture.filterMode = filterMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static float DistanceToArcWidth(Vector2 position, Vector2 center, float start, float end, float radius, float width, float angleOffet)
|
||||
{
|
||||
float innerRadius = radius - width;
|
||||
float angle = PosToAngle(position, center, -angleOffet);
|
||||
|
||||
angle = Mathf.Repeat(angle - start, 360f);
|
||||
float range = end - start;
|
||||
|
||||
if (angle >= 0f && angle <= range)
|
||||
{
|
||||
float distanceToCenter = (position - center).magnitude;
|
||||
|
||||
if (distanceToCenter <= radius && distanceToCenter >= innerRadius)
|
||||
return 0f;
|
||||
else if (distanceToCenter > radius)
|
||||
return distanceToCenter - radius;
|
||||
else if (distanceToCenter < innerRadius)
|
||||
return innerRadius - distanceToCenter;
|
||||
}
|
||||
else if (angle < 0f)
|
||||
{
|
||||
Vector2 pos1 = (Vector2)(Quaternion.AngleAxis(start + angleOffet, Vector3.forward) * Vector3.right * radius) + center;
|
||||
Vector2 pos2 = (Vector2)(Quaternion.AngleAxis(start + angleOffet, Vector3.forward) * Vector3.right * innerRadius) + center;
|
||||
|
||||
return Mathf.Min((position - pos1).magnitude, (position - pos2).magnitude);
|
||||
}
|
||||
else if (angle > range)
|
||||
{
|
||||
Vector2 pos1 = (Vector2)(Quaternion.AngleAxis(end + angleOffet, Vector3.forward) * Vector3.right * radius) + center;
|
||||
Vector2 pos2 = (Vector2)(Quaternion.AngleAxis(end + angleOffet, Vector3.forward) * Vector3.right * innerRadius) + center;
|
||||
|
||||
return Mathf.Min((position - pos1).magnitude, (position - pos2).magnitude);
|
||||
}
|
||||
|
||||
return float.MaxValue;
|
||||
}
|
||||
|
||||
public static float DistanceToRectangle(Vector3 position, Quaternion rotation, Vector2 size)
|
||||
{
|
||||
Vector3[] points = { Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero, Vector3.zero };
|
||||
Vector3 sideways = rotation * new Vector3(size.x, 0, 0);
|
||||
Vector3 up = rotation * new Vector3(0, size.y, 0);
|
||||
points[0] = HandleUtility.WorldToGUIPoint(position + sideways + up);
|
||||
points[1] = HandleUtility.WorldToGUIPoint(position + sideways - up);
|
||||
points[2] = HandleUtility.WorldToGUIPoint(position - sideways - up);
|
||||
points[3] = HandleUtility.WorldToGUIPoint(position - sideways + up);
|
||||
points[4] = points[0];
|
||||
|
||||
Vector2 pos = Event.current.mousePosition;
|
||||
bool oddNodes = false;
|
||||
int j = 4;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
if ((points[i].y > pos.y) != (points[j].y > pos.y))
|
||||
{
|
||||
if (pos.x < (points[j].x - points[i].x) * (pos.y - points[i].y) / (points[j].y - points[i].y) + points[i].x)
|
||||
{
|
||||
oddNodes = !oddNodes;
|
||||
}
|
||||
}
|
||||
j = i;
|
||||
}
|
||||
if (!oddNodes)
|
||||
{
|
||||
// Distance to closest edge (not so fast)
|
||||
float dist, closestDist = -1f;
|
||||
j = 1;
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
dist = HandleUtility.DistancePointToLineSegment(pos, points[i], points[j++]);
|
||||
if (dist < closestDist || closestDist < 0)
|
||||
closestDist = dist;
|
||||
}
|
||||
return closestDist;
|
||||
}
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "Unity.2D.SpriteShape.Editor",
|
||||
"references": [
|
||||
"Unity.InternalAPIEditorBridge.001",
|
||||
"Unity.2D.Common.Editor",
|
||||
"Unity.2D.Common.Runtime",
|
||||
"Unity.2D.SpriteShape.Runtime",
|
||||
"Unity.2D.Path.Editor"
|
||||
],
|
||||
"optionalUnityReferences": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": []
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
com.unity.2d.spriteshape copyright © 2020 Unity Technologies ApS
|
||||
|
||||
Licensed under the Unity Companion License for Unity-dependent projects (see https://unity3d.com/legal/licenses/unity_companion_license).
|
||||
|
||||
Unless expressly provided otherwise, the Software under this license is made available strictly on an “AS IS” BASIS WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. Please review the license for details on these and other terms and conditions.
|
@@ -0,0 +1,21 @@
|
||||
# About 2D SpriteShape
|
||||
|
||||
Use the **2D SpriteShape** package as a powerful worldbuilding tool that allows you to tile Sprites along the path of a shape, with the Sprites automatically deforming in response to different angles according to their settings. For example, use **2D SpriteShapes** to quickly build various 2D platforms by editing their spline paths into different shapes.
|
||||
|
||||
# Installing 2D SpriteShape
|
||||
|
||||
To install this package, please follow the instructions in the [Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@1.9/manual/index.html).
|
||||
|
||||
# Links to Feature Documentation and Useful Resources
|
||||
|
||||
* [2D SpriteShape Online Documentation](https://github.com/Unity-Technologies/2d-spriteshape-samples/blob/master/Documentation/SpriteShape.md)
|
||||
|
||||
* [2D SpriteShape Samples](https://github.com/Unity-Technologies/2d-spriteshape-samples)
|
||||
|
||||
* [2D SpriteShape Discussion Forums](https://forum.unity.com/threads/spriteshape-preview-package.522575/)
|
||||
|
||||
# Requirements
|
||||
|
||||
This version of **2D SpriteShape** is compatible with the following versions of the Unity Editor:
|
||||
|
||||
* 2018.1 and later (recommended)
|
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Editor")]
|
||||
[assembly: InternalsVisibleTo("Unity.2D.SpriteShape.Tests.EditorTests")]
|
@@ -0,0 +1,267 @@
|
||||
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
public static class BezierUtility
|
||||
{
|
||||
static Vector3[] s_TempPoints = new Vector3[3];
|
||||
|
||||
public static Vector3 BezierPoint(Vector3 startPosition, Vector3 startTangent, Vector3 endTangent, Vector3 endPosition, float t)
|
||||
{
|
||||
float s = 1.0f - t;
|
||||
return s * s * s * startPosition + 3.0f * s * s * t * startTangent + 3.0f * s * t * t * endTangent + t * t * t * endPosition;
|
||||
}
|
||||
internal static float GetSpritePixelWidth(Sprite sprite)
|
||||
{
|
||||
float4 meta = new float4(sprite.pixelsPerUnit, sprite.pivot.y / sprite.textureRect.height, sprite.rect.width, sprite.rect.height);
|
||||
float4 border = new float4(sprite.border.x, sprite.border.y, sprite.border.z, sprite.border.w);
|
||||
float rpunits = 1.0f / meta.x;
|
||||
float2 whsize = new float2(meta.z, meta.w) * rpunits;
|
||||
|
||||
border = border * rpunits;
|
||||
float stPixelU = border.x;
|
||||
float enPixelU = whsize.x - border.z;
|
||||
float pxlWidth = enPixelU - stPixelU;
|
||||
return pxlWidth;
|
||||
}
|
||||
internal static float BezierLength(NativeArray<ShapeControlPoint> shapePoints, int splineDetail)
|
||||
{
|
||||
// Expand the Bezier.
|
||||
int controlPointContour = shapePoints.Length - 1;
|
||||
float spd = 0;
|
||||
float fmax = (float)(splineDetail - 1);
|
||||
for (int i = 0; i < controlPointContour; ++i)
|
||||
{
|
||||
int j = i + 1;
|
||||
ShapeControlPoint cp = shapePoints[i];
|
||||
ShapeControlPoint pp = shapePoints[j];
|
||||
|
||||
Vector3 p0 = cp.position;
|
||||
Vector3 p1 = pp.position;
|
||||
Vector3 sp = p0;
|
||||
Vector3 rt = p0 + cp.rightTangent;
|
||||
Vector3 lt = p1 + pp.leftTangent;
|
||||
|
||||
for (int n = 0; n < splineDetail; ++n)
|
||||
{
|
||||
float t = (float)n / fmax;
|
||||
Vector3 bp = BezierPoint(rt, p0, p1, lt, t);
|
||||
spd += math.distance(bp, sp);
|
||||
sp = bp;
|
||||
}
|
||||
}
|
||||
|
||||
return spd;
|
||||
}
|
||||
|
||||
public static Vector3 ClosestPointOnCurve(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, out float t)
|
||||
{
|
||||
Vector3 startToEnd = endPosition - startPosition;
|
||||
Vector3 startToTangent = (startTangent - startPosition);
|
||||
Vector3 endToTangent = (endTangent - endPosition);
|
||||
|
||||
float sqrError = 0.001f;
|
||||
|
||||
if (Colinear(startToTangent, startToEnd, sqrError) && Colinear(endToTangent, startToEnd, sqrError))
|
||||
return ClosestPointToSegment(point, startPosition, endPosition, out t);
|
||||
|
||||
Vector3 leftStartPosition;
|
||||
Vector3 leftEndPosition;
|
||||
Vector3 leftStartTangent;
|
||||
Vector3 leftEndTangent;
|
||||
|
||||
Vector3 rightStartPosition;
|
||||
Vector3 rightEndPosition;
|
||||
Vector3 rightStartTangent;
|
||||
Vector3 rightEndTangent;
|
||||
|
||||
float leftStartT = 0f;
|
||||
float leftEndT = 0.5f;
|
||||
float rightStartT = 0.5f;
|
||||
float rightEndT = 1f;
|
||||
|
||||
SplitBezier(0.5f, startPosition, endPosition, startTangent, endTangent,
|
||||
out leftStartPosition, out leftEndPosition, out leftStartTangent, out leftEndTangent,
|
||||
out rightStartPosition, out rightEndPosition, out rightStartTangent, out rightEndTangent);
|
||||
|
||||
Vector3 pointLeft = ClosestPointOnCurveIterative(point, leftStartPosition, leftEndPosition, leftStartTangent, leftEndTangent, sqrError, ref leftStartT, ref leftEndT);
|
||||
Vector3 pointRight = ClosestPointOnCurveIterative(point, rightStartPosition, rightEndPosition, rightStartTangent, rightEndTangent, sqrError, ref rightStartT, ref rightEndT);
|
||||
|
||||
if ((point - pointLeft).sqrMagnitude < (point - pointRight).sqrMagnitude)
|
||||
{
|
||||
t = leftStartT;
|
||||
return pointLeft;
|
||||
}
|
||||
|
||||
t = rightStartT;
|
||||
return pointRight;
|
||||
}
|
||||
|
||||
public static Vector3 ClosestPointOnCurveFast(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, out float t)
|
||||
{
|
||||
float sqrError = 0.001f;
|
||||
float startT = 0f;
|
||||
float endT = 1f;
|
||||
|
||||
Vector3 closestPoint = ClosestPointOnCurveIterative(point, startPosition, endPosition, startTangent, endTangent, sqrError, ref startT, ref endT);
|
||||
|
||||
t = startT;
|
||||
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
private static Vector3 ClosestPointOnCurveIterative(Vector3 point, Vector3 startPosition, Vector3 endPosition, Vector3 startTangent, Vector3 endTangent, float sqrError, ref float startT, ref float endT)
|
||||
{
|
||||
while ((startPosition - endPosition).sqrMagnitude > sqrError)
|
||||
{
|
||||
Vector3 startToEnd = endPosition - startPosition;
|
||||
Vector3 startToTangent = (startTangent - startPosition);
|
||||
Vector3 endToTangent = (endTangent - endPosition);
|
||||
|
||||
if (Colinear(startToTangent, startToEnd, sqrError) && Colinear(endToTangent, startToEnd, sqrError))
|
||||
{
|
||||
float t;
|
||||
Vector3 closestPoint = ClosestPointToSegment(point, startPosition, endPosition, out t);
|
||||
t *= (endT - startT);
|
||||
startT += t;
|
||||
endT -= t;
|
||||
return closestPoint;
|
||||
}
|
||||
|
||||
Vector3 leftStartPosition;
|
||||
Vector3 leftEndPosition;
|
||||
Vector3 leftStartTangent;
|
||||
Vector3 leftEndTangent;
|
||||
|
||||
Vector3 rightStartPosition;
|
||||
Vector3 rightEndPosition;
|
||||
Vector3 rightStartTangent;
|
||||
Vector3 rightEndTangent;
|
||||
|
||||
SplitBezier(0.5f, startPosition, endPosition, startTangent, endTangent,
|
||||
out leftStartPosition, out leftEndPosition, out leftStartTangent, out leftEndTangent,
|
||||
out rightStartPosition, out rightEndPosition, out rightStartTangent, out rightEndTangent);
|
||||
|
||||
s_TempPoints[0] = leftStartPosition;
|
||||
s_TempPoints[1] = leftStartTangent;
|
||||
s_TempPoints[2] = leftEndTangent;
|
||||
|
||||
float sqrDistanceLeft = SqrDistanceToPolyLine(point, s_TempPoints);
|
||||
|
||||
s_TempPoints[0] = rightEndPosition;
|
||||
s_TempPoints[1] = rightEndTangent;
|
||||
s_TempPoints[2] = rightStartTangent;
|
||||
|
||||
float sqrDistanceRight = SqrDistanceToPolyLine(point, s_TempPoints);
|
||||
|
||||
if (sqrDistanceLeft < sqrDistanceRight)
|
||||
{
|
||||
startPosition = leftStartPosition;
|
||||
endPosition = leftEndPosition;
|
||||
startTangent = leftStartTangent;
|
||||
endTangent = leftEndTangent;
|
||||
|
||||
endT -= (endT - startT) * 0.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
startPosition = rightStartPosition;
|
||||
endPosition = rightEndPosition;
|
||||
startTangent = rightStartTangent;
|
||||
endTangent = rightEndTangent;
|
||||
|
||||
startT += (endT - startT) * 0.5f;
|
||||
}
|
||||
}
|
||||
|
||||
return endPosition;
|
||||
}
|
||||
|
||||
public static void SplitBezier(float t, Vector3 startPosition, Vector3 endPosition, Vector3 startRightTangent, Vector3 endLeftTangent,
|
||||
out Vector3 leftStartPosition, out Vector3 leftEndPosition, out Vector3 leftStartTangent, out Vector3 leftEndTangent,
|
||||
out Vector3 rightStartPosition, out Vector3 rightEndPosition, out Vector3 rightStartTangent, out Vector3 rightEndTangent)
|
||||
{
|
||||
Vector3 tangent0 = (startRightTangent - startPosition);
|
||||
Vector3 tangent1 = (endLeftTangent - endPosition);
|
||||
Vector3 tangentEdge = (endLeftTangent - startRightTangent);
|
||||
|
||||
Vector3 tangentPoint0 = startPosition + tangent0 * t;
|
||||
Vector3 tangentPoint1 = endPosition + tangent1 * (1f - t);
|
||||
Vector3 tangentEdgePoint = startRightTangent + tangentEdge * t;
|
||||
|
||||
Vector3 newTangent0 = tangentPoint0 + (tangentEdgePoint - tangentPoint0) * t;
|
||||
Vector3 newTangent1 = tangentPoint1 + (tangentEdgePoint - tangentPoint1) * (1f - t);
|
||||
Vector3 newTangentEdge = newTangent1 - newTangent0;
|
||||
|
||||
Vector3 bezierPoint = newTangent0 + newTangentEdge * t;
|
||||
|
||||
leftStartPosition = startPosition;
|
||||
leftEndPosition = bezierPoint;
|
||||
leftStartTangent = tangentPoint0;
|
||||
leftEndTangent = newTangent0;
|
||||
|
||||
rightStartPosition = bezierPoint;
|
||||
rightEndPosition = endPosition;
|
||||
rightStartTangent = newTangent1;
|
||||
rightEndTangent = tangentPoint1;
|
||||
}
|
||||
|
||||
private static Vector3 ClosestPointToSegment(Vector3 point, Vector3 segmentStart, Vector3 segmentEnd, out float t)
|
||||
{
|
||||
Vector3 relativePoint = point - segmentStart;
|
||||
Vector3 segment = (segmentEnd - segmentStart);
|
||||
Vector3 segmentDirection = segment.normalized;
|
||||
float length = segment.magnitude;
|
||||
|
||||
float dot = Vector3.Dot(relativePoint, segmentDirection);
|
||||
|
||||
if (dot <= 0f)
|
||||
dot = 0f;
|
||||
else if (dot >= length)
|
||||
dot = length;
|
||||
|
||||
t = dot / length;
|
||||
|
||||
return segmentStart + segment * t;
|
||||
}
|
||||
|
||||
private static float SqrDistanceToPolyLine(Vector3 point, Vector3[] points)
|
||||
{
|
||||
float minDistance = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < points.Length - 1; ++i)
|
||||
{
|
||||
float distance = SqrDistanceToSegment(point, points[i], points[i + 1]);
|
||||
|
||||
if (distance < minDistance)
|
||||
minDistance = distance;
|
||||
}
|
||||
|
||||
return minDistance;
|
||||
}
|
||||
|
||||
private static float SqrDistanceToSegment(Vector3 point, Vector3 segmentStart, Vector3 segmentEnd)
|
||||
{
|
||||
Vector3 relativePoint = point - segmentStart;
|
||||
Vector3 segment = (segmentEnd - segmentStart);
|
||||
Vector3 segmentDirection = segment.normalized;
|
||||
float length = segment.magnitude;
|
||||
|
||||
float dot = Vector3.Dot(relativePoint, segmentDirection);
|
||||
|
||||
if (dot <= 0f)
|
||||
return (point - segmentStart).sqrMagnitude;
|
||||
else if (dot >= length)
|
||||
return (point - segmentEnd).sqrMagnitude;
|
||||
|
||||
return Vector3.Cross(relativePoint, segmentDirection).sqrMagnitude;
|
||||
}
|
||||
|
||||
private static bool Colinear(Vector3 v1, Vector3 v2, float error = 0.0001f)
|
||||
{
|
||||
return Mathf.Abs(v1.x * v2.y - v1.y * v2.x + v1.x * v2.z - v1.z * v2.x + v1.y * v2.z - v1.z * v2.y) < error;
|
||||
}
|
||||
}
|
||||
}
|
107
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/Dict.cs
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal class Dict<TValue> where TValue : class
|
||||
{
|
||||
public class Node
|
||||
{
|
||||
internal TValue _key;
|
||||
internal Node _prev, _next;
|
||||
|
||||
public TValue Key { get { return _key; } }
|
||||
public Node Prev { get { return _prev; } }
|
||||
public Node Next { get { return _next; } }
|
||||
}
|
||||
|
||||
public delegate bool LessOrEqual(TValue lhs, TValue rhs);
|
||||
|
||||
private LessOrEqual _leq;
|
||||
Node _head;
|
||||
|
||||
public Dict(LessOrEqual leq)
|
||||
{
|
||||
_leq = leq;
|
||||
|
||||
_head = new Node { _key = null };
|
||||
_head._prev = _head;
|
||||
_head._next = _head;
|
||||
}
|
||||
|
||||
public Node Insert(TValue key)
|
||||
{
|
||||
return InsertBefore(_head, key);
|
||||
}
|
||||
|
||||
public Node InsertBefore(Node node, TValue key)
|
||||
{
|
||||
do {
|
||||
node = node._prev;
|
||||
} while (node._key != null && !_leq(node._key, key));
|
||||
|
||||
var newNode = new Node { _key = key };
|
||||
newNode._next = node._next;
|
||||
node._next._prev = newNode;
|
||||
newNode._prev = node;
|
||||
node._next = newNode;
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
public Node Find(TValue key)
|
||||
{
|
||||
var node = _head;
|
||||
do {
|
||||
node = node._next;
|
||||
} while (node._key != null && !_leq(key, node._key));
|
||||
return node;
|
||||
}
|
||||
|
||||
public Node Min()
|
||||
{
|
||||
return _head._next;
|
||||
}
|
||||
|
||||
public void Remove(Node node)
|
||||
{
|
||||
node._next._prev = node._prev;
|
||||
node._prev._next = node._next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
301
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/Geom.cs
vendored
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
using Real = System.Single;
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal static class Geom
|
||||
{
|
||||
public static bool IsWindingInside(WindingRule rule, int n)
|
||||
{
|
||||
switch (rule)
|
||||
{
|
||||
case WindingRule.EvenOdd:
|
||||
return (n & 1) == 1;
|
||||
case WindingRule.NonZero:
|
||||
return n != 0;
|
||||
case WindingRule.Positive:
|
||||
return n > 0;
|
||||
case WindingRule.Negative:
|
||||
return n < 0;
|
||||
case WindingRule.AbsGeqTwo:
|
||||
return n >= 2 || n <= -2;
|
||||
}
|
||||
throw new Exception("Wrong winding rule");
|
||||
}
|
||||
|
||||
public static bool VertCCW(MeshUtils.Vertex u, MeshUtils.Vertex v, MeshUtils.Vertex w)
|
||||
{
|
||||
return (u._s * (v._t - w._t) + v._s * (w._t - u._t) + w._s * (u._t - v._t)) >= 0.0f;
|
||||
}
|
||||
public static bool VertEq(MeshUtils.Vertex lhs, MeshUtils.Vertex rhs)
|
||||
{
|
||||
return lhs._s == rhs._s && lhs._t == rhs._t;
|
||||
}
|
||||
public static bool VertLeq(MeshUtils.Vertex lhs, MeshUtils.Vertex rhs)
|
||||
{
|
||||
return (lhs._s < rhs._s) || (lhs._s == rhs._s && lhs._t <= rhs._t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given three vertices u,v,w such that VertLeq(u,v) && VertLeq(v,w),
|
||||
/// evaluates the t-coord of the edge uw at the s-coord of the vertex v.
|
||||
/// Returns v->t - (uw)(v->s), ie. the signed distance from uw to v.
|
||||
/// If uw is vertical (and thus passes thru v), the result is zero.
|
||||
///
|
||||
/// The calculation is extremely accurate and stable, even when v
|
||||
/// is very close to u or w. In particular if we set v->t = 0 and
|
||||
/// let r be the negated result (this evaluates (uw)(v->s)), then
|
||||
/// r is guaranteed to satisfy MIN(u->t,w->t) <= r <= MAX(u->t,w->t).
|
||||
/// </summary>
|
||||
public static Real EdgeEval(MeshUtils.Vertex u, MeshUtils.Vertex v, MeshUtils.Vertex w)
|
||||
{
|
||||
Debug.Assert(VertLeq(u, v) && VertLeq(v, w));
|
||||
|
||||
var gapL = v._s - u._s;
|
||||
var gapR = w._s - v._s;
|
||||
|
||||
if (gapL + gapR > 0.0f)
|
||||
{
|
||||
if (gapL < gapR)
|
||||
{
|
||||
return (v._t - u._t) + (u._t - w._t) * (gapL / (gapL + gapR));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (v._t - w._t) + (w._t - u._t) * (gapR / (gapL + gapR));
|
||||
}
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a number whose sign matches EdgeEval(u,v,w) but which
|
||||
/// is cheaper to evaluate. Returns > 0, == 0 , or < 0
|
||||
/// as v is above, on, or below the edge uw.
|
||||
/// </summary>
|
||||
public static Real EdgeSign(MeshUtils.Vertex u, MeshUtils.Vertex v, MeshUtils.Vertex w)
|
||||
{
|
||||
Debug.Assert(VertLeq(u, v) && VertLeq(v, w));
|
||||
|
||||
var gapL = v._s - u._s;
|
||||
var gapR = w._s - v._s;
|
||||
|
||||
if (gapL + gapR > 0.0f)
|
||||
{
|
||||
return (v._t - w._t) * gapL + (v._t - u._t) * gapR;
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool TransLeq(MeshUtils.Vertex lhs, MeshUtils.Vertex rhs)
|
||||
{
|
||||
return (lhs._t < rhs._t) || (lhs._t == rhs._t && lhs._s <= rhs._s);
|
||||
}
|
||||
|
||||
public static Real TransEval(MeshUtils.Vertex u, MeshUtils.Vertex v, MeshUtils.Vertex w)
|
||||
{
|
||||
Debug.Assert(TransLeq(u, v) && TransLeq(v, w));
|
||||
|
||||
var gapL = v._t - u._t;
|
||||
var gapR = w._t - v._t;
|
||||
|
||||
if (gapL + gapR > 0.0f)
|
||||
{
|
||||
if (gapL < gapR)
|
||||
{
|
||||
return (v._s - u._s) + (u._s - w._s) * (gapL / (gapL + gapR));
|
||||
}
|
||||
else
|
||||
{
|
||||
return (v._s - w._s) + (w._s - u._s) * (gapR / (gapL + gapR));
|
||||
}
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Real TransSign(MeshUtils.Vertex u, MeshUtils.Vertex v, MeshUtils.Vertex w)
|
||||
{
|
||||
Debug.Assert(TransLeq(u, v) && TransLeq(v, w));
|
||||
|
||||
var gapL = v._t - u._t;
|
||||
var gapR = w._t - v._t;
|
||||
|
||||
if (gapL + gapR > 0.0f)
|
||||
{
|
||||
return (v._s - w._s) * gapL + (v._s - u._s) * gapR;
|
||||
}
|
||||
/* vertical line */
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static bool EdgeGoesLeft(MeshUtils.Edge e)
|
||||
{
|
||||
return VertLeq(e._Dst, e._Org);
|
||||
}
|
||||
|
||||
public static bool EdgeGoesRight(MeshUtils.Edge e)
|
||||
{
|
||||
return VertLeq(e._Org, e._Dst);
|
||||
}
|
||||
|
||||
public static Real VertL1dist(MeshUtils.Vertex u, MeshUtils.Vertex v)
|
||||
{
|
||||
return Math.Abs(u._s - v._s) + Math.Abs(u._t - v._t);
|
||||
}
|
||||
|
||||
public static void AddWinding(MeshUtils.Edge eDst, MeshUtils.Edge eSrc)
|
||||
{
|
||||
eDst._winding += eSrc._winding;
|
||||
eDst._Sym._winding += eSrc._Sym._winding;
|
||||
}
|
||||
|
||||
public static Real Interpolate(Real a, Real x, Real b, Real y)
|
||||
{
|
||||
if (a < 0.0f)
|
||||
{
|
||||
a = 0.0f;
|
||||
}
|
||||
if (b < 0.0f)
|
||||
{
|
||||
b = 0.0f;
|
||||
}
|
||||
return ((a <= b) ? ((b == 0.0f) ? ((x+y) / 2.0f)
|
||||
: (x + (y-x) * (a/(a+b))))
|
||||
: (y + (x-y) * (b/(a+b))));
|
||||
}
|
||||
|
||||
static void Swap(ref MeshUtils.Vertex a, ref MeshUtils.Vertex b)
|
||||
{
|
||||
var tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given edges (o1,d1) and (o2,d2), compute their point of intersection.
|
||||
/// The computed point is guaranteed to lie in the intersection of the
|
||||
/// bounding rectangles defined by each edge.
|
||||
/// </summary>
|
||||
public static void EdgeIntersect(MeshUtils.Vertex o1, MeshUtils.Vertex d1, MeshUtils.Vertex o2, MeshUtils.Vertex d2, MeshUtils.Vertex v)
|
||||
{
|
||||
// This is certainly not the most efficient way to find the intersection
|
||||
// of two line segments, but it is very numerically stable.
|
||||
//
|
||||
// Strategy: find the two middle vertices in the VertLeq ordering,
|
||||
// and interpolate the intersection s-value from these. Then repeat
|
||||
// using the TransLeq ordering to find the intersection t-value.
|
||||
|
||||
if (!VertLeq(o1, d1)) { Swap(ref o1, ref d1); }
|
||||
if (!VertLeq(o2, d2)) { Swap(ref o2, ref d2); }
|
||||
if (!VertLeq(o1, o2)) { Swap(ref o1, ref o2); Swap(ref d1, ref d2); }
|
||||
|
||||
if (!VertLeq(o2, d1))
|
||||
{
|
||||
// Technically, no intersection -- do our best
|
||||
v._s = (o2._s + d1._s) / 2.0f;
|
||||
}
|
||||
else if (VertLeq(d1, d2))
|
||||
{
|
||||
// Interpolate between o2 and d1
|
||||
var z1 = EdgeEval(o1, o2, d1);
|
||||
var z2 = EdgeEval(o2, d1, d2);
|
||||
if (z1 + z2 < 0.0f)
|
||||
{
|
||||
z1 = -z1;
|
||||
z2 = -z2;
|
||||
}
|
||||
v._s = Interpolate(z1, o2._s, z2, d1._s);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolate between o2 and d2
|
||||
var z1 = EdgeSign(o1, o2, d1);
|
||||
var z2 = -EdgeSign(o1, d2, d1);
|
||||
if (z1 + z2 < 0.0f)
|
||||
{
|
||||
z1 = -z1;
|
||||
z2 = -z2;
|
||||
}
|
||||
v._s = Interpolate(z1, o2._s, z2, d2._s);
|
||||
}
|
||||
|
||||
// Now repeat the process for t
|
||||
|
||||
if (!TransLeq(o1, d1)) { Swap(ref o1, ref d1); }
|
||||
if (!TransLeq(o2, d2)) { Swap(ref o2, ref d2); }
|
||||
if (!TransLeq(o1, o2)) { Swap(ref o1, ref o2); Swap(ref d1, ref d2); }
|
||||
|
||||
if (!TransLeq(o2, d1))
|
||||
{
|
||||
// Technically, no intersection -- do our best
|
||||
v._t = (o2._t + d1._t) / 2.0f;
|
||||
}
|
||||
else if (TransLeq(d1, d2))
|
||||
{
|
||||
// Interpolate between o2 and d1
|
||||
var z1 = TransEval(o1, o2, d1);
|
||||
var z2 = TransEval(o2, d1, d2);
|
||||
if (z1 + z2 < 0.0f)
|
||||
{
|
||||
z1 = -z1;
|
||||
z2 = -z2;
|
||||
}
|
||||
v._t = Interpolate(z1, o2._t, z2, d1._t);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interpolate between o2 and d2
|
||||
var z1 = TransSign(o1, o2, d1);
|
||||
var z2 = -TransSign(o1, d2, d1);
|
||||
if (z1 + z2 < 0.0f)
|
||||
{
|
||||
z1 = -z1;
|
||||
z2 = -z2;
|
||||
}
|
||||
v._t = Interpolate(z1, o2._t, z2, d2._t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
25
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/LICENSE.txt
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
500
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/Mesh.cs
vendored
Normal file
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal class Mesh : MeshUtils.Pooled<Mesh>
|
||||
{
|
||||
internal MeshUtils.Vertex _vHead;
|
||||
internal MeshUtils.Face _fHead;
|
||||
internal MeshUtils.Edge _eHead, _eHeadSym;
|
||||
|
||||
public Mesh()
|
||||
{
|
||||
var v = _vHead = MeshUtils.Vertex.Create();
|
||||
var f = _fHead = MeshUtils.Face.Create();
|
||||
|
||||
var pair = MeshUtils.EdgePair.Create();
|
||||
var e = _eHead = pair._e;
|
||||
var eSym = _eHeadSym = pair._eSym;
|
||||
|
||||
v._next = v._prev = v;
|
||||
v._anEdge = null;
|
||||
|
||||
f._next = f._prev = f;
|
||||
f._anEdge = null;
|
||||
f._trail = null;
|
||||
f._marked = false;
|
||||
f._inside = false;
|
||||
|
||||
e._next = e;
|
||||
e._Sym = eSym;
|
||||
e._Onext = null;
|
||||
e._Lnext = null;
|
||||
e._Org = null;
|
||||
e._Lface = null;
|
||||
e._winding = 0;
|
||||
e._activeRegion = null;
|
||||
|
||||
eSym._next = eSym;
|
||||
eSym._Sym = e;
|
||||
eSym._Onext = null;
|
||||
eSym._Lnext = null;
|
||||
eSym._Org = null;
|
||||
eSym._Lface = null;
|
||||
eSym._winding = 0;
|
||||
eSym._activeRegion = null;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_vHead = null;
|
||||
_fHead = null;
|
||||
_eHead = _eHeadSym = null;
|
||||
}
|
||||
|
||||
public override void OnFree()
|
||||
{
|
||||
for (MeshUtils.Face f = _fHead._next, fNext = _fHead; f != _fHead; f = fNext)
|
||||
{
|
||||
fNext = f._next;
|
||||
f.Free();
|
||||
}
|
||||
for (MeshUtils.Vertex v = _vHead._next, vNext = _vHead; v != _vHead; v = vNext)
|
||||
{
|
||||
vNext = v._next;
|
||||
v.Free();
|
||||
}
|
||||
for (MeshUtils.Edge e = _eHead._next, eNext = _eHead; e != _eHead; e = eNext)
|
||||
{
|
||||
eNext = e._next;
|
||||
e.Free();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates one edge, two vertices and a loop (face).
|
||||
/// The loop consists of the two new half-edges.
|
||||
/// </summary>
|
||||
public MeshUtils.Edge MakeEdge()
|
||||
{
|
||||
var e = MeshUtils.MakeEdge(_eHead);
|
||||
|
||||
MeshUtils.MakeVertex(e, _vHead);
|
||||
MeshUtils.MakeVertex(e._Sym, _vHead);
|
||||
MeshUtils.MakeFace(e, _fHead);
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splice is the basic operation for changing the
|
||||
/// mesh connectivity and topology. It changes the mesh so that
|
||||
/// eOrg->Onext = OLD( eDst->Onext )
|
||||
/// eDst->Onext = OLD( eOrg->Onext )
|
||||
/// where OLD(...) means the value before the meshSplice operation.
|
||||
///
|
||||
/// This can have two effects on the vertex structure:
|
||||
/// - if eOrg->Org != eDst->Org, the two vertices are merged together
|
||||
/// - if eOrg->Org == eDst->Org, the origin is split into two vertices
|
||||
/// In both cases, eDst->Org is changed and eOrg->Org is untouched.
|
||||
///
|
||||
/// Similarly (and independently) for the face structure,
|
||||
/// - if eOrg->Lface == eDst->Lface, one loop is split into two
|
||||
/// - if eOrg->Lface != eDst->Lface, two distinct loops are joined into one
|
||||
/// In both cases, eDst->Lface is changed and eOrg->Lface is unaffected.
|
||||
///
|
||||
/// Some special cases:
|
||||
/// If eDst == eOrg, the operation has no effect.
|
||||
/// If eDst == eOrg->Lnext, the new face will have a single edge.
|
||||
/// If eDst == eOrg->Lprev, the old face will have a single edge.
|
||||
/// If eDst == eOrg->Onext, the new vertex will have a single edge.
|
||||
/// If eDst == eOrg->Oprev, the old vertex will have a single edge.
|
||||
/// </summary>
|
||||
public void Splice(MeshUtils.Edge eOrg, MeshUtils.Edge eDst)
|
||||
{
|
||||
if (eOrg == eDst)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
bool joiningVertices = false;
|
||||
if (eDst._Org != eOrg._Org)
|
||||
{
|
||||
// We are merging two disjoint vertices -- destroy eDst->Org
|
||||
joiningVertices = true;
|
||||
MeshUtils.KillVertex(eDst._Org, eOrg._Org);
|
||||
}
|
||||
bool joiningLoops = false;
|
||||
if (eDst._Lface != eOrg._Lface)
|
||||
{
|
||||
// We are connecting two disjoint loops -- destroy eDst->Lface
|
||||
joiningLoops = true;
|
||||
MeshUtils.KillFace(eDst._Lface, eOrg._Lface);
|
||||
}
|
||||
|
||||
// Change the edge structure
|
||||
MeshUtils.Splice(eDst, eOrg);
|
||||
|
||||
if (!joiningVertices)
|
||||
{
|
||||
// We split one vertex into two -- the new vertex is eDst->Org.
|
||||
// Make sure the old vertex points to a valid half-edge.
|
||||
MeshUtils.MakeVertex(eDst, eOrg._Org);
|
||||
eOrg._Org._anEdge = eOrg;
|
||||
}
|
||||
if (!joiningLoops)
|
||||
{
|
||||
// We split one loop into two -- the new loop is eDst->Lface.
|
||||
// Make sure the old face points to a valid half-edge.
|
||||
MeshUtils.MakeFace(eDst, eOrg._Lface);
|
||||
eOrg._Lface._anEdge = eOrg;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the edge eDel. There are several cases:
|
||||
/// if (eDel->Lface != eDel->Rface), we join two loops into one; the loop
|
||||
/// eDel->Lface is deleted. Otherwise, we are splitting one loop into two;
|
||||
/// the newly created loop will contain eDel->Dst. If the deletion of eDel
|
||||
/// would create isolated vertices, those are deleted as well.
|
||||
/// </summary>
|
||||
public void Delete(MeshUtils.Edge eDel)
|
||||
{
|
||||
var eDelSym = eDel._Sym;
|
||||
|
||||
// First step: disconnect the origin vertex eDel->Org. We make all
|
||||
// changes to get a consistent mesh in this "intermediate" state.
|
||||
|
||||
bool joiningLoops = false;
|
||||
if (eDel._Lface != eDel._Rface)
|
||||
{
|
||||
// We are joining two loops into one -- remove the left face
|
||||
joiningLoops = true;
|
||||
MeshUtils.KillFace(eDel._Lface, eDel._Rface);
|
||||
}
|
||||
|
||||
if (eDel._Onext == eDel)
|
||||
{
|
||||
MeshUtils.KillVertex(eDel._Org, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure that eDel->Org and eDel->Rface point to valid half-edges
|
||||
eDel._Rface._anEdge = eDel._Oprev;
|
||||
eDel._Org._anEdge = eDel._Onext;
|
||||
|
||||
MeshUtils.Splice(eDel, eDel._Oprev);
|
||||
|
||||
if (!joiningLoops)
|
||||
{
|
||||
// We are splitting one loop into two -- create a new loop for eDel.
|
||||
MeshUtils.MakeFace(eDel, eDel._Lface);
|
||||
}
|
||||
}
|
||||
|
||||
// Claim: the mesh is now in a consistent state, except that eDel->Org
|
||||
// may have been deleted. Now we disconnect eDel->Dst.
|
||||
|
||||
if (eDelSym._Onext == eDelSym)
|
||||
{
|
||||
MeshUtils.KillVertex(eDelSym._Org, null);
|
||||
MeshUtils.KillFace(eDelSym._Lface, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure that eDel->Dst and eDel->Lface point to valid half-edges
|
||||
eDel._Lface._anEdge = eDelSym._Oprev;
|
||||
eDelSym._Org._anEdge = eDelSym._Onext;
|
||||
MeshUtils.Splice(eDelSym, eDelSym._Oprev);
|
||||
}
|
||||
|
||||
// Any isolated vertices or faces have already been freed.
|
||||
MeshUtils.KillEdge(eDel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new edge such that eNew == eOrg.Lnext and eNew.Dst is a newly created vertex.
|
||||
/// eOrg and eNew will have the same left face.
|
||||
/// </summary>
|
||||
public MeshUtils.Edge AddEdgeVertex(MeshUtils.Edge eOrg)
|
||||
{
|
||||
var eNew = MeshUtils.MakeEdge(eOrg);
|
||||
var eNewSym = eNew._Sym;
|
||||
|
||||
// Connect the new edge appropriately
|
||||
MeshUtils.Splice(eNew, eOrg._Lnext);
|
||||
|
||||
// Set vertex and face information
|
||||
eNew._Org = eOrg._Dst;
|
||||
MeshUtils.MakeVertex(eNewSym, eNew._Org);
|
||||
eNew._Lface = eNewSym._Lface = eOrg._Lface;
|
||||
|
||||
return eNew;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits eOrg into two edges eOrg and eNew such that eNew == eOrg.Lnext.
|
||||
/// The new vertex is eOrg.Dst == eNew.Org.
|
||||
/// eOrg and eNew will have the same left face.
|
||||
/// </summary>
|
||||
public MeshUtils.Edge SplitEdge(MeshUtils.Edge eOrg)
|
||||
{
|
||||
var eTmp = AddEdgeVertex(eOrg);
|
||||
var eNew = eTmp._Sym;
|
||||
|
||||
// Disconnect eOrg from eOrg->Dst and connect it to eNew->Org
|
||||
MeshUtils.Splice(eOrg._Sym, eOrg._Sym._Oprev);
|
||||
MeshUtils.Splice(eOrg._Sym, eNew);
|
||||
|
||||
// Set the vertex and face information
|
||||
eOrg._Dst = eNew._Org;
|
||||
eNew._Dst._anEdge = eNew._Sym; // may have pointed to eOrg->Sym
|
||||
eNew._Rface = eOrg._Rface;
|
||||
eNew._winding = eOrg._winding; // copy old winding information
|
||||
eNew._Sym._winding = eOrg._Sym._winding;
|
||||
|
||||
return eNew;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new edge from eOrg->Dst to eDst->Org, and returns the corresponding half-edge eNew.
|
||||
/// If eOrg->Lface == eDst->Lface, this splits one loop into two,
|
||||
/// and the newly created loop is eNew->Lface. Otherwise, two disjoint
|
||||
/// loops are merged into one, and the loop eDst->Lface is destroyed.
|
||||
///
|
||||
/// If (eOrg == eDst), the new face will have only two edges.
|
||||
/// If (eOrg->Lnext == eDst), the old face is reduced to a single edge.
|
||||
/// If (eOrg->Lnext->Lnext == eDst), the old face is reduced to two edges.
|
||||
/// </summary>
|
||||
public MeshUtils.Edge Connect(MeshUtils.Edge eOrg, MeshUtils.Edge eDst)
|
||||
{
|
||||
var eNew = MeshUtils.MakeEdge(eOrg);
|
||||
var eNewSym = eNew._Sym;
|
||||
|
||||
bool joiningLoops = false;
|
||||
if (eDst._Lface != eOrg._Lface)
|
||||
{
|
||||
// We are connecting two disjoint loops -- destroy eDst->Lface
|
||||
joiningLoops = true;
|
||||
MeshUtils.KillFace(eDst._Lface, eOrg._Lface);
|
||||
}
|
||||
|
||||
// Connect the new edge appropriately
|
||||
MeshUtils.Splice(eNew, eOrg._Lnext);
|
||||
MeshUtils.Splice(eNewSym, eDst);
|
||||
|
||||
// Set the vertex and face information
|
||||
eNew._Org = eOrg._Dst;
|
||||
eNewSym._Org = eDst._Org;
|
||||
eNew._Lface = eNewSym._Lface = eOrg._Lface;
|
||||
|
||||
// Make sure the old face points to a valid half-edge
|
||||
eOrg._Lface._anEdge = eNewSym;
|
||||
|
||||
if (!joiningLoops)
|
||||
{
|
||||
MeshUtils.MakeFace(eNew, eOrg._Lface);
|
||||
}
|
||||
|
||||
return eNew;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Destroys a face and removes it from the global face list. All edges of
|
||||
/// fZap will have a NULL pointer as their left face. Any edges which
|
||||
/// also have a NULL pointer as their right face are deleted entirely
|
||||
/// (along with any isolated vertices this produces).
|
||||
/// An entire mesh can be deleted by zapping its faces, one at a time,
|
||||
/// in any order. Zapped faces cannot be used in further mesh operations!
|
||||
/// </summary>
|
||||
public void ZapFace(MeshUtils.Face fZap)
|
||||
{
|
||||
var eStart = fZap._anEdge;
|
||||
|
||||
// walk around face, deleting edges whose right face is also NULL
|
||||
var eNext = eStart._Lnext;
|
||||
MeshUtils.Edge e, eSym;
|
||||
do {
|
||||
e = eNext;
|
||||
eNext = e._Lnext;
|
||||
|
||||
e._Lface = null;
|
||||
if (e._Rface == null)
|
||||
{
|
||||
// delete the edge -- see TESSmeshDelete above
|
||||
|
||||
if (e._Onext == e)
|
||||
{
|
||||
MeshUtils.KillVertex(e._Org, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure that e._Org points to a valid half-edge
|
||||
e._Org._anEdge = e._Onext;
|
||||
MeshUtils.Splice(e, e._Oprev);
|
||||
}
|
||||
eSym = e._Sym;
|
||||
if (eSym._Onext == eSym)
|
||||
{
|
||||
MeshUtils.KillVertex(eSym._Org, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Make sure that eSym._Org points to a valid half-edge
|
||||
eSym._Org._anEdge = eSym._Onext;
|
||||
MeshUtils.Splice(eSym, eSym._Oprev);
|
||||
}
|
||||
MeshUtils.KillEdge(e);
|
||||
}
|
||||
} while (e != eStart);
|
||||
|
||||
/* delete from circular doubly-linked list */
|
||||
var fPrev = fZap._prev;
|
||||
var fNext = fZap._next;
|
||||
fNext._prev = fPrev;
|
||||
fPrev._next = fNext;
|
||||
|
||||
fZap.Free();
|
||||
}
|
||||
|
||||
public void MergeConvexFaces(int maxVertsPerFace)
|
||||
{
|
||||
for (var f = _fHead._next; f != _fHead; f = f._next)
|
||||
{
|
||||
// Skip faces which are outside the result
|
||||
if (!f._inside)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var eCur = f._anEdge;
|
||||
var vStart = eCur._Org;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var eNext = eCur._Lnext;
|
||||
var eSym = eCur._Sym;
|
||||
|
||||
if (eSym != null && eSym._Lface != null && eSym._Lface._inside)
|
||||
{
|
||||
// Try to merge the neighbour faces if the resulting polygons
|
||||
// does not exceed maximum number of vertices.
|
||||
int curNv = f.VertsCount;
|
||||
int symNv = eSym._Lface.VertsCount;
|
||||
if ((curNv + symNv - 2) <= maxVertsPerFace)
|
||||
{
|
||||
// Merge if the resulting poly is convex.
|
||||
if (Geom.VertCCW(eCur._Lprev._Org, eCur._Org, eSym._Lnext._Lnext._Org) &&
|
||||
Geom.VertCCW(eSym._Lprev._Org, eSym._Org, eCur._Lnext._Lnext._Org))
|
||||
{
|
||||
eNext = eSym._Lnext;
|
||||
Delete(eSym);
|
||||
eCur = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (eCur != null && eCur._Lnext._Org == vStart)
|
||||
break;
|
||||
|
||||
// Continue to next edge.
|
||||
eCur = eNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
public void Check()
|
||||
{
|
||||
MeshUtils.Edge e;
|
||||
|
||||
MeshUtils.Face fPrev = _fHead, f;
|
||||
for (fPrev = _fHead; (f = fPrev._next) != _fHead; fPrev = f)
|
||||
{
|
||||
e = f._anEdge;
|
||||
do {
|
||||
Debug.Assert(e._Sym != e);
|
||||
Debug.Assert(e._Sym._Sym == e);
|
||||
Debug.Assert(e._Lnext._Onext._Sym == e);
|
||||
Debug.Assert(e._Onext._Sym._Lnext == e);
|
||||
Debug.Assert(e._Lface == f);
|
||||
e = e._Lnext;
|
||||
} while (e != f._anEdge);
|
||||
}
|
||||
Debug.Assert(f._prev == fPrev && f._anEdge == null);
|
||||
|
||||
MeshUtils.Vertex vPrev = _vHead, v;
|
||||
for (vPrev = _vHead; (v = vPrev._next) != _vHead; vPrev = v)
|
||||
{
|
||||
Debug.Assert(v._prev == vPrev);
|
||||
e = v._anEdge;
|
||||
do
|
||||
{
|
||||
Debug.Assert(e._Sym != e);
|
||||
Debug.Assert(e._Sym._Sym == e);
|
||||
Debug.Assert(e._Lnext._Onext._Sym == e);
|
||||
Debug.Assert(e._Onext._Sym._Lnext == e);
|
||||
Debug.Assert(e._Org == v);
|
||||
e = e._Onext;
|
||||
} while (e != v._anEdge);
|
||||
}
|
||||
Debug.Assert(v._prev == vPrev && v._anEdge == null);
|
||||
|
||||
MeshUtils.Edge ePrev = _eHead;
|
||||
for (ePrev = _eHead; (e = ePrev._next) != _eHead; ePrev = e)
|
||||
{
|
||||
Debug.Assert(e._Sym._next == ePrev._Sym);
|
||||
Debug.Assert(e._Sym != e);
|
||||
Debug.Assert(e._Sym._Sym == e);
|
||||
Debug.Assert(e._Org != null);
|
||||
Debug.Assert(e._Dst != null);
|
||||
Debug.Assert(e._Lnext._Onext._Sym == e);
|
||||
Debug.Assert(e._Onext._Sym._Lnext == e);
|
||||
}
|
||||
Debug.Assert(e._Sym._next == ePrev._Sym
|
||||
&& e._Sym == _eHeadSym
|
||||
&& e._Sym._Sym == e
|
||||
&& e._Org == null && e._Dst == null
|
||||
&& e._Lface == null && e._Rface == null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
465
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/MeshUtils.cs
vendored
Normal file
@@ -0,0 +1,465 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
using Real = System.Single;
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal struct Vec3
|
||||
{
|
||||
public readonly static Vec3 Zero = new Vec3();
|
||||
|
||||
public Real X, Y, Z;
|
||||
|
||||
public Real this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (index == 0) return X;
|
||||
if (index == 1) return Y;
|
||||
if (index == 2) return Z;
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
set
|
||||
{
|
||||
if (index == 0) X = value;
|
||||
else if (index == 1) Y = value;
|
||||
else if (index == 2) Z = value;
|
||||
else throw new IndexOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Sub(ref Vec3 lhs, ref Vec3 rhs, out Vec3 result)
|
||||
{
|
||||
result.X = lhs.X - rhs.X;
|
||||
result.Y = lhs.Y - rhs.Y;
|
||||
result.Z = lhs.Z - rhs.Z;
|
||||
}
|
||||
|
||||
public static void Neg(ref Vec3 v)
|
||||
{
|
||||
v.X = -v.X;
|
||||
v.Y = -v.Y;
|
||||
v.Z = -v.Z;
|
||||
}
|
||||
|
||||
public static void Dot(ref Vec3 u, ref Vec3 v, out Real dot)
|
||||
{
|
||||
dot = u.X * v.X + u.Y * v.Y + u.Z * v.Z;
|
||||
}
|
||||
|
||||
public static void Normalize(ref Vec3 v)
|
||||
{
|
||||
var len = v.X * v.X + v.Y * v.Y + v.Z * v.Z;
|
||||
Debug.Assert(len >= 0.0f);
|
||||
len = 1.0f / (Real)Math.Sqrt(len);
|
||||
v.X *= len;
|
||||
v.Y *= len;
|
||||
v.Z *= len;
|
||||
}
|
||||
|
||||
public static int LongAxis(ref Vec3 v)
|
||||
{
|
||||
int i = 0;
|
||||
if (Math.Abs(v.Y) > Math.Abs(v.X)) i = 1;
|
||||
if (Math.Abs(v.Z) > Math.Abs(i == 0 ? v.X : v.Y)) i = 2;
|
||||
return i;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}, {1}, {2}", X, Y, Z);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class MeshUtils
|
||||
{
|
||||
public const int Undef = ~0;
|
||||
|
||||
public abstract class Pooled<T> where T : Pooled<T>, new()
|
||||
{
|
||||
private static Stack<T> _stack;
|
||||
public abstract void Reset();
|
||||
public virtual void OnFree() { }
|
||||
|
||||
public static T Create()
|
||||
{
|
||||
if (_stack != null && _stack.Count > 0)
|
||||
{
|
||||
return _stack.Pop();
|
||||
}
|
||||
return new T();
|
||||
}
|
||||
|
||||
public void Free()
|
||||
{
|
||||
OnFree();
|
||||
Reset();
|
||||
if (_stack == null)
|
||||
{
|
||||
_stack = new Stack<T>();
|
||||
}
|
||||
_stack.Push((T)this);
|
||||
}
|
||||
}
|
||||
|
||||
public class Vertex : Pooled<Vertex>
|
||||
{
|
||||
internal Vertex _prev, _next;
|
||||
internal Edge _anEdge;
|
||||
|
||||
internal Vec3 _coords;
|
||||
internal Real _s, _t;
|
||||
internal PQHandle _pqHandle;
|
||||
internal int _n;
|
||||
internal object _data;
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_prev = _next = null;
|
||||
_anEdge = null;
|
||||
_coords = Vec3.Zero;
|
||||
_s = 0;
|
||||
_t = 0;
|
||||
_pqHandle = new PQHandle();
|
||||
_n = 0;
|
||||
_data = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Face : Pooled<Face>
|
||||
{
|
||||
internal Face _prev, _next;
|
||||
internal Edge _anEdge;
|
||||
|
||||
internal Face _trail;
|
||||
internal int _n;
|
||||
internal bool _marked, _inside;
|
||||
|
||||
internal int VertsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int n = 0;
|
||||
var eCur = _anEdge;
|
||||
do {
|
||||
n++;
|
||||
eCur = eCur._Lnext;
|
||||
} while (eCur != _anEdge);
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_prev = _next = null;
|
||||
_anEdge = null;
|
||||
_trail = null;
|
||||
_n = 0;
|
||||
_marked = false;
|
||||
_inside = false;
|
||||
}
|
||||
}
|
||||
|
||||
public struct EdgePair
|
||||
{
|
||||
internal Edge _e, _eSym;
|
||||
|
||||
public static EdgePair Create()
|
||||
{
|
||||
var pair = new MeshUtils.EdgePair();
|
||||
pair._e = MeshUtils.Edge.Create();
|
||||
pair._e._pair = pair;
|
||||
pair._eSym = MeshUtils.Edge.Create();
|
||||
pair._eSym._pair = pair;
|
||||
return pair;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_e = _eSym = null;
|
||||
}
|
||||
}
|
||||
|
||||
public class Edge : Pooled<Edge>
|
||||
{
|
||||
internal EdgePair _pair;
|
||||
internal Edge _next, _Sym, _Onext, _Lnext;
|
||||
internal Vertex _Org;
|
||||
internal Face _Lface;
|
||||
internal Tess.ActiveRegion _activeRegion;
|
||||
internal int _winding;
|
||||
|
||||
internal Face _Rface { get { return _Sym._Lface; } set { _Sym._Lface = value; } }
|
||||
internal Vertex _Dst { get { return _Sym._Org; } set { _Sym._Org = value; } }
|
||||
|
||||
internal Edge _Oprev { get { return _Sym._Lnext; } set { _Sym._Lnext = value; } }
|
||||
internal Edge _Lprev { get { return _Onext._Sym; } set { _Onext._Sym = value; } }
|
||||
internal Edge _Dprev { get { return _Lnext._Sym; } set { _Lnext._Sym = value; } }
|
||||
internal Edge _Rprev { get { return _Sym._Onext; } set { _Sym._Onext = value; } }
|
||||
internal Edge _Dnext { get { return _Rprev._Sym; } set { _Rprev._Sym = value; } }
|
||||
internal Edge _Rnext { get { return _Oprev._Sym; } set { _Oprev._Sym = value; } }
|
||||
|
||||
internal static void EnsureFirst(ref Edge e)
|
||||
{
|
||||
if (e == e._pair._eSym)
|
||||
{
|
||||
e = e._Sym;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
_pair.Reset();
|
||||
_next = _Sym = _Onext = _Lnext = null;
|
||||
_Org = null;
|
||||
_Lface = null;
|
||||
_activeRegion = null;
|
||||
_winding = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MakeEdge creates a new pair of half-edges which form their own loop.
|
||||
/// No vertex or face structures are allocated, but these must be assigned
|
||||
/// before the current edge operation is completed.
|
||||
/// </summary>
|
||||
public static Edge MakeEdge(Edge eNext)
|
||||
{
|
||||
Debug.Assert(eNext != null);
|
||||
|
||||
var pair = EdgePair.Create();
|
||||
var e = pair._e;
|
||||
var eSym = pair._eSym;
|
||||
|
||||
// Make sure eNext points to the first edge of the edge pair
|
||||
Edge.EnsureFirst(ref eNext);
|
||||
|
||||
// Insert in circular doubly-linked list before eNext.
|
||||
// Note that the prev pointer is stored in Sym->next.
|
||||
var ePrev = eNext._Sym._next;
|
||||
eSym._next = ePrev;
|
||||
ePrev._Sym._next = e;
|
||||
e._next = eNext;
|
||||
eNext._Sym._next = eSym;
|
||||
|
||||
e._Sym = eSym;
|
||||
e._Onext = e;
|
||||
e._Lnext = eSym;
|
||||
e._Org = null;
|
||||
e._Lface = null;
|
||||
e._winding = 0;
|
||||
e._activeRegion = null;
|
||||
|
||||
eSym._Sym = e;
|
||||
eSym._Onext = eSym;
|
||||
eSym._Lnext = e;
|
||||
eSym._Org = null;
|
||||
eSym._Lface = null;
|
||||
eSym._winding = 0;
|
||||
eSym._activeRegion = null;
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splice( a, b ) is best described by the Guibas/Stolfi paper or the
|
||||
/// CS348a notes (see Mesh.cs). Basically it modifies the mesh so that
|
||||
/// a->Onext and b->Onext are exchanged. This can have various effects
|
||||
/// depending on whether a and b belong to different face or vertex rings.
|
||||
/// For more explanation see Mesh.Splice().
|
||||
/// </summary>
|
||||
public static void Splice(Edge a, Edge b)
|
||||
{
|
||||
var aOnext = a._Onext;
|
||||
var bOnext = b._Onext;
|
||||
|
||||
aOnext._Sym._Lnext = b;
|
||||
bOnext._Sym._Lnext = a;
|
||||
a._Onext = bOnext;
|
||||
b._Onext = aOnext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MakeVertex( eOrig, vNext ) attaches a new vertex and makes it the
|
||||
/// origin of all edges in the vertex loop to which eOrig belongs. "vNext" gives
|
||||
/// a place to insert the new vertex in the global vertex list. We insert
|
||||
/// the new vertex *before* vNext so that algorithms which walk the vertex
|
||||
/// list will not see the newly created vertices.
|
||||
/// </summary>
|
||||
public static void MakeVertex(Edge eOrig, Vertex vNext)
|
||||
{
|
||||
var vNew = MeshUtils.Vertex.Create();
|
||||
|
||||
// insert in circular doubly-linked list before vNext
|
||||
var vPrev = vNext._prev;
|
||||
vNew._prev = vPrev;
|
||||
vPrev._next = vNew;
|
||||
vNew._next = vNext;
|
||||
vNext._prev = vNew;
|
||||
|
||||
vNew._anEdge = eOrig;
|
||||
// leave coords, s, t undefined
|
||||
|
||||
// fix other edges on this vertex loop
|
||||
var e = eOrig;
|
||||
do {
|
||||
e._Org = vNew;
|
||||
e = e._Onext;
|
||||
} while (e != eOrig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MakeFace( eOrig, fNext ) attaches a new face and makes it the left
|
||||
/// face of all edges in the face loop to which eOrig belongs. "fNext" gives
|
||||
/// a place to insert the new face in the global face list. We insert
|
||||
/// the new face *before* fNext so that algorithms which walk the face
|
||||
/// list will not see the newly created faces.
|
||||
/// </summary>
|
||||
public static void MakeFace(Edge eOrig, Face fNext)
|
||||
{
|
||||
var fNew = MeshUtils.Face.Create();
|
||||
|
||||
// insert in circular doubly-linked list before fNext
|
||||
var fPrev = fNext._prev;
|
||||
fNew._prev = fPrev;
|
||||
fPrev._next = fNew;
|
||||
fNew._next = fNext;
|
||||
fNext._prev = fNew;
|
||||
|
||||
fNew._anEdge = eOrig;
|
||||
fNew._trail = null;
|
||||
fNew._marked = false;
|
||||
|
||||
// The new face is marked "inside" if the old one was. This is a
|
||||
// convenience for the common case where a face has been split in two.
|
||||
fNew._inside = fNext._inside;
|
||||
|
||||
// fix other edges on this face loop
|
||||
var e = eOrig;
|
||||
do {
|
||||
e._Lface = fNew;
|
||||
e = e._Lnext;
|
||||
} while (e != eOrig);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KillEdge( eDel ) destroys an edge (the half-edges eDel and eDel->Sym),
|
||||
/// and removes from the global edge list.
|
||||
/// </summary>
|
||||
public static void KillEdge(Edge eDel)
|
||||
{
|
||||
// Half-edges are allocated in pairs, see EdgePair above
|
||||
Edge.EnsureFirst(ref eDel);
|
||||
|
||||
// delete from circular doubly-linked list
|
||||
var eNext = eDel._next;
|
||||
var ePrev = eDel._Sym._next;
|
||||
eNext._Sym._next = ePrev;
|
||||
ePrev._Sym._next = eNext;
|
||||
|
||||
eDel.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KillVertex( vDel ) destroys a vertex and removes it from the global
|
||||
/// vertex list. It updates the vertex loop to point to a given new vertex.
|
||||
/// </summary>
|
||||
public static void KillVertex(Vertex vDel, Vertex newOrg)
|
||||
{
|
||||
var eStart = vDel._anEdge;
|
||||
|
||||
// change the origin of all affected edges
|
||||
var e = eStart;
|
||||
do {
|
||||
e._Org = newOrg;
|
||||
e = e._Onext;
|
||||
} while (e != eStart);
|
||||
|
||||
// delete from circular doubly-linked list
|
||||
var vPrev = vDel._prev;
|
||||
var vNext = vDel._next;
|
||||
vNext._prev = vPrev;
|
||||
vPrev._next = vNext;
|
||||
|
||||
vDel.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// KillFace( fDel ) destroys a face and removes it from the global face
|
||||
/// list. It updates the face loop to point to a given new face.
|
||||
/// </summary>
|
||||
public static void KillFace(Face fDel, Face newLFace)
|
||||
{
|
||||
var eStart = fDel._anEdge;
|
||||
|
||||
// change the left face of all affected edges
|
||||
var e = eStart;
|
||||
do {
|
||||
e._Lface = newLFace;
|
||||
e = e._Lnext;
|
||||
} while (e != eStart);
|
||||
|
||||
// delete from circular doubly-linked list
|
||||
var fPrev = fDel._prev;
|
||||
var fNext = fDel._next;
|
||||
fNext._prev = fPrev;
|
||||
fPrev._next = fNext;
|
||||
|
||||
fDel.Free();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return signed area of face.
|
||||
/// </summary>
|
||||
public static Real FaceArea(Face f)
|
||||
{
|
||||
Real area = 0;
|
||||
var e = f._anEdge;
|
||||
do
|
||||
{
|
||||
area += (e._Org._s - e._Dst._s) * (e._Org._t + e._Dst._t);
|
||||
e = e._Lnext;
|
||||
} while (e != f._anEdge);
|
||||
return area;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
246
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/PriorityHeap.cs
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal struct PQHandle
|
||||
{
|
||||
public static readonly int Invalid = 0x0fffffff;
|
||||
internal int _handle;
|
||||
}
|
||||
|
||||
internal class PriorityHeap<TValue> where TValue : class
|
||||
{
|
||||
public delegate bool LessOrEqual(TValue lhs, TValue rhs);
|
||||
|
||||
protected class HandleElem
|
||||
{
|
||||
internal TValue _key;
|
||||
internal int _node;
|
||||
}
|
||||
|
||||
private LessOrEqual _leq;
|
||||
private int[] _nodes;
|
||||
private HandleElem[] _handles;
|
||||
private int _size, _max;
|
||||
private int _freeList;
|
||||
private bool _initialized;
|
||||
|
||||
public bool Empty { get { return _size == 0; } }
|
||||
|
||||
public PriorityHeap(int initialSize, LessOrEqual leq)
|
||||
{
|
||||
_leq = leq;
|
||||
|
||||
_nodes = new int[initialSize + 1];
|
||||
_handles = new HandleElem[initialSize + 1];
|
||||
|
||||
_size = 0;
|
||||
_max = initialSize;
|
||||
_freeList = 0;
|
||||
_initialized = false;
|
||||
|
||||
_nodes[1] = 1;
|
||||
_handles[1] = new HandleElem { _key = null };
|
||||
}
|
||||
|
||||
private void FloatDown(int curr)
|
||||
{
|
||||
int child;
|
||||
int hCurr, hChild;
|
||||
|
||||
hCurr = _nodes[curr];
|
||||
while (true)
|
||||
{
|
||||
child = curr << 1;
|
||||
if (child < _size && _leq(_handles[_nodes[child + 1]]._key, _handles[_nodes[child]]._key))
|
||||
{
|
||||
++child;
|
||||
}
|
||||
|
||||
Debug.Assert(child <= _max);
|
||||
|
||||
hChild = _nodes[child];
|
||||
if (child > _size || _leq(_handles[hCurr]._key, _handles[hChild]._key))
|
||||
{
|
||||
_nodes[curr] = hCurr;
|
||||
_handles[hCurr]._node = curr;
|
||||
break;
|
||||
}
|
||||
|
||||
_nodes[curr] = hChild;
|
||||
_handles[hChild]._node = curr;
|
||||
curr = child;
|
||||
}
|
||||
}
|
||||
|
||||
private void FloatUp(int curr)
|
||||
{
|
||||
int parent;
|
||||
int hCurr, hParent;
|
||||
|
||||
hCurr = _nodes[curr];
|
||||
while (true)
|
||||
{
|
||||
parent = curr >> 1;
|
||||
hParent = _nodes[parent];
|
||||
if (parent == 0 || _leq(_handles[hParent]._key, _handles[hCurr]._key))
|
||||
{
|
||||
_nodes[curr] = hCurr;
|
||||
_handles[hCurr]._node = curr;
|
||||
break;
|
||||
}
|
||||
_nodes[curr] = hParent;
|
||||
_handles[hParent]._node = curr;
|
||||
curr = parent;
|
||||
}
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
for (int i = _size; i >= 1; --i)
|
||||
{
|
||||
FloatDown(i);
|
||||
}
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public PQHandle Insert(TValue value)
|
||||
{
|
||||
int curr = ++_size;
|
||||
if ((curr * 2) > _max)
|
||||
{
|
||||
_max <<= 1;
|
||||
Array.Resize(ref _nodes, _max + 1);
|
||||
Array.Resize(ref _handles, _max + 1);
|
||||
}
|
||||
|
||||
int free;
|
||||
if (_freeList == 0)
|
||||
{
|
||||
free = curr;
|
||||
}
|
||||
else
|
||||
{
|
||||
free = _freeList;
|
||||
_freeList = _handles[free]._node;
|
||||
}
|
||||
|
||||
_nodes[curr] = free;
|
||||
if (_handles[free] == null)
|
||||
{
|
||||
_handles[free] = new HandleElem { _key = value, _node = curr };
|
||||
}
|
||||
else
|
||||
{
|
||||
_handles[free]._node = curr;
|
||||
_handles[free]._key = value;
|
||||
}
|
||||
|
||||
if (_initialized)
|
||||
{
|
||||
FloatUp(curr);
|
||||
}
|
||||
|
||||
Debug.Assert(free != PQHandle.Invalid);
|
||||
return new PQHandle { _handle = free };
|
||||
}
|
||||
|
||||
public TValue ExtractMin()
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int hMin = _nodes[1];
|
||||
TValue min = _handles[hMin]._key;
|
||||
|
||||
if (_size > 0)
|
||||
{
|
||||
_nodes[1] = _nodes[_size];
|
||||
_handles[_nodes[1]]._node = 1;
|
||||
|
||||
_handles[hMin]._key = null;
|
||||
_handles[hMin]._node = _freeList;
|
||||
_freeList = hMin;
|
||||
|
||||
if (--_size > 0)
|
||||
{
|
||||
FloatDown(1);
|
||||
}
|
||||
}
|
||||
|
||||
return min;
|
||||
}
|
||||
|
||||
public TValue Minimum()
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
return _handles[_nodes[1]]._key;
|
||||
}
|
||||
|
||||
public void Remove(PQHandle handle)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int hCurr = handle._handle;
|
||||
Debug.Assert(hCurr >= 1 && hCurr <= _max && _handles[hCurr]._key != null);
|
||||
|
||||
int curr = _handles[hCurr]._node;
|
||||
_nodes[curr] = _nodes[_size];
|
||||
_handles[_nodes[curr]]._node = curr;
|
||||
|
||||
if (curr <= --_size)
|
||||
{
|
||||
if (curr <= 1 || _leq(_handles[_nodes[curr >> 1]]._key, _handles[_nodes[curr]]._key))
|
||||
{
|
||||
FloatDown(curr);
|
||||
}
|
||||
else
|
||||
{
|
||||
FloatUp(curr);
|
||||
}
|
||||
}
|
||||
|
||||
_handles[hCurr]._key = null;
|
||||
_handles[hCurr]._node = _freeList;
|
||||
_freeList = hCurr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
231
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/PriorityQueue.cs
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal class PriorityQueue<TValue> where TValue : class
|
||||
{
|
||||
private PriorityHeap<TValue>.LessOrEqual _leq;
|
||||
private PriorityHeap<TValue> _heap;
|
||||
private TValue[] _keys;
|
||||
private int[] _order;
|
||||
|
||||
private int _size, _max;
|
||||
private bool _initialized;
|
||||
|
||||
public bool Empty { get { return _size == 0 && _heap.Empty; } }
|
||||
|
||||
public PriorityQueue(int initialSize, PriorityHeap<TValue>.LessOrEqual leq)
|
||||
{
|
||||
_leq = leq;
|
||||
_heap = new PriorityHeap<TValue>(initialSize, leq);
|
||||
|
||||
_keys = new TValue[initialSize];
|
||||
|
||||
_size = 0;
|
||||
_max = initialSize;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
class StackItem
|
||||
{
|
||||
internal int p, r;
|
||||
};
|
||||
|
||||
static void Swap(ref int a, ref int b)
|
||||
{
|
||||
int tmp = a;
|
||||
a = b;
|
||||
b = tmp;
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
var stack = new Stack<StackItem>();
|
||||
int p, r, i, j, piv;
|
||||
uint seed = 2016473283;
|
||||
|
||||
p = 0;
|
||||
r = _size - 1;
|
||||
_order = new int[_size + 1];
|
||||
for (piv = 0, i = p; i <= r; ++piv, ++i)
|
||||
{
|
||||
_order[i] = piv;
|
||||
}
|
||||
|
||||
stack.Push(new StackItem { p = p, r = r });
|
||||
while (stack.Count > 0)
|
||||
{
|
||||
var top = stack.Pop();
|
||||
p = top.p;
|
||||
r = top.r;
|
||||
|
||||
while (r > p + 10)
|
||||
{
|
||||
seed = seed * 1539415821 + 1;
|
||||
i = p + (int)(seed % (r - p + 1));
|
||||
piv = _order[i];
|
||||
_order[i] = _order[p];
|
||||
_order[p] = piv;
|
||||
i = p - 1;
|
||||
j = r + 1;
|
||||
do {
|
||||
do { ++i; } while (!_leq(_keys[_order[i]], _keys[piv]));
|
||||
do { --j; } while (!_leq(_keys[piv], _keys[_order[j]]));
|
||||
Swap(ref _order[i], ref _order[j]);
|
||||
} while (i < j);
|
||||
Swap(ref _order[i], ref _order[j]);
|
||||
if (i - p < r - j)
|
||||
{
|
||||
stack.Push(new StackItem { p = j + 1, r = r });
|
||||
r = i - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
stack.Push(new StackItem { p = p, r = i - 1 });
|
||||
p = j + 1;
|
||||
}
|
||||
}
|
||||
for (i = p + 1; i <= r; ++i)
|
||||
{
|
||||
piv = _order[i];
|
||||
for (j = i; j > p && !_leq(_keys[piv], _keys[_order[j - 1]]); --j)
|
||||
{
|
||||
_order[j] = _order[j - 1];
|
||||
}
|
||||
_order[j] = piv;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
p = 0;
|
||||
r = _size - 1;
|
||||
for (i = p; i < r; ++i)
|
||||
{
|
||||
Debug.Assert(_leq(_keys[_order[i + 1]], _keys[_order[i]]), "Wrong sort");
|
||||
}
|
||||
#endif
|
||||
|
||||
_max = _size;
|
||||
_initialized = true;
|
||||
_heap.Init();
|
||||
}
|
||||
|
||||
public PQHandle Insert(TValue value)
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return _heap.Insert(value);
|
||||
}
|
||||
|
||||
int curr = _size;
|
||||
if (++_size >= _max)
|
||||
{
|
||||
_max <<= 1;
|
||||
Array.Resize(ref _keys, _max);
|
||||
}
|
||||
|
||||
_keys[curr] = value;
|
||||
return new PQHandle { _handle = -(curr + 1) };
|
||||
}
|
||||
|
||||
public TValue ExtractMin()
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
if (_size == 0)
|
||||
{
|
||||
return _heap.ExtractMin();
|
||||
}
|
||||
TValue sortMin = _keys[_order[_size - 1]];
|
||||
if (!_heap.Empty)
|
||||
{
|
||||
TValue heapMin = _heap.Minimum();
|
||||
if (_leq(heapMin, sortMin))
|
||||
return _heap.ExtractMin();
|
||||
}
|
||||
do {
|
||||
--_size;
|
||||
} while (_size > 0 && _keys[_order[_size - 1]] == null);
|
||||
|
||||
return sortMin;
|
||||
}
|
||||
|
||||
public TValue Minimum()
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
if (_size == 0)
|
||||
{
|
||||
return _heap.Minimum();
|
||||
}
|
||||
TValue sortMin = _keys[_order[_size - 1]];
|
||||
if (!_heap.Empty)
|
||||
{
|
||||
TValue heapMin = _heap.Minimum();
|
||||
if (_leq(heapMin, sortMin))
|
||||
return heapMin;
|
||||
}
|
||||
return sortMin;
|
||||
}
|
||||
|
||||
public void Remove(PQHandle handle)
|
||||
{
|
||||
Debug.Assert(_initialized);
|
||||
|
||||
int curr = handle._handle;
|
||||
if (curr >= 0)
|
||||
{
|
||||
_heap.Remove(handle);
|
||||
return;
|
||||
}
|
||||
curr = -(curr + 1);
|
||||
Debug.Assert(curr < _max && _keys[curr] != null);
|
||||
|
||||
_keys[curr] = null;
|
||||
while (_size > 0 && _keys[_order[_size - 1]] == null)
|
||||
{
|
||||
--_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
1238
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/Sweep.cs
vendored
Normal file
746
Library/PackageCache/com.unity.2d.spriteshape@6.0.0/Runtime/External/LibTessDotNet/Tess.cs
vendored
Normal file
@@ -0,0 +1,746 @@
|
||||
/*
|
||||
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
** Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
** All Rights Reserved.
|
||||
**
|
||||
** Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
** of this software and associated documentation files (the "Software"), to deal
|
||||
** in the Software without restriction, including without limitation the rights
|
||||
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
** of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
** subject to the following conditions:
|
||||
**
|
||||
** The above copyright notice including the dates of first publication and either this
|
||||
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
** included in all copies or substantial portions of the Software.
|
||||
**
|
||||
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
** OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
**
|
||||
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
** be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
** this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
*/
|
||||
/*
|
||||
** Original Author: Eric Veach, July 1994.
|
||||
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
|
||||
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.SpriteShape.External
|
||||
{
|
||||
|
||||
using Real = System.Single;
|
||||
namespace LibTessDotNet
|
||||
{
|
||||
internal enum WindingRule
|
||||
{
|
||||
EvenOdd,
|
||||
NonZero,
|
||||
Positive,
|
||||
Negative,
|
||||
AbsGeqTwo
|
||||
}
|
||||
|
||||
internal enum ElementType
|
||||
{
|
||||
Polygons,
|
||||
ConnectedPolygons,
|
||||
BoundaryContours
|
||||
}
|
||||
|
||||
internal enum ContourOrientation
|
||||
{
|
||||
Original,
|
||||
Clockwise,
|
||||
CounterClockwise
|
||||
}
|
||||
|
||||
internal struct ContourVertex
|
||||
{
|
||||
public Vec3 Position;
|
||||
public object Data;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0}, {1}", Position, Data);
|
||||
}
|
||||
}
|
||||
|
||||
internal delegate object CombineCallback(Vec3 position, object[] data, Real[] weights);
|
||||
|
||||
internal partial class Tess
|
||||
{
|
||||
private Mesh _mesh;
|
||||
private Vec3 _normal;
|
||||
private Vec3 _sUnit;
|
||||
private Vec3 _tUnit;
|
||||
|
||||
private Real _bminX, _bminY, _bmaxX, _bmaxY;
|
||||
|
||||
private WindingRule _windingRule;
|
||||
|
||||
private Dict<ActiveRegion> _dict;
|
||||
private PriorityQueue<MeshUtils.Vertex> _pq;
|
||||
private MeshUtils.Vertex _event;
|
||||
|
||||
private CombineCallback _combineCallback;
|
||||
|
||||
private ContourVertex[] _vertices;
|
||||
private int _vertexCount;
|
||||
private int[] _elements;
|
||||
private int _elementCount;
|
||||
|
||||
public Vec3 Normal { get { return _normal; } set { _normal = value; } }
|
||||
|
||||
public Real SUnitX = 1;
|
||||
public Real SUnitY = 0;
|
||||
public Real SentinelCoord = 4e30f;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will remove empty (zero area) polygons.
|
||||
/// </summary>
|
||||
public bool NoEmptyPolygons = false;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will use pooling to reduce GC (compare performance with/without, can vary wildly).
|
||||
/// </summary>
|
||||
public bool UsePooling = false;
|
||||
|
||||
public ContourVertex[] Vertices { get { return _vertices; } }
|
||||
public int VertexCount { get { return _vertexCount; } }
|
||||
|
||||
public int[] Elements { get { return _elements; } }
|
||||
public int ElementCount { get { return _elementCount; } }
|
||||
|
||||
public Tess()
|
||||
{
|
||||
_normal = Vec3.Zero;
|
||||
_bminX = _bminY = _bmaxX = _bmaxY = 0;
|
||||
|
||||
_windingRule = WindingRule.EvenOdd;
|
||||
_mesh = null;
|
||||
|
||||
_vertices = null;
|
||||
_vertexCount = 0;
|
||||
_elements = null;
|
||||
_elementCount = 0;
|
||||
}
|
||||
|
||||
private void ComputeNormal(ref Vec3 norm)
|
||||
{
|
||||
var v = _mesh._vHead._next;
|
||||
|
||||
var minVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z };
|
||||
var minVert = new MeshUtils.Vertex[3] { v, v, v };
|
||||
var maxVal = new Real[3] { v._coords.X, v._coords.Y, v._coords.Z };
|
||||
var maxVert = new MeshUtils.Vertex[3] { v, v, v };
|
||||
|
||||
for (; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
if (v._coords.X < minVal[0]) { minVal[0] = v._coords.X; minVert[0] = v; }
|
||||
if (v._coords.Y < minVal[1]) { minVal[1] = v._coords.Y; minVert[1] = v; }
|
||||
if (v._coords.Z < minVal[2]) { minVal[2] = v._coords.Z; minVert[2] = v; }
|
||||
if (v._coords.X > maxVal[0]) { maxVal[0] = v._coords.X; maxVert[0] = v; }
|
||||
if (v._coords.Y > maxVal[1]) { maxVal[1] = v._coords.Y; maxVert[1] = v; }
|
||||
if (v._coords.Z > maxVal[2]) { maxVal[2] = v._coords.Z; maxVert[2] = v; }
|
||||
}
|
||||
|
||||
// Find two vertices separated by at least 1/sqrt(3) of the maximum
|
||||
// distance between any two vertices
|
||||
int i = 0;
|
||||
if (maxVal[1] - minVal[1] > maxVal[0] - minVal[0]) { i = 1; }
|
||||
if (maxVal[2] - minVal[2] > maxVal[i] - minVal[i]) { i = 2; }
|
||||
if (minVal[i] >= maxVal[i])
|
||||
{
|
||||
// All vertices are the same -- normal doesn't matter
|
||||
norm = new Vec3 { X = 0, Y = 0, Z = 1 };
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for a third vertex which forms the triangle with maximum area
|
||||
// (Length of normal == twice the triangle area)
|
||||
Real maxLen2 = 0, tLen2;
|
||||
var v1 = minVert[i];
|
||||
var v2 = maxVert[i];
|
||||
Vec3 d1, d2, tNorm;
|
||||
Vec3.Sub(ref v1._coords, ref v2._coords, out d1);
|
||||
for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
Vec3.Sub(ref v._coords, ref v2._coords, out d2);
|
||||
tNorm.X = d1.Y * d2.Z - d1.Z * d2.Y;
|
||||
tNorm.Y = d1.Z * d2.X - d1.X * d2.Z;
|
||||
tNorm.Z = d1.X * d2.Y - d1.Y * d2.X;
|
||||
tLen2 = tNorm.X*tNorm.X + tNorm.Y*tNorm.Y + tNorm.Z*tNorm.Z;
|
||||
if (tLen2 > maxLen2)
|
||||
{
|
||||
maxLen2 = tLen2;
|
||||
norm = tNorm;
|
||||
}
|
||||
}
|
||||
|
||||
if (maxLen2 <= 0.0f)
|
||||
{
|
||||
// All points lie on a single line -- any decent normal will do
|
||||
norm = Vec3.Zero;
|
||||
i = Vec3.LongAxis(ref d1);
|
||||
norm[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckOrientation()
|
||||
{
|
||||
// When we compute the normal automatically, we choose the orientation
|
||||
// so that the the sum of the signed areas of all contours is non-negative.
|
||||
Real area = 0.0f;
|
||||
for (var f = _mesh._fHead._next; f != _mesh._fHead; f = f._next)
|
||||
{
|
||||
if (f._anEdge._winding <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
area += MeshUtils.FaceArea(f);
|
||||
}
|
||||
if (area < 0.0f)
|
||||
{
|
||||
// Reverse the orientation by flipping all the t-coordinates
|
||||
for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
v._t = -v._t;
|
||||
}
|
||||
Vec3.Neg(ref _tUnit);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProjectPolygon()
|
||||
{
|
||||
var norm = _normal;
|
||||
|
||||
bool computedNormal = false;
|
||||
if (norm.X == 0.0f && norm.Y == 0.0f && norm.Z == 0.0f)
|
||||
{
|
||||
ComputeNormal(ref norm);
|
||||
_normal = norm;
|
||||
computedNormal = true;
|
||||
}
|
||||
|
||||
int i = Vec3.LongAxis(ref norm);
|
||||
|
||||
_sUnit[i] = 0;
|
||||
_sUnit[(i + 1) % 3] = SUnitX;
|
||||
_sUnit[(i + 2) % 3] = SUnitY;
|
||||
|
||||
_tUnit[i] = 0;
|
||||
_tUnit[(i + 1) % 3] = norm[i] > 0.0f ? -SUnitY : SUnitY;
|
||||
_tUnit[(i + 2) % 3] = norm[i] > 0.0f ? SUnitX : -SUnitX;
|
||||
|
||||
// Project the vertices onto the sweep plane
|
||||
for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
Vec3.Dot(ref v._coords, ref _sUnit, out v._s);
|
||||
Vec3.Dot(ref v._coords, ref _tUnit, out v._t);
|
||||
}
|
||||
if (computedNormal)
|
||||
{
|
||||
CheckOrientation();
|
||||
}
|
||||
|
||||
// Compute ST bounds.
|
||||
bool first = true;
|
||||
for (var v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
if (first)
|
||||
{
|
||||
_bminX = _bmaxX = v._s;
|
||||
_bminY = _bmaxY = v._t;
|
||||
first = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v._s < _bminX) _bminX = v._s;
|
||||
if (v._s > _bmaxX) _bmaxX = v._s;
|
||||
if (v._t < _bminY) _bminY = v._t;
|
||||
if (v._t > _bmaxY) _bmaxY = v._t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TessellateMonoRegion( face ) tessellates a monotone region
|
||||
/// (what else would it do??) The region must consist of a single
|
||||
/// loop of half-edges (see mesh.h) oriented CCW. "Monotone" in this
|
||||
/// case means that any vertical line intersects the interior of the
|
||||
/// region in a single interval.
|
||||
///
|
||||
/// Tessellation consists of adding interior edges (actually pairs of
|
||||
/// half-edges), to split the region into non-overlapping triangles.
|
||||
///
|
||||
/// The basic idea is explained in Preparata and Shamos (which I don't
|
||||
/// have handy right now), although their implementation is more
|
||||
/// complicated than this one. The are two edge chains, an upper chain
|
||||
/// and a lower chain. We process all vertices from both chains in order,
|
||||
/// from right to left.
|
||||
///
|
||||
/// The algorithm ensures that the following invariant holds after each
|
||||
/// vertex is processed: the untessellated region consists of two
|
||||
/// chains, where one chain (say the upper) is a single edge, and
|
||||
/// the other chain is concave. The left vertex of the single edge
|
||||
/// is always to the left of all vertices in the concave chain.
|
||||
///
|
||||
/// Each step consists of adding the rightmost unprocessed vertex to one
|
||||
/// of the two chains, and forming a fan of triangles from the rightmost
|
||||
/// of two chain endpoints. Determining whether we can add each triangle
|
||||
/// to the fan is a simple orientation test. By making the fan as large
|
||||
/// as possible, we restore the invariant (check it yourself).
|
||||
/// </summary>
|
||||
private void TessellateMonoRegion(MeshUtils.Face face)
|
||||
{
|
||||
// All edges are oriented CCW around the boundary of the region.
|
||||
// First, find the half-edge whose origin vertex is rightmost.
|
||||
// Since the sweep goes from left to right, face->anEdge should
|
||||
// be close to the edge we want.
|
||||
var up = face._anEdge;
|
||||
Debug.Assert(up._Lnext != up && up._Lnext._Lnext != up);
|
||||
|
||||
while (Geom.VertLeq(up._Dst, up._Org)) up = up._Lprev;
|
||||
while (Geom.VertLeq(up._Org, up._Dst)) up = up._Lnext;
|
||||
|
||||
var lo = up._Lprev;
|
||||
|
||||
while (up._Lnext != lo)
|
||||
{
|
||||
if (Geom.VertLeq(up._Dst, lo._Org))
|
||||
{
|
||||
// up.Dst is on the left. It is safe to form triangles from lo.Org.
|
||||
// The EdgeGoesLeft test guarantees progress even when some triangles
|
||||
// are CW, given that the upper and lower chains are truly monotone.
|
||||
while (lo._Lnext != up && (Geom.EdgeGoesLeft(lo._Lnext)
|
||||
|| Geom.EdgeSign(lo._Org, lo._Dst, lo._Lnext._Dst) <= 0.0f))
|
||||
{
|
||||
lo = _mesh.Connect(lo._Lnext, lo)._Sym;
|
||||
}
|
||||
lo = lo._Lprev;
|
||||
}
|
||||
else
|
||||
{
|
||||
// lo.Org is on the left. We can make CCW triangles from up.Dst.
|
||||
while (lo._Lnext != up && (Geom.EdgeGoesRight(up._Lprev)
|
||||
|| Geom.EdgeSign(up._Dst, up._Org, up._Lprev._Org) >= 0.0f))
|
||||
{
|
||||
up = _mesh.Connect(up, up._Lprev)._Sym;
|
||||
}
|
||||
up = up._Lnext;
|
||||
}
|
||||
}
|
||||
|
||||
// Now lo.Org == up.Dst == the leftmost vertex. The remaining region
|
||||
// can be tessellated in a fan from this leftmost vertex.
|
||||
Debug.Assert(lo._Lnext != up);
|
||||
while (lo._Lnext._Lnext != up)
|
||||
{
|
||||
lo = _mesh.Connect(lo._Lnext, lo)._Sym;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TessellateInterior( mesh ) tessellates each region of
|
||||
/// the mesh which is marked "inside" the polygon. Each such region
|
||||
/// must be monotone.
|
||||
/// </summary>
|
||||
private void TessellateInterior()
|
||||
{
|
||||
MeshUtils.Face f, next;
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = next)
|
||||
{
|
||||
// Make sure we don't try to tessellate the new triangles.
|
||||
next = f._next;
|
||||
if (f._inside)
|
||||
{
|
||||
TessellateMonoRegion(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DiscardExterior zaps (ie. sets to null) all faces
|
||||
/// which are not marked "inside" the polygon. Since further mesh operations
|
||||
/// on NULL faces are not allowed, the main purpose is to clean up the
|
||||
/// mesh so that exterior loops are not represented in the data structure.
|
||||
/// </summary>
|
||||
private void DiscardExterior()
|
||||
{
|
||||
MeshUtils.Face f, next;
|
||||
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = next)
|
||||
{
|
||||
// Since f will be destroyed, save its next pointer.
|
||||
next = f._next;
|
||||
if( ! f._inside ) {
|
||||
_mesh.ZapFace(f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SetWindingNumber( value, keepOnlyBoundary ) resets the
|
||||
/// winding numbers on all edges so that regions marked "inside" the
|
||||
/// polygon have a winding number of "value", and regions outside
|
||||
/// have a winding number of 0.
|
||||
///
|
||||
/// If keepOnlyBoundary is TRUE, it also deletes all edges which do not
|
||||
/// separate an interior region from an exterior one.
|
||||
/// </summary>
|
||||
private void SetWindingNumber(int value, bool keepOnlyBoundary)
|
||||
{
|
||||
MeshUtils.Edge e, eNext;
|
||||
|
||||
for (e = _mesh._eHead._next; e != _mesh._eHead; e = eNext)
|
||||
{
|
||||
eNext = e._next;
|
||||
if (e._Rface._inside != e._Lface._inside)
|
||||
{
|
||||
|
||||
/* This is a boundary edge (one side is interior, one is exterior). */
|
||||
e._winding = (e._Lface._inside) ? value : -value;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
/* Both regions are interior, or both are exterior. */
|
||||
if (!keepOnlyBoundary)
|
||||
{
|
||||
e._winding = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mesh.Delete(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int GetNeighbourFace(MeshUtils.Edge edge)
|
||||
{
|
||||
if (edge._Rface == null)
|
||||
return MeshUtils.Undef;
|
||||
if (!edge._Rface._inside)
|
||||
return MeshUtils.Undef;
|
||||
return edge._Rface._n;
|
||||
}
|
||||
|
||||
private void OutputPolymesh(ElementType elementType, int polySize)
|
||||
{
|
||||
MeshUtils.Vertex v;
|
||||
MeshUtils.Face f;
|
||||
MeshUtils.Edge edge;
|
||||
int maxFaceCount = 0;
|
||||
int maxVertexCount = 0;
|
||||
int faceVerts, i;
|
||||
|
||||
if (polySize < 3)
|
||||
{
|
||||
polySize = 3;
|
||||
}
|
||||
// Assume that the input data is triangles now.
|
||||
// Try to merge as many polygons as possible
|
||||
if (polySize > 3)
|
||||
{
|
||||
_mesh.MergeConvexFaces(polySize);
|
||||
}
|
||||
|
||||
// Mark unused
|
||||
for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
v._n = MeshUtils.Undef;
|
||||
|
||||
// Create unique IDs for all vertices and faces.
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next)
|
||||
{
|
||||
f._n = MeshUtils.Undef;
|
||||
if (!f._inside) continue;
|
||||
|
||||
if (NoEmptyPolygons)
|
||||
{
|
||||
var area = MeshUtils.FaceArea(f);
|
||||
if (Math.Abs(area) < Real.Epsilon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
edge = f._anEdge;
|
||||
faceVerts = 0;
|
||||
do {
|
||||
v = edge._Org;
|
||||
if (v._n == MeshUtils.Undef)
|
||||
{
|
||||
v._n = maxVertexCount;
|
||||
maxVertexCount++;
|
||||
}
|
||||
faceVerts++;
|
||||
edge = edge._Lnext;
|
||||
}
|
||||
while (edge != f._anEdge);
|
||||
|
||||
Debug.Assert(faceVerts <= polySize);
|
||||
|
||||
f._n = maxFaceCount;
|
||||
++maxFaceCount;
|
||||
}
|
||||
|
||||
_elementCount = maxFaceCount;
|
||||
if (elementType == ElementType.ConnectedPolygons)
|
||||
maxFaceCount *= 2;
|
||||
_elements = new int[maxFaceCount * polySize];
|
||||
|
||||
_vertexCount = maxVertexCount;
|
||||
_vertices = new ContourVertex[_vertexCount];
|
||||
|
||||
// Output vertices.
|
||||
for (v = _mesh._vHead._next; v != _mesh._vHead; v = v._next)
|
||||
{
|
||||
if (v._n != MeshUtils.Undef)
|
||||
{
|
||||
// Store coordinate
|
||||
_vertices[v._n].Position = v._coords;
|
||||
_vertices[v._n].Data = v._data;
|
||||
}
|
||||
}
|
||||
|
||||
// Output indices.
|
||||
int elementIndex = 0;
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next)
|
||||
{
|
||||
if (!f._inside) continue;
|
||||
|
||||
if (NoEmptyPolygons)
|
||||
{
|
||||
var area = MeshUtils.FaceArea(f);
|
||||
if (Math.Abs(area) < Real.Epsilon)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Store polygon
|
||||
edge = f._anEdge;
|
||||
faceVerts = 0;
|
||||
do {
|
||||
v = edge._Org;
|
||||
_elements[elementIndex++] = v._n;
|
||||
faceVerts++;
|
||||
edge = edge._Lnext;
|
||||
} while (edge != f._anEdge);
|
||||
// Fill unused.
|
||||
for (i = faceVerts; i < polySize; ++i)
|
||||
{
|
||||
_elements[elementIndex++] = MeshUtils.Undef;
|
||||
}
|
||||
|
||||
// Store polygon connectivity
|
||||
if (elementType == ElementType.ConnectedPolygons)
|
||||
{
|
||||
edge = f._anEdge;
|
||||
do
|
||||
{
|
||||
_elements[elementIndex++] = GetNeighbourFace(edge);
|
||||
edge = edge._Lnext;
|
||||
} while (edge != f._anEdge);
|
||||
// Fill unused.
|
||||
for (i = faceVerts; i < polySize; ++i)
|
||||
{
|
||||
_elements[elementIndex++] = MeshUtils.Undef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OutputContours()
|
||||
{
|
||||
MeshUtils.Face f;
|
||||
MeshUtils.Edge edge, start;
|
||||
int startVert = 0;
|
||||
int vertCount = 0;
|
||||
|
||||
_vertexCount = 0;
|
||||
_elementCount = 0;
|
||||
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next)
|
||||
{
|
||||
if (!f._inside) continue;
|
||||
|
||||
start = edge = f._anEdge;
|
||||
do
|
||||
{
|
||||
++_vertexCount;
|
||||
edge = edge._Lnext;
|
||||
}
|
||||
while (edge != start);
|
||||
|
||||
++_elementCount;
|
||||
}
|
||||
|
||||
_elements = new int[_elementCount * 2];
|
||||
_vertices = new ContourVertex[_vertexCount];
|
||||
|
||||
int vertIndex = 0;
|
||||
int elementIndex = 0;
|
||||
|
||||
startVert = 0;
|
||||
|
||||
for (f = _mesh._fHead._next; f != _mesh._fHead; f = f._next)
|
||||
{
|
||||
if (!f._inside) continue;
|
||||
|
||||
vertCount = 0;
|
||||
start = edge = f._anEdge;
|
||||
do {
|
||||
_vertices[vertIndex].Position = edge._Org._coords;
|
||||
_vertices[vertIndex].Data = edge._Org._data;
|
||||
++vertIndex;
|
||||
++vertCount;
|
||||
edge = edge._Lnext;
|
||||
} while (edge != start);
|
||||
|
||||
_elements[elementIndex++] = startVert;
|
||||
_elements[elementIndex++] = vertCount;
|
||||
|
||||
startVert += vertCount;
|
||||
}
|
||||
}
|
||||
|
||||
private Real SignedArea(ContourVertex[] vertices)
|
||||
{
|
||||
Real area = 0.0f;
|
||||
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
var v0 = vertices[i];
|
||||
var v1 = vertices[(i + 1) % vertices.Length];
|
||||
|
||||
area += v0.Position.X * v1.Position.Y;
|
||||
area -= v0.Position.Y * v1.Position.X;
|
||||
}
|
||||
|
||||
return 0.5f * area;
|
||||
}
|
||||
|
||||
public void AddContour(ContourVertex[] vertices)
|
||||
{
|
||||
AddContour(vertices, ContourOrientation.Original);
|
||||
}
|
||||
|
||||
public void AddContour(ContourVertex[] vertices, ContourOrientation forceOrientation)
|
||||
{
|
||||
if (_mesh == null)
|
||||
{
|
||||
_mesh = new Mesh();
|
||||
}
|
||||
|
||||
bool reverse = false;
|
||||
if (forceOrientation != ContourOrientation.Original)
|
||||
{
|
||||
var area = SignedArea(vertices);
|
||||
reverse = (forceOrientation == ContourOrientation.Clockwise && area < 0.0f) || (forceOrientation == ContourOrientation.CounterClockwise && area > 0.0f);
|
||||
}
|
||||
|
||||
MeshUtils.Edge e = null;
|
||||
for (int i = 0; i < vertices.Length; ++i)
|
||||
{
|
||||
if (e == null)
|
||||
{
|
||||
e = _mesh.MakeEdge();
|
||||
_mesh.Splice(e, e._Sym);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create a new vertex and edge which immediately follow e
|
||||
// in the ordering around the left face.
|
||||
_mesh.SplitEdge(e);
|
||||
e = e._Lnext;
|
||||
}
|
||||
|
||||
int index = reverse ? vertices.Length - 1 - i : i;
|
||||
// The new vertex is now e._Org.
|
||||
e._Org._coords = vertices[index].Position;
|
||||
e._Org._data = vertices[index].Data;
|
||||
|
||||
// The winding of an edge says how the winding number changes as we
|
||||
// cross from the edge's right face to its left face. We add the
|
||||
// vertices in such an order that a CCW contour will add +1 to
|
||||
// the winding number of the region inside the contour.
|
||||
e._winding = 1;
|
||||
e._Sym._winding = -1;
|
||||
}
|
||||
}
|
||||
|
||||
public void Tessellate(WindingRule windingRule, ElementType elementType, int polySize)
|
||||
{
|
||||
Tessellate(windingRule, elementType, polySize, null);
|
||||
}
|
||||
|
||||
public void Tessellate(WindingRule windingRule, ElementType elementType, int polySize, CombineCallback combineCallback)
|
||||
{
|
||||
_normal = Vec3.Zero;
|
||||
_vertices = null;
|
||||
_elements = null;
|
||||
|
||||
_windingRule = windingRule;
|
||||
_combineCallback = combineCallback;
|
||||
|
||||
if (_mesh == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the polygon normal and project vertices onto the plane
|
||||
// of the polygon.
|
||||
ProjectPolygon();
|
||||
|
||||
// ComputeInterior computes the planar arrangement specified
|
||||
// by the given contours, and further subdivides this arrangement
|
||||
// into regions. Each region is marked "inside" if it belongs
|
||||
// to the polygon, according to the rule given by windingRule.
|
||||
// Each interior region is guaranteed be monotone.
|
||||
ComputeInterior();
|
||||
|
||||
// If the user wants only the boundary contours, we throw away all edges
|
||||
// except those which separate the interior from the exterior.
|
||||
// Otherwise we tessellate all the regions marked "inside".
|
||||
if (elementType == ElementType.BoundaryContours)
|
||||
{
|
||||
SetWindingNumber(1, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
TessellateInterior();
|
||||
}
|
||||
|
||||
_mesh.Check();
|
||||
|
||||
if (elementType == ElementType.BoundaryContours)
|
||||
{
|
||||
OutputContours();
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputPolymesh(elementType, polySize);
|
||||
}
|
||||
|
||||
if (UsePooling)
|
||||
{
|
||||
_mesh.Free();
|
||||
}
|
||||
_mesh = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Unity.VectorGraphics.External
|
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
// Spline Internal Meta Data.
|
||||
internal struct SplinePointMetaData
|
||||
{
|
||||
public float height;
|
||||
public uint spriteIndex;
|
||||
public int cornerMode;
|
||||
};
|
||||
|
||||
[Serializable]
|
||||
public class Spline
|
||||
{
|
||||
private static readonly string KErrorMessage = "Internal error: Point too close to neighbor";
|
||||
private static readonly float KEpsilon = 0.01f;
|
||||
[SerializeField]
|
||||
private bool m_IsOpenEnded;
|
||||
[SerializeField]
|
||||
private List<SplineControlPoint> m_ControlPoints = new List<SplineControlPoint>();
|
||||
|
||||
public bool isOpenEnded
|
||||
{
|
||||
get
|
||||
{
|
||||
if (GetPointCount() < 3)
|
||||
return true;
|
||||
|
||||
return m_IsOpenEnded;
|
||||
}
|
||||
set { m_IsOpenEnded = value; }
|
||||
}
|
||||
|
||||
private bool IsPositionValid(int index, int next, Vector3 point)
|
||||
{
|
||||
int prev = (index == 0) ? (m_ControlPoints.Count - 1) : (index - 1);
|
||||
next = (next >= m_ControlPoints.Count) ? 0 : next;
|
||||
if (prev >= 0)
|
||||
{
|
||||
Vector3 diff = m_ControlPoints[prev].position - point;
|
||||
if (diff.magnitude < KEpsilon)
|
||||
return false;
|
||||
}
|
||||
if (next < m_ControlPoints.Count)
|
||||
{
|
||||
Vector3 diff = m_ControlPoints[next].position - point;
|
||||
if (diff.magnitude < KEpsilon)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_ControlPoints.Clear();
|
||||
}
|
||||
|
||||
public int GetPointCount()
|
||||
{
|
||||
return m_ControlPoints.Count;
|
||||
}
|
||||
|
||||
public void InsertPointAt(int index, Vector3 point)
|
||||
{
|
||||
if (!IsPositionValid(index, index, point))
|
||||
throw new ArgumentException(KErrorMessage);
|
||||
m_ControlPoints.Insert(index, new SplineControlPoint { position = point, height = 1.0f, cornerMode = Corner.Automatic });
|
||||
}
|
||||
|
||||
public void RemovePointAt(int index)
|
||||
{
|
||||
if (m_ControlPoints.Count > 2)
|
||||
m_ControlPoints.RemoveAt(index);
|
||||
}
|
||||
|
||||
public Vector3 GetPosition(int index)
|
||||
{
|
||||
return m_ControlPoints[index].position;
|
||||
}
|
||||
|
||||
public void SetPosition(int index, Vector3 point)
|
||||
{
|
||||
if (!IsPositionValid(index, index + 1, point))
|
||||
throw new ArgumentException(KErrorMessage);
|
||||
SplineControlPoint newPoint = m_ControlPoints[index];
|
||||
newPoint.position = point;
|
||||
m_ControlPoints[index] = newPoint;
|
||||
}
|
||||
|
||||
public Vector3 GetLeftTangent(int index)
|
||||
{
|
||||
ShapeTangentMode mode = GetTangentMode(index);
|
||||
|
||||
if (mode == ShapeTangentMode.Linear)
|
||||
return Vector3.zero;
|
||||
|
||||
return m_ControlPoints[index].leftTangent;
|
||||
}
|
||||
|
||||
public void SetLeftTangent(int index, Vector3 tangent)
|
||||
{
|
||||
ShapeTangentMode mode = GetTangentMode(index);
|
||||
|
||||
if (mode == ShapeTangentMode.Linear)
|
||||
return;
|
||||
|
||||
SplineControlPoint newPoint = m_ControlPoints[index];
|
||||
newPoint.leftTangent = tangent;
|
||||
m_ControlPoints[index] = newPoint;
|
||||
}
|
||||
|
||||
public Vector3 GetRightTangent(int index)
|
||||
{
|
||||
ShapeTangentMode mode = GetTangentMode(index);
|
||||
|
||||
if (mode == ShapeTangentMode.Linear)
|
||||
return Vector3.zero;
|
||||
|
||||
return m_ControlPoints[index].rightTangent;
|
||||
}
|
||||
|
||||
public void SetRightTangent(int index, Vector3 tangent)
|
||||
{
|
||||
ShapeTangentMode mode = GetTangentMode(index);
|
||||
|
||||
if (mode == ShapeTangentMode.Linear)
|
||||
return;
|
||||
|
||||
SplineControlPoint newPoint = m_ControlPoints[index];
|
||||
newPoint.rightTangent = tangent;
|
||||
m_ControlPoints[index] = newPoint;
|
||||
}
|
||||
|
||||
public ShapeTangentMode GetTangentMode(int index)
|
||||
{
|
||||
return m_ControlPoints[index].mode;
|
||||
}
|
||||
|
||||
public void SetTangentMode(int index, ShapeTangentMode mode)
|
||||
{
|
||||
SplineControlPoint newPoint = m_ControlPoints[index];
|
||||
newPoint.mode = mode;
|
||||
m_ControlPoints[index] = newPoint;
|
||||
}
|
||||
|
||||
public float GetHeight(int index)
|
||||
{
|
||||
return m_ControlPoints[index].height;
|
||||
}
|
||||
|
||||
public void SetHeight(int index, float value)
|
||||
{
|
||||
m_ControlPoints[index].height = value;
|
||||
}
|
||||
|
||||
public int GetSpriteIndex(int index)
|
||||
{
|
||||
return m_ControlPoints[index].spriteIndex;
|
||||
}
|
||||
|
||||
public void SetSpriteIndex(int index, int value)
|
||||
{
|
||||
m_ControlPoints[index].spriteIndex = value;
|
||||
}
|
||||
|
||||
public bool GetCorner(int index)
|
||||
{
|
||||
return GetCornerMode(index) != Corner.Disable;
|
||||
}
|
||||
|
||||
public void SetCorner(int index, bool value)
|
||||
{
|
||||
m_ControlPoints[index].corner = value;
|
||||
m_ControlPoints[index].cornerMode = value ? Corner.Automatic : Corner.Disable;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = (int)2166136261;
|
||||
|
||||
for (int i = 0; i < GetPointCount(); ++i)
|
||||
{
|
||||
hashCode = hashCode * 16777619 ^ m_ControlPoints[i].GetHashCode();
|
||||
}
|
||||
|
||||
hashCode = hashCode * 16777619 ^ m_IsOpenEnded.GetHashCode();
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetCornerMode(int index, Corner value)
|
||||
{
|
||||
m_ControlPoints[index].corner = (value != Corner.Disable);
|
||||
m_ControlPoints[index].cornerMode = value;
|
||||
}
|
||||
|
||||
internal Corner GetCornerMode(int index)
|
||||
{
|
||||
if (m_ControlPoints[index].cornerMode == Corner.Disable)
|
||||
{
|
||||
// For backward compatibility.
|
||||
if (m_ControlPoints[index].corner)
|
||||
{
|
||||
m_ControlPoints[index].cornerMode = Corner.Automatic;
|
||||
return Corner.Automatic;
|
||||
}
|
||||
}
|
||||
return m_ControlPoints[index].cornerMode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
public class SplineUtility
|
||||
{
|
||||
public static float SlopeAngle(Vector2 start, Vector2 end)
|
||||
{
|
||||
Vector2 dir = start - end;
|
||||
dir.Normalize();
|
||||
Vector2 dvup = new Vector2(0, 1f);
|
||||
Vector2 dvrt = new Vector2(1f, 0);
|
||||
|
||||
float dr = Vector2.Dot(dir, dvrt);
|
||||
float du = Vector2.Dot(dir, dvup);
|
||||
float cu = Mathf.Acos(du);
|
||||
float sn = dr >= 0 ? 1.0f : -1.0f;
|
||||
float an = cu * Mathf.Rad2Deg * sn;
|
||||
|
||||
// Adjust angles when direction is parallel to Up Axis.
|
||||
an = (du != 1f) ? an : 0;
|
||||
an = (du != -1f) ? an : -180f;
|
||||
return an;
|
||||
}
|
||||
public static void CalculateTangents(Vector3 point, Vector3 prevPoint, Vector3 nextPoint, Vector3 forward, float scale, out Vector3 rightTangent, out Vector3 leftTangent)
|
||||
{
|
||||
Vector3 v1 = (prevPoint - point).normalized;
|
||||
Vector3 v2 = (nextPoint - point).normalized;
|
||||
Vector3 v3 = v1 + v2;
|
||||
Vector3 cross = forward;
|
||||
|
||||
if (prevPoint != nextPoint)
|
||||
{
|
||||
bool colinear = Mathf.Abs(v1.x * v2.y - v1.y * v2.x + v1.x * v2.z - v1.z * v2.x + v1.y * v2.z - v1.z * v2.y) < 0.01f;
|
||||
|
||||
if (colinear)
|
||||
{
|
||||
rightTangent = v2 * scale;
|
||||
leftTangent = v1 * scale;
|
||||
return;
|
||||
}
|
||||
|
||||
cross = Vector3.Cross(v1, v2);
|
||||
}
|
||||
|
||||
rightTangent = Vector3.Cross(cross, v3).normalized * scale;
|
||||
leftTangent = -rightTangent;
|
||||
}
|
||||
|
||||
public static int NextIndex(int index, int pointCount)
|
||||
{
|
||||
return Mod(index + 1, pointCount);
|
||||
}
|
||||
|
||||
public static int PreviousIndex(int index, int pointCount)
|
||||
{
|
||||
return Mod(index - 1, pointCount);
|
||||
}
|
||||
|
||||
private static int Mod(int x, int m)
|
||||
{
|
||||
int r = x % m;
|
||||
return r < 0 ? r + m : r;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy utility.
|
||||
internal class SpriteShapeCopyUtility<T> where T : struct
|
||||
{
|
||||
internal static void Copy(NativeSlice<T> dst, T[] src, int length)
|
||||
{
|
||||
NativeSlice<T> dstSet = new NativeSlice<T>(dst, 0, length);
|
||||
dstSet.CopyFrom(src);
|
||||
}
|
||||
|
||||
internal static void Copy(T[] dst, NativeSlice<T> src, int length)
|
||||
{
|
||||
NativeSlice<T> dstSet = new NativeSlice<T>(src, 0, length);
|
||||
dstSet.CopyTo(dst);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,333 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
public enum ShapeTangentMode
|
||||
{
|
||||
Linear = 0,
|
||||
Continuous = 1,
|
||||
Broken = 2,
|
||||
};
|
||||
|
||||
public enum CornerType
|
||||
{
|
||||
OuterTopLeft,
|
||||
OuterTopRight,
|
||||
OuterBottomLeft,
|
||||
OuterBottomRight,
|
||||
InnerTopLeft,
|
||||
InnerTopRight,
|
||||
InnerBottomLeft,
|
||||
InnerBottomRight,
|
||||
};
|
||||
|
||||
public enum QualityDetail
|
||||
{
|
||||
High = 16,
|
||||
Mid = 8,
|
||||
Low = 4
|
||||
}
|
||||
|
||||
public enum Corner
|
||||
{
|
||||
Disable = 0,
|
||||
Automatic = 1,
|
||||
Stretched = 2,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class SplineControlPoint
|
||||
{
|
||||
public Vector3 position;
|
||||
public Vector3 leftTangent;
|
||||
public Vector3 rightTangent;
|
||||
public ShapeTangentMode mode;
|
||||
public float height = 1f;
|
||||
public float bevelCutoff;
|
||||
public float bevelSize;
|
||||
public int spriteIndex;
|
||||
public bool corner;
|
||||
[SerializeField]
|
||||
Corner m_CornerMode;
|
||||
|
||||
public Corner cornerMode
|
||||
{
|
||||
get => m_CornerMode;
|
||||
set => m_CornerMode = value;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ((int)position.x).GetHashCode() ^ ((int)position.y).GetHashCode() ^ position.GetHashCode() ^
|
||||
(leftTangent.GetHashCode() << 2) ^ (rightTangent.GetHashCode() >> 2) ^ ((int)mode).GetHashCode() ^
|
||||
height.GetHashCode() ^ spriteIndex.GetHashCode() ^ corner.GetHashCode() ^ (m_CornerMode.GetHashCode() << 2);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class AngleRange : ICloneable
|
||||
{
|
||||
public float start
|
||||
{
|
||||
get { return m_Start; }
|
||||
set { m_Start = value; }
|
||||
}
|
||||
|
||||
public float end
|
||||
{
|
||||
get { return m_End; }
|
||||
set { m_End = value; }
|
||||
}
|
||||
|
||||
public int order
|
||||
{
|
||||
get { return m_Order; }
|
||||
set { m_Order = value; }
|
||||
}
|
||||
|
||||
public List<Sprite> sprites
|
||||
{
|
||||
get { return m_Sprites; }
|
||||
set { m_Sprites = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
float m_Start;
|
||||
[SerializeField]
|
||||
float m_End;
|
||||
[SerializeField]
|
||||
int m_Order;
|
||||
[SerializeField]
|
||||
List<Sprite> m_Sprites = new List<Sprite>();
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
AngleRange clone = this.MemberwiseClone() as AngleRange;
|
||||
clone.sprites = new List<Sprite>(clone.sprites);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as AngleRange;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
bool equals = start.Equals(other.start) && end.Equals(other.end) && order.Equals(other.order);
|
||||
|
||||
if (!equals)
|
||||
return false;
|
||||
|
||||
if (sprites.Count != other.sprites.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < sprites.Count; ++i)
|
||||
if (sprites[i] != other.sprites[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = start.GetHashCode() ^ end.GetHashCode() ^ order.GetHashCode();
|
||||
|
||||
if (sprites != null)
|
||||
{
|
||||
for (int i = 0; i < sprites.Count; i++)
|
||||
{
|
||||
Sprite sprite = sprites[i];
|
||||
if (sprite)
|
||||
hashCode = hashCode * 16777619 ^ (sprite.GetHashCode() + i);
|
||||
}
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class CornerSprite : ICloneable
|
||||
{
|
||||
public CornerType cornerType
|
||||
{
|
||||
get { return m_CornerType; }
|
||||
set { m_CornerType = value; }
|
||||
}
|
||||
|
||||
public List<Sprite> sprites
|
||||
{
|
||||
get { return m_Sprites; }
|
||||
set { m_Sprites = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
CornerType m_CornerType; ///< Set Corner type. enum { OuterTopLeft = 0, OuterTopRight = 1, OuterBottomLeft = 2, OuterBottomRight = 3, InnerTopLeft = 4, InnerTopRight = 5, InnerBottomLeft = 6, InnerBottomRight = 7 }
|
||||
[SerializeField]
|
||||
List<Sprite> m_Sprites;
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
CornerSprite clone = this.MemberwiseClone() as CornerSprite;
|
||||
clone.sprites = new List<Sprite>(clone.sprites);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var other = obj as CornerSprite;
|
||||
|
||||
if (other == null)
|
||||
return false;
|
||||
|
||||
if (!cornerType.Equals(other.cornerType))
|
||||
return false;
|
||||
|
||||
if (sprites.Count != other.sprites.Count)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < sprites.Count; ++i)
|
||||
if (sprites[i] != other.sprites[i])
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hashCode = cornerType.GetHashCode();
|
||||
|
||||
if (sprites != null)
|
||||
{
|
||||
for (int i = 0; i < sprites.Count; i++)
|
||||
{
|
||||
Sprite sprite = sprites[i];
|
||||
if (sprite)
|
||||
{
|
||||
hashCode ^= (i + 1);
|
||||
hashCode ^= sprite.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
[HelpURLAttribute("https://docs.unity3d.com/Packages/com.unity.2d.spriteshape@latest/index.html?subfolder=/manual/SSProfile.html")]
|
||||
public class SpriteShape : ScriptableObject
|
||||
{
|
||||
public List<AngleRange> angleRanges
|
||||
{
|
||||
get { return m_Angles; }
|
||||
set { m_Angles = value; }
|
||||
}
|
||||
|
||||
public Texture2D fillTexture
|
||||
{
|
||||
get { return m_FillTexture; }
|
||||
set { m_FillTexture = value; }
|
||||
}
|
||||
|
||||
public List<CornerSprite> cornerSprites
|
||||
{
|
||||
get { return m_CornerSprites; }
|
||||
set { m_CornerSprites = value; }
|
||||
}
|
||||
|
||||
public float fillOffset
|
||||
{
|
||||
get { return m_FillOffset; }
|
||||
set { m_FillOffset = value; }
|
||||
}
|
||||
|
||||
public bool useSpriteBorders
|
||||
{
|
||||
get { return m_UseSpriteBorders; }
|
||||
set { m_UseSpriteBorders = value; }
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
List<AngleRange> m_Angles = new List<AngleRange>();
|
||||
[SerializeField]
|
||||
Texture2D m_FillTexture;
|
||||
[SerializeField]
|
||||
List<CornerSprite> m_CornerSprites = new List<CornerSprite>();
|
||||
[SerializeField]
|
||||
float m_FillOffset;
|
||||
|
||||
[SerializeField]
|
||||
bool m_UseSpriteBorders = true;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal static event Action<SpriteShape> onReset = null;
|
||||
#endif
|
||||
private CornerSprite GetCornerSprite(CornerType cornerType)
|
||||
{
|
||||
var cornerSprite = new CornerSprite();
|
||||
cornerSprite.cornerType = cornerType;
|
||||
cornerSprite.sprites = new List<Sprite>();
|
||||
cornerSprite.sprites.Insert(0, null);
|
||||
return cornerSprite;
|
||||
}
|
||||
|
||||
void ResetCornerList()
|
||||
{
|
||||
m_CornerSprites.Clear();
|
||||
m_CornerSprites.Insert(0, GetCornerSprite(CornerType.OuterTopLeft));
|
||||
m_CornerSprites.Insert(1, GetCornerSprite(CornerType.OuterTopRight));
|
||||
m_CornerSprites.Insert(2, GetCornerSprite(CornerType.OuterBottomLeft));
|
||||
m_CornerSprites.Insert(3, GetCornerSprite(CornerType.OuterBottomRight));
|
||||
m_CornerSprites.Insert(4, GetCornerSprite(CornerType.InnerTopLeft));
|
||||
m_CornerSprites.Insert(5, GetCornerSprite(CornerType.InnerTopRight));
|
||||
m_CornerSprites.Insert(6, GetCornerSprite(CornerType.InnerBottomLeft));
|
||||
m_CornerSprites.Insert(7, GetCornerSprite(CornerType.InnerBottomRight));
|
||||
}
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (m_CornerSprites.Count != 8)
|
||||
ResetCornerList();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_Angles.Clear();
|
||||
ResetCornerList();
|
||||
#if UNITY_EDITOR
|
||||
onReset?.Invoke(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static int GetSpriteShapeHashCode(SpriteShape spriteShape)
|
||||
{
|
||||
// useSpriteBorders, fillOffset and fillTexture are hashChecked elsewhere.
|
||||
|
||||
unchecked
|
||||
{
|
||||
int hashCode = (int)2166136261;
|
||||
|
||||
hashCode = hashCode * 16777619 ^ spriteShape.angleRanges.Count;
|
||||
|
||||
for (int i = 0; i < spriteShape.angleRanges.Count; ++i)
|
||||
{
|
||||
hashCode = hashCode * 16777619 ^ (spriteShape.angleRanges[i].GetHashCode() + i);
|
||||
}
|
||||
|
||||
hashCode = hashCode * 16777619 ^ spriteShape.cornerSprites.Count;
|
||||
|
||||
for (int i = 0; i < spriteShape.cornerSprites.Count; ++i)
|
||||
{
|
||||
hashCode = hashCode * 16777619 ^ (spriteShape.cornerSprites[i].GetHashCode() + i);
|
||||
}
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,826 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Jobs;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor.U2D;
|
||||
#endif
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
[ExecuteInEditMode]
|
||||
[RequireComponent(typeof(SpriteShapeRenderer))]
|
||||
[DisallowMultipleComponent]
|
||||
[HelpURLAttribute("https://docs.unity3d.com/Packages/com.unity.2d.spriteshape@latest/index.html?subfolder=/manual/SSController.html")]
|
||||
public class SpriteShapeController : MonoBehaviour
|
||||
{
|
||||
// Internal Dataset.
|
||||
const float s_DistanceTolerance = 0.001f;
|
||||
|
||||
// Cached Objects.
|
||||
SpriteShape m_ActiveSpriteShape;
|
||||
EdgeCollider2D m_EdgeCollider2D;
|
||||
PolygonCollider2D m_PolygonCollider2D;
|
||||
SpriteShapeRenderer m_SpriteShapeRenderer;
|
||||
SpriteShapeGeometryCache m_SpriteShapeGeometryCache;
|
||||
|
||||
Sprite[] m_SpriteArray = new Sprite[0];
|
||||
Sprite[] m_EdgeSpriteArray = new Sprite[0];
|
||||
Sprite[] m_CornerSpriteArray = new Sprite[0];
|
||||
AngleRangeInfo[] m_AngleRangeInfoArray = new AngleRangeInfo[0];
|
||||
|
||||
// Required for Generation.
|
||||
NativeArray<float2> m_ColliderData;
|
||||
NativeArray<Vector4> m_TangentData;
|
||||
|
||||
// Renderer Stuff.
|
||||
bool m_DynamicOcclusionLocal;
|
||||
bool m_DynamicOcclusionOverriden;
|
||||
|
||||
// Hash Check.
|
||||
int m_ActiveSplineHash = 0;
|
||||
int m_ActiveSpriteShapeHash = 0;
|
||||
JobHandle m_JobHandle;
|
||||
SpriteShapeParameters m_ActiveShapeParameters;
|
||||
|
||||
// Serialized Data.
|
||||
[SerializeField]
|
||||
Spline m_Spline = new Spline();
|
||||
[SerializeField]
|
||||
SpriteShape m_SpriteShape;
|
||||
|
||||
[SerializeField]
|
||||
float m_FillPixelPerUnit = 100.0f;
|
||||
[SerializeField]
|
||||
float m_StretchTiling = 1.0f;
|
||||
[SerializeField]
|
||||
int m_SplineDetail;
|
||||
[SerializeField]
|
||||
bool m_AdaptiveUV;
|
||||
[SerializeField]
|
||||
bool m_StretchUV;
|
||||
[SerializeField]
|
||||
bool m_WorldSpaceUV;
|
||||
[SerializeField]
|
||||
float m_CornerAngleThreshold = 30.0f;
|
||||
[SerializeField]
|
||||
int m_ColliderDetail;
|
||||
[SerializeField, Range(-0.5f, 0.5f)]
|
||||
float m_ColliderOffset;
|
||||
[SerializeField]
|
||||
bool m_UpdateCollider = true;
|
||||
[SerializeField]
|
||||
bool m_OptimizeCollider = true;
|
||||
[SerializeField]
|
||||
bool m_OptimizeGeometry = true;
|
||||
[SerializeField]
|
||||
bool m_EnableTangents = false;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
bool m_GeometryCached = false;
|
||||
[SerializeField]
|
||||
bool m_UTess2D = false;
|
||||
|
||||
#region GetSet
|
||||
|
||||
internal bool geometryCached
|
||||
{
|
||||
get { return m_GeometryCached; }
|
||||
set { m_GeometryCached = value; }
|
||||
}
|
||||
|
||||
internal int splineHashCode
|
||||
{
|
||||
get { return m_ActiveSplineHash; }
|
||||
}
|
||||
|
||||
internal Sprite[] spriteArray
|
||||
{
|
||||
get { return m_SpriteArray; }
|
||||
}
|
||||
|
||||
internal SpriteShapeParameters spriteShapeParameters
|
||||
{
|
||||
get { return m_ActiveShapeParameters; }
|
||||
}
|
||||
|
||||
internal SpriteShapeGeometryCache spriteShapeGeometryCache
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_SpriteShapeGeometryCache)
|
||||
m_SpriteShapeGeometryCache = GetComponent<SpriteShapeGeometryCache>();
|
||||
return m_SpriteShapeGeometryCache;
|
||||
}
|
||||
}
|
||||
|
||||
public int spriteShapeHashCode
|
||||
{
|
||||
get { return m_ActiveSpriteShapeHash; }
|
||||
}
|
||||
|
||||
public bool worldSpaceUVs
|
||||
{
|
||||
get { return m_WorldSpaceUV; }
|
||||
set { m_WorldSpaceUV = value; }
|
||||
}
|
||||
|
||||
public float fillPixelsPerUnit
|
||||
{
|
||||
get { return m_FillPixelPerUnit; }
|
||||
set { m_FillPixelPerUnit = value; }
|
||||
}
|
||||
|
||||
public bool enableTangents
|
||||
{
|
||||
get { return m_EnableTangents; }
|
||||
set { m_EnableTangents = value; }
|
||||
}
|
||||
|
||||
public float stretchTiling
|
||||
{
|
||||
get { return m_StretchTiling; }
|
||||
set { m_StretchTiling = value; }
|
||||
}
|
||||
|
||||
public int splineDetail
|
||||
{
|
||||
get { return m_SplineDetail; }
|
||||
set { m_SplineDetail = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
public int colliderDetail
|
||||
{
|
||||
get { return m_ColliderDetail; }
|
||||
set { m_ColliderDetail = Mathf.Max(0, value); }
|
||||
}
|
||||
|
||||
public float colliderOffset
|
||||
{
|
||||
get { return m_ColliderOffset; }
|
||||
set { m_ColliderOffset = value; }
|
||||
}
|
||||
|
||||
public float cornerAngleThreshold
|
||||
{
|
||||
get { return m_CornerAngleThreshold; }
|
||||
set { m_CornerAngleThreshold = value; }
|
||||
}
|
||||
|
||||
public bool autoUpdateCollider
|
||||
{
|
||||
get { return m_UpdateCollider; }
|
||||
set { m_UpdateCollider = value; }
|
||||
}
|
||||
|
||||
public bool optimizeCollider
|
||||
{
|
||||
get { return m_OptimizeCollider; }
|
||||
}
|
||||
|
||||
public bool optimizeGeometry
|
||||
{
|
||||
get { return m_OptimizeGeometry; }
|
||||
}
|
||||
|
||||
public bool hasCollider
|
||||
{
|
||||
get { return (edgeCollider != null) || (polygonCollider != null); }
|
||||
}
|
||||
|
||||
public Spline spline
|
||||
{
|
||||
get { return m_Spline; }
|
||||
}
|
||||
|
||||
public SpriteShape spriteShape
|
||||
{
|
||||
get { return m_SpriteShape; }
|
||||
set { m_SpriteShape = value; }
|
||||
}
|
||||
|
||||
public EdgeCollider2D edgeCollider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_EdgeCollider2D)
|
||||
m_EdgeCollider2D = GetComponent<EdgeCollider2D>();
|
||||
return m_EdgeCollider2D;
|
||||
}
|
||||
}
|
||||
|
||||
public PolygonCollider2D polygonCollider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_PolygonCollider2D)
|
||||
m_PolygonCollider2D = GetComponent<PolygonCollider2D>();
|
||||
return m_PolygonCollider2D;
|
||||
}
|
||||
}
|
||||
|
||||
public SpriteShapeRenderer spriteShapeRenderer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!m_SpriteShapeRenderer)
|
||||
m_SpriteShapeRenderer = GetComponent<SpriteShapeRenderer>();
|
||||
return m_SpriteShapeRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EventHandles.
|
||||
|
||||
void DisposeInternal()
|
||||
{
|
||||
if (m_ColliderData.IsCreated)
|
||||
m_ColliderData.Dispose();
|
||||
if (m_TangentData.IsCreated)
|
||||
m_TangentData.Dispose();
|
||||
}
|
||||
|
||||
void OnApplicationQuit()
|
||||
{
|
||||
DisposeInternal();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
spriteShapeRenderer.enabled = true;
|
||||
m_DynamicOcclusionOverriden = true;
|
||||
m_DynamicOcclusionLocal = spriteShapeRenderer.allowOcclusionWhenDynamic;
|
||||
spriteShapeRenderer.allowOcclusionWhenDynamic = false;
|
||||
UpdateSpriteData();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
spriteShapeRenderer.enabled = false;
|
||||
DisposeInternal();
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
m_SplineDetail = (int)QualityDetail.High;
|
||||
m_AdaptiveUV = true;
|
||||
m_StretchUV = false;
|
||||
m_FillPixelPerUnit = 100f;
|
||||
|
||||
m_ColliderDetail = (int)QualityDetail.High;
|
||||
m_StretchTiling = 1.0f;
|
||||
m_WorldSpaceUV = false;
|
||||
m_CornerAngleThreshold = 30.0f;
|
||||
m_ColliderOffset = 0;
|
||||
m_UpdateCollider = true;
|
||||
m_OptimizeCollider = true;
|
||||
m_OptimizeGeometry = true;
|
||||
m_EnableTangents = false;
|
||||
|
||||
spline.Clear();
|
||||
spline.InsertPointAt(0, Vector2.left + Vector2.down);
|
||||
spline.InsertPointAt(1, Vector2.left + Vector2.up);
|
||||
spline.InsertPointAt(2, Vector2.right + Vector2.up);
|
||||
spline.InsertPointAt(3, Vector2.right + Vector2.down);
|
||||
}
|
||||
|
||||
static void SmartDestroy(UnityEngine.Object o)
|
||||
{
|
||||
if (o == null)
|
||||
return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
DestroyImmediate(o);
|
||||
else
|
||||
#endif
|
||||
Destroy(o);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HashAndDataCheck
|
||||
|
||||
public void RefreshSpriteShape()
|
||||
{
|
||||
m_ActiveSplineHash = 0;
|
||||
}
|
||||
|
||||
// Ensure Neighbor points are not too close to each other.
|
||||
bool ValidateSpline()
|
||||
{
|
||||
int pointCount = spline.GetPointCount();
|
||||
if (pointCount < 2)
|
||||
return false;
|
||||
for (int i = 0; i < pointCount - 1; ++i)
|
||||
{
|
||||
var vec = spline.GetPosition(i) - spline.GetPosition(i + 1);
|
||||
if (vec.sqrMagnitude < s_DistanceTolerance)
|
||||
{
|
||||
Debug.LogWarningFormat(gameObject, "[SpriteShape] Control points {0} & {1} are too close. SpriteShape will not be generated for < {2} >.", i, i + 1, gameObject.name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ensure SpriteShape is valid if not
|
||||
bool ValidateSpriteShapeTexture()
|
||||
{
|
||||
bool valid = false;
|
||||
|
||||
// Check if SpriteShape Profile is valid.
|
||||
if (spriteShape != null)
|
||||
{
|
||||
// Open ended and no valid Sprites set. Check if it has a valid fill texture.
|
||||
if (!spline.isOpenEnded)
|
||||
{
|
||||
valid = (spriteShape.fillTexture != null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Warn that no SpriteShape is set.
|
||||
Debug.LogWarningFormat(gameObject, "[SpriteShape] A valid SpriteShape profile has not been set for gameObject < {0} >.", gameObject.name);
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
// We allow null SpriteShape for rapid prototyping in Editor.
|
||||
valid = true;
|
||||
#endif
|
||||
return valid;
|
||||
}
|
||||
|
||||
bool ValidateUTess2D(bool uTess2D)
|
||||
{
|
||||
if (uTess2D && null != spriteShape)
|
||||
{
|
||||
// Check for all properties
|
||||
uTess2D = (spriteShape.fillOffset >= 0);
|
||||
}
|
||||
return uTess2D;
|
||||
}
|
||||
|
||||
bool HasSpriteShapeChanged()
|
||||
{
|
||||
bool changed = (m_ActiveSpriteShape != spriteShape);
|
||||
if (changed)
|
||||
m_ActiveSpriteShape = spriteShape;
|
||||
return changed;
|
||||
}
|
||||
|
||||
bool HasSpriteShapeDataChanged()
|
||||
{
|
||||
bool updateSprites = HasSpriteShapeChanged();
|
||||
if (spriteShape)
|
||||
{
|
||||
var hashCode = SpriteShape.GetSpriteShapeHashCode(spriteShape);
|
||||
if (spriteShapeHashCode != hashCode)
|
||||
{
|
||||
m_ActiveSpriteShapeHash = hashCode;
|
||||
updateSprites = true;
|
||||
}
|
||||
}
|
||||
return updateSprites;
|
||||
}
|
||||
|
||||
bool HasSplineDataChanged()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// Spline.
|
||||
int hashCode = (int)2166136261 ^ spline.GetHashCode();
|
||||
|
||||
// Local Stuff.
|
||||
hashCode = hashCode * 16777619 ^ (m_UTess2D ? 1 : 0);
|
||||
hashCode = hashCode * 16777619 ^ (m_WorldSpaceUV ? 1 : 0);
|
||||
hashCode = hashCode * 16777619 ^ (m_EnableTangents ? 1 : 0);
|
||||
hashCode = hashCode * 16777619 ^ (m_GeometryCached ? 1 : 0);
|
||||
hashCode = hashCode * 16777619 ^ (m_OptimizeGeometry ? 1 : 0);
|
||||
hashCode = hashCode * 16777619 ^ (m_StretchTiling.GetHashCode());
|
||||
hashCode = hashCode * 16777619 ^ (m_ColliderOffset.GetHashCode());
|
||||
hashCode = hashCode * 16777619 ^ (m_ColliderDetail.GetHashCode());
|
||||
|
||||
if (splineHashCode != hashCode)
|
||||
{
|
||||
m_ActiveSplineHash = hashCode;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
BakeCollider();
|
||||
}
|
||||
|
||||
void OnWillRenderObject()
|
||||
{
|
||||
BakeMesh();
|
||||
}
|
||||
|
||||
public JobHandle BakeMesh()
|
||||
{
|
||||
JobHandle jobHandle = default;
|
||||
|
||||
#if !UNITY_EDITOR
|
||||
if (spriteShapeGeometryCache)
|
||||
{
|
||||
// If SpriteShapeGeometry has already been uploaded, don't bother checking further.
|
||||
if (0 != m_ActiveSplineHash && 0 != spriteShapeGeometryCache.maxArrayCount)
|
||||
return jobHandle;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool valid = ValidateSpline();
|
||||
|
||||
if (valid)
|
||||
{
|
||||
bool splineChanged = HasSplineDataChanged();
|
||||
bool spriteShapeChanged = HasSpriteShapeDataChanged();
|
||||
bool spriteShapeParametersChanged = UpdateSpriteShapeParameters();
|
||||
|
||||
if (spriteShapeChanged)
|
||||
{
|
||||
UpdateSpriteData();
|
||||
}
|
||||
|
||||
if (splineChanged || spriteShapeChanged || spriteShapeParametersChanged)
|
||||
{
|
||||
jobHandle = ScheduleBake();
|
||||
}
|
||||
}
|
||||
return jobHandle;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UpdateData
|
||||
|
||||
public bool UpdateSpriteShapeParameters()
|
||||
{
|
||||
bool carpet = !spline.isOpenEnded;
|
||||
bool smartSprite = true;
|
||||
bool adaptiveUV = m_AdaptiveUV;
|
||||
bool stretchUV = m_StretchUV;
|
||||
bool spriteBorders = false;
|
||||
uint fillScale = 0;
|
||||
uint splineDetail = (uint)m_SplineDetail;
|
||||
float borderPivot = 0f;
|
||||
float angleThreshold = (m_CornerAngleThreshold >= 0 && m_CornerAngleThreshold < 90) ? m_CornerAngleThreshold : 89.9999f;
|
||||
Texture2D fillTexture = null;
|
||||
Matrix4x4 transformMatrix = Matrix4x4.identity;
|
||||
|
||||
if (spriteShape)
|
||||
{
|
||||
if (worldSpaceUVs)
|
||||
transformMatrix = transform.localToWorldMatrix;
|
||||
|
||||
fillTexture = spriteShape.fillTexture;
|
||||
fillScale = stretchUV ? (uint)stretchTiling : (uint)fillPixelsPerUnit;
|
||||
borderPivot = spriteShape.fillOffset;
|
||||
spriteBorders = spriteShape.useSpriteBorders;
|
||||
// If Corners are enabled, set smart-sprite to false.
|
||||
if (spriteShape.cornerSprites.Count > 0)
|
||||
smartSprite = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
fillTexture = UnityEditor.EditorGUIUtility.whiteTexture;
|
||||
fillScale = 100;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool changed = m_ActiveShapeParameters.adaptiveUV != adaptiveUV ||
|
||||
m_ActiveShapeParameters.angleThreshold != angleThreshold ||
|
||||
m_ActiveShapeParameters.borderPivot != borderPivot ||
|
||||
m_ActiveShapeParameters.carpet != carpet ||
|
||||
m_ActiveShapeParameters.fillScale != fillScale ||
|
||||
m_ActiveShapeParameters.fillTexture != fillTexture ||
|
||||
m_ActiveShapeParameters.smartSprite != smartSprite ||
|
||||
m_ActiveShapeParameters.splineDetail != splineDetail ||
|
||||
m_ActiveShapeParameters.spriteBorders != spriteBorders ||
|
||||
m_ActiveShapeParameters.transform != transformMatrix ||
|
||||
m_ActiveShapeParameters.stretchUV != stretchUV;
|
||||
|
||||
m_ActiveShapeParameters.adaptiveUV = adaptiveUV;
|
||||
m_ActiveShapeParameters.stretchUV = stretchUV;
|
||||
m_ActiveShapeParameters.angleThreshold = angleThreshold;
|
||||
m_ActiveShapeParameters.borderPivot = borderPivot;
|
||||
m_ActiveShapeParameters.carpet = carpet;
|
||||
m_ActiveShapeParameters.fillScale = fillScale;
|
||||
m_ActiveShapeParameters.fillTexture = fillTexture;
|
||||
m_ActiveShapeParameters.smartSprite = smartSprite;
|
||||
m_ActiveShapeParameters.splineDetail = splineDetail;
|
||||
m_ActiveShapeParameters.spriteBorders = spriteBorders;
|
||||
m_ActiveShapeParameters.transform = transformMatrix;
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
void UpdateSpriteData()
|
||||
{
|
||||
if (spriteShape)
|
||||
{
|
||||
List<Sprite> edgeSpriteList = new List<Sprite>();
|
||||
List<Sprite> cornerSpriteList = new List<Sprite>();
|
||||
List<AngleRangeInfo> angleRangeInfoList = new List<AngleRangeInfo>();
|
||||
|
||||
List<AngleRange> sortedAngleRanges = new List<AngleRange>(spriteShape.angleRanges);
|
||||
sortedAngleRanges.Sort((a, b) => a.order.CompareTo(b.order));
|
||||
|
||||
for (int i = 0; i < sortedAngleRanges.Count; i++)
|
||||
{
|
||||
bool validSpritesFound = false;
|
||||
AngleRange angleRange = sortedAngleRanges[i];
|
||||
foreach (Sprite edgeSprite in angleRange.sprites)
|
||||
{
|
||||
if (edgeSprite != null)
|
||||
{
|
||||
validSpritesFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (validSpritesFound)
|
||||
{
|
||||
AngleRangeInfo angleRangeInfo = new AngleRangeInfo();
|
||||
angleRangeInfo.start = angleRange.start;
|
||||
angleRangeInfo.end = angleRange.end;
|
||||
angleRangeInfo.order = (uint)angleRange.order;
|
||||
List<int> spriteIndices = new List<int>();
|
||||
foreach (Sprite edgeSprite in angleRange.sprites)
|
||||
{
|
||||
edgeSpriteList.Add(edgeSprite);
|
||||
spriteIndices.Add(edgeSpriteList.Count - 1);
|
||||
}
|
||||
angleRangeInfo.sprites = spriteIndices.ToArray();
|
||||
angleRangeInfoList.Add(angleRangeInfo);
|
||||
}
|
||||
}
|
||||
|
||||
bool validCornerSpritesFound = false;
|
||||
foreach (CornerSprite cornerSprite in spriteShape.cornerSprites)
|
||||
{
|
||||
if (cornerSprite.sprites[0] != null)
|
||||
{
|
||||
validCornerSpritesFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (validCornerSpritesFound)
|
||||
{
|
||||
for (int i = 0; i < spriteShape.cornerSprites.Count; i++)
|
||||
{
|
||||
CornerSprite cornerSprite = spriteShape.cornerSprites[i];
|
||||
cornerSpriteList.Add(cornerSprite.sprites[0]);
|
||||
}
|
||||
}
|
||||
|
||||
m_EdgeSpriteArray = edgeSpriteList.ToArray();
|
||||
m_CornerSpriteArray = cornerSpriteList.ToArray();
|
||||
m_AngleRangeInfoArray = angleRangeInfoList.ToArray();
|
||||
|
||||
List<Sprite> spriteList = new List<Sprite>();
|
||||
spriteList.AddRange(m_EdgeSpriteArray);
|
||||
spriteList.AddRange(m_CornerSpriteArray);
|
||||
m_SpriteArray = spriteList.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_SpriteArray = new Sprite[0];
|
||||
m_EdgeSpriteArray = new Sprite[0];
|
||||
m_CornerSpriteArray = new Sprite[0];
|
||||
m_AngleRangeInfoArray = new AngleRangeInfo[0];
|
||||
}
|
||||
}
|
||||
|
||||
NativeArray<ShapeControlPoint> GetShapeControlPoints()
|
||||
{
|
||||
int pointCount = spline.GetPointCount();
|
||||
NativeArray<ShapeControlPoint> shapePoints = new NativeArray<ShapeControlPoint>(pointCount, Allocator.Temp);
|
||||
for (int i = 0; i < pointCount; ++i)
|
||||
{
|
||||
ShapeControlPoint shapeControlPoint;
|
||||
shapeControlPoint.position = spline.GetPosition(i);
|
||||
shapeControlPoint.leftTangent = spline.GetLeftTangent(i);
|
||||
shapeControlPoint.rightTangent = spline.GetRightTangent(i);
|
||||
shapeControlPoint.mode = (int)spline.GetTangentMode(i);
|
||||
shapePoints[i] = shapeControlPoint;
|
||||
}
|
||||
return shapePoints;
|
||||
}
|
||||
|
||||
NativeArray<SplinePointMetaData> GetSplinePointMetaData()
|
||||
{
|
||||
int pointCount = spline.GetPointCount();
|
||||
NativeArray<SplinePointMetaData> shapeMetaData = new NativeArray<SplinePointMetaData>(pointCount, Allocator.Temp);
|
||||
for (int i = 0; i < pointCount; ++i)
|
||||
{
|
||||
SplinePointMetaData metaData;
|
||||
metaData.height = m_Spline.GetHeight(i);
|
||||
metaData.spriteIndex = (uint)m_Spline.GetSpriteIndex(i);
|
||||
metaData.cornerMode = (int)m_Spline.GetCornerMode(i);
|
||||
shapeMetaData[i] = metaData;
|
||||
}
|
||||
return shapeMetaData;
|
||||
}
|
||||
|
||||
int CalculateMaxArrayCount(NativeArray<ShapeControlPoint> shapePoints)
|
||||
{
|
||||
bool hasSprites = false;
|
||||
float smallestWidth = 99999.0f;
|
||||
|
||||
if (null != spriteArray)
|
||||
{
|
||||
foreach (var sprite in m_SpriteArray)
|
||||
{
|
||||
if (sprite != null)
|
||||
{
|
||||
hasSprites = true;
|
||||
float pixelWidth = BezierUtility.GetSpritePixelWidth(sprite);
|
||||
smallestWidth = (smallestWidth > pixelWidth) ? pixelWidth : smallestWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Approximate vertex Array Count.
|
||||
float shapeLength = BezierUtility.BezierLength(shapePoints, splineDetail * splineDetail) * 2.0f;
|
||||
int adjustWidth = hasSprites ? ((int)(shapeLength / smallestWidth) * 6) + (shapePoints.Length * 6 * splineDetail) : 0;
|
||||
int adjustShape = shapePoints.Length * 4 * splineDetail;
|
||||
adjustShape = optimizeGeometry ? (adjustShape) : (adjustShape * 2);
|
||||
var validFT = ValidateSpriteShapeTexture();
|
||||
#if !UNITY_EDITOR
|
||||
adjustShape = validFT ? adjustShape : 0;
|
||||
#endif
|
||||
int maxArrayCount = adjustShape + adjustWidth;
|
||||
return maxArrayCount;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
unsafe JobHandle ScheduleBake()
|
||||
{
|
||||
JobHandle jobHandle = default;
|
||||
|
||||
bool staticUpload = Application.isPlaying;
|
||||
#if !UNITY_EDITOR
|
||||
staticUpload = true;
|
||||
#endif
|
||||
if (staticUpload && geometryCached)
|
||||
{
|
||||
if (spriteShapeGeometryCache)
|
||||
if (spriteShapeGeometryCache.maxArrayCount != 0)
|
||||
return spriteShapeGeometryCache.Upload(spriteShapeRenderer, this);
|
||||
}
|
||||
|
||||
int pointCount = spline.GetPointCount();
|
||||
NativeArray<ShapeControlPoint> shapePoints = GetShapeControlPoints();
|
||||
NativeArray<SplinePointMetaData> shapeMetaData = GetSplinePointMetaData();
|
||||
int maxArrayCount = CalculateMaxArrayCount(shapePoints);
|
||||
|
||||
if (maxArrayCount > 0 && enabled)
|
||||
{
|
||||
// Collider Data
|
||||
if (m_ColliderData.IsCreated)
|
||||
m_ColliderData.Dispose();
|
||||
m_ColliderData = new NativeArray<float2>(maxArrayCount, Allocator.Persistent);
|
||||
|
||||
// Tangent Data
|
||||
if (!m_TangentData.IsCreated)
|
||||
m_TangentData = new NativeArray<Vector4>(1, Allocator.Persistent);
|
||||
|
||||
NativeArray<ushort> indexArray;
|
||||
NativeSlice<Vector3> posArray;
|
||||
NativeSlice<Vector2> uv0Array;
|
||||
NativeArray<Bounds> bounds = spriteShapeRenderer.GetBounds();
|
||||
NativeArray<SpriteShapeSegment> geomArray = spriteShapeRenderer.GetSegments(shapePoints.Length * 8);
|
||||
NativeSlice<Vector4> tanArray = new NativeSlice<Vector4>(m_TangentData);
|
||||
|
||||
if (m_EnableTangents)
|
||||
{
|
||||
spriteShapeRenderer.GetChannels(maxArrayCount, out indexArray, out posArray, out uv0Array, out tanArray);
|
||||
}
|
||||
else
|
||||
{
|
||||
spriteShapeRenderer.GetChannels(maxArrayCount, out indexArray, out posArray, out uv0Array);
|
||||
}
|
||||
|
||||
var uTess2D = ValidateUTess2D(m_UTess2D);
|
||||
var spriteShapeJob = new SpriteShapeGenerator() { m_Bounds = bounds, m_PosArray = posArray, m_Uv0Array = uv0Array, m_TanArray = tanArray, m_GeomArray = geomArray, m_IndexArray = indexArray, m_ColliderPoints = m_ColliderData };
|
||||
spriteShapeJob.Prepare(this, m_ActiveShapeParameters, maxArrayCount, shapePoints, shapeMetaData, m_AngleRangeInfoArray, m_EdgeSpriteArray, m_CornerSpriteArray, uTess2D);
|
||||
// Only update Handle for an actual Job is scheduled.
|
||||
m_JobHandle = spriteShapeJob.Schedule();
|
||||
spriteShapeRenderer.Prepare(m_JobHandle, m_ActiveShapeParameters, m_SpriteArray);
|
||||
jobHandle = m_JobHandle;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (spriteShapeGeometryCache && geometryCached)
|
||||
spriteShapeGeometryCache.SetGeometryCache(maxArrayCount, posArray, uv0Array, tanArray, indexArray, geomArray);
|
||||
#endif
|
||||
|
||||
JobHandle.ScheduleBatchedJobs();
|
||||
}
|
||||
|
||||
if (m_DynamicOcclusionOverriden)
|
||||
{
|
||||
spriteShapeRenderer.allowOcclusionWhenDynamic = m_DynamicOcclusionLocal;
|
||||
m_DynamicOcclusionOverriden = false;
|
||||
}
|
||||
shapePoints.Dispose();
|
||||
shapeMetaData.Dispose();
|
||||
return jobHandle;
|
||||
}
|
||||
|
||||
public void BakeCollider()
|
||||
{
|
||||
// Previously this must be explicitly called if using BakeMesh.
|
||||
// But now we do it internally. BakeCollider_CanBeCalledMultipleTimesWithoutJobComplete
|
||||
m_JobHandle.Complete();
|
||||
|
||||
if (m_ColliderData.IsCreated)
|
||||
{
|
||||
if (autoUpdateCollider)
|
||||
{
|
||||
if (hasCollider)
|
||||
{
|
||||
int maxCount = short.MaxValue - 1;
|
||||
float2 last = (float2)0;
|
||||
List<Vector2> m_ColliderSegment = new List<Vector2>();
|
||||
for (int i = 0; i < maxCount; ++i)
|
||||
{
|
||||
float2 now = m_ColliderData[i];
|
||||
if (!math.any(last) && !math.any(now))
|
||||
break;
|
||||
m_ColliderSegment.Add(new Vector2(now.x, now.y));
|
||||
}
|
||||
|
||||
if (edgeCollider != null)
|
||||
edgeCollider.points = m_ColliderSegment.ToArray();
|
||||
if (polygonCollider != null)
|
||||
polygonCollider.points = m_ColliderSegment.ToArray();
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.SceneView.RepaintAll();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
// Dispose Collider as its no longer needed.
|
||||
m_ColliderData.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal void BakeMeshForced()
|
||||
{
|
||||
if (spriteShapeRenderer != null)
|
||||
{
|
||||
var hasSplineChanged = HasSplineDataChanged();
|
||||
if (!spriteShapeRenderer.isVisible && hasSplineChanged)
|
||||
{
|
||||
BakeMesh();
|
||||
Rendering.CommandBuffer rc = new Rendering.CommandBuffer();
|
||||
rc.GetTemporaryRT(0, 256, 256, 0);
|
||||
rc.SetRenderTarget(0);
|
||||
rc.DrawRenderer(spriteShapeRenderer, spriteShapeRenderer.sharedMaterial);
|
||||
rc.ReleaseTemporaryRT(0);
|
||||
Graphics.ExecuteCommandBuffer(rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D GetTextureFromIndex(int index)
|
||||
{
|
||||
if (index == 0)
|
||||
return spriteShape ? spriteShape.fillTexture : null;
|
||||
|
||||
--index;
|
||||
if (index < m_EdgeSpriteArray.Length)
|
||||
return GetSpriteTexture(m_EdgeSpriteArray[index]);
|
||||
|
||||
index -= m_EdgeSpriteArray.Length;
|
||||
return GetSpriteTexture(m_CornerSpriteArray[index]);
|
||||
}
|
||||
|
||||
Texture2D GetSpriteTexture(Sprite sprite)
|
||||
{
|
||||
if (sprite)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
return UnityEditor.Sprites.SpriteUtility.GetSpriteTexture(sprite, sprite.packed);
|
||||
#else
|
||||
return sprite.texture;
|
||||
#endif
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.U2D;
|
||||
using Unity.Collections;
|
||||
|
||||
[Serializable]
|
||||
internal struct SpriteShapeGeometryInfo
|
||||
{
|
||||
[SerializeField]
|
||||
internal int geomIndex;
|
||||
[SerializeField]
|
||||
internal int indexCount;
|
||||
[SerializeField]
|
||||
internal int vertexCount;
|
||||
[SerializeField]
|
||||
internal int spriteIndex;
|
||||
}
|
||||
|
||||
// Simple Cache for SpriteShape Geometry Data.
|
||||
[AddComponentMenu("")]
|
||||
internal class SpriteShapeGeometryCache : MonoBehaviour
|
||||
{
|
||||
|
||||
// Serialized Data.
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
int m_MaxArrayCount;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
Vector3[] m_PosArray = null;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
Vector2[] m_Uv0Array = null;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
Vector4[] m_TanArray = null;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
ushort[] m_IndexArray = null;
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
SpriteShapeGeometryInfo[] m_GeomArray = null;
|
||||
|
||||
// Update set.
|
||||
bool m_RequiresUpdate = false;
|
||||
bool m_RequiresUpload = false;
|
||||
NativeSlice<Vector3> m_PosArrayCache;
|
||||
NativeSlice<Vector2> m_Uv0ArrayCache;
|
||||
NativeSlice<Vector4> m_TanArrayCache;
|
||||
NativeArray<ushort> m_IndexArrayCache;
|
||||
NativeArray<UnityEngine.U2D.SpriteShapeSegment> m_GeomArrayCache;
|
||||
|
||||
internal int maxArrayCount
|
||||
{
|
||||
get { return m_MaxArrayCount; }
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_RequiresUpload = true;
|
||||
m_RequiresUpdate = false;
|
||||
}
|
||||
|
||||
// Set Geometry Cache.
|
||||
internal void SetGeometryCache(int _maxArrayCount, NativeSlice<Vector3> _posArray, NativeSlice<Vector2> _uv0Array, NativeSlice<Vector4> _tanArray, NativeArray<ushort> _indexArray, NativeArray<UnityEngine.U2D.SpriteShapeSegment> _geomArray)
|
||||
{
|
||||
m_RequiresUpdate = true;
|
||||
m_PosArrayCache = _posArray;
|
||||
m_Uv0ArrayCache = _uv0Array;
|
||||
m_TanArrayCache = _tanArray;
|
||||
m_GeomArrayCache = _geomArray;
|
||||
m_IndexArrayCache = _indexArray;
|
||||
m_MaxArrayCount = _maxArrayCount;
|
||||
}
|
||||
|
||||
// Update GeometryCache.
|
||||
internal void UpdateGeometryCache()
|
||||
{
|
||||
bool updateCache = m_RequiresUpdate && m_GeomArrayCache.IsCreated;
|
||||
updateCache = updateCache && m_IndexArrayCache.IsCreated;
|
||||
if (updateCache)
|
||||
{
|
||||
int geomCount = 0;
|
||||
int indexCount = 0;
|
||||
int vertexCount = 0;
|
||||
|
||||
for (int i = 0; (i < m_GeomArrayCache.Length); ++i)
|
||||
{
|
||||
var geom = m_GeomArrayCache[i];
|
||||
indexCount += geom.indexCount;
|
||||
vertexCount += geom.vertexCount;
|
||||
if (geom.vertexCount > 0)
|
||||
geomCount = i + 1;
|
||||
}
|
||||
|
||||
m_GeomArray = new SpriteShapeGeometryInfo[geomCount];
|
||||
NativeArray<SpriteShapeGeometryInfo> geomInfoArray = m_GeomArrayCache.Reinterpret<SpriteShapeGeometryInfo>();
|
||||
SpriteShapeCopyUtility<SpriteShapeGeometryInfo>.Copy(m_GeomArray, geomInfoArray, geomCount);
|
||||
|
||||
m_PosArray = new Vector3[vertexCount];
|
||||
m_Uv0Array = new Vector2[vertexCount];
|
||||
m_TanArray = new Vector4[vertexCount];
|
||||
m_IndexArray = new ushort[indexCount];
|
||||
|
||||
SpriteShapeCopyUtility<Vector3>.Copy(m_PosArray, m_PosArrayCache, vertexCount);
|
||||
SpriteShapeCopyUtility<Vector2>.Copy(m_Uv0Array, m_Uv0ArrayCache, vertexCount);
|
||||
SpriteShapeCopyUtility<Vector4>.Copy(m_TanArray, m_TanArrayCache, vertexCount);
|
||||
SpriteShapeCopyUtility<ushort>.Copy(m_IndexArray, m_IndexArrayCache, indexCount);
|
||||
|
||||
m_MaxArrayCount = (vertexCount > indexCount) ? vertexCount : indexCount;
|
||||
m_RequiresUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal JobHandle Upload(SpriteShapeRenderer sr, SpriteShapeController sc)
|
||||
{
|
||||
|
||||
JobHandle jobHandle = (default);
|
||||
if (m_RequiresUpload)
|
||||
{
|
||||
|
||||
// Update Geometries.
|
||||
NativeArray<SpriteShapeSegment> geomArray = sr.GetSegments(m_GeomArray.Length);
|
||||
NativeArray<SpriteShapeGeometryInfo> geomInfoArray = geomArray.Reinterpret<SpriteShapeGeometryInfo>();
|
||||
geomInfoArray.CopyFrom(m_GeomArray);
|
||||
|
||||
// Update Mesh Data.
|
||||
NativeSlice<Vector3> posArray;
|
||||
NativeSlice<Vector2> uv0Array;
|
||||
NativeArray<ushort> indexArray;
|
||||
|
||||
if (sc.enableTangents)
|
||||
{
|
||||
NativeSlice<Vector4> tanArray;
|
||||
sr.GetChannels(m_MaxArrayCount, out indexArray, out posArray, out uv0Array, out tanArray);
|
||||
SpriteShapeCopyUtility<Vector4>.Copy(tanArray, m_TanArray, m_TanArray.Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
sr.GetChannels(m_MaxArrayCount, out indexArray, out posArray, out uv0Array);
|
||||
}
|
||||
|
||||
SpriteShapeCopyUtility<Vector3>.Copy(posArray, m_PosArray, m_PosArray.Length);
|
||||
SpriteShapeCopyUtility<Vector2>.Copy(uv0Array, m_Uv0Array, m_Uv0Array.Length);
|
||||
SpriteShapeCopyUtility<ushort>.Copy(indexArray, m_IndexArray, m_IndexArray.Length);
|
||||
sr.Prepare(jobHandle, sc.spriteShapeParameters, sc.spriteArray);
|
||||
m_RequiresUpload = false;
|
||||
|
||||
}
|
||||
return jobHandle;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
namespace UnityEngine.U2D
|
||||
{
|
||||
|
||||
namespace UTess
|
||||
{
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
[DebuggerDisplay("Length = {Length}")]
|
||||
internal unsafe struct ArraySlice<T> : System.IEquatable<ArraySlice<T>> where T : struct
|
||||
{
|
||||
[NativeDisableUnsafePtrRestriction] internal byte* m_Buffer;
|
||||
internal int m_Stride;
|
||||
internal int m_Length;
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
internal int m_MinIndex;
|
||||
internal int m_MaxIndex;
|
||||
internal AtomicSafetyHandle m_Safety;
|
||||
#endif
|
||||
|
||||
public ArraySlice(NativeArray<T> array, int start, int length)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (start < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(start), $"Slice start {start} < 0.");
|
||||
if (length < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(length), $"Slice length {length} < 0.");
|
||||
if (start + length > array.Length)
|
||||
throw new ArgumentException(
|
||||
$"Slice start + length ({start + length}) range must be <= array.Length ({array.Length})");
|
||||
m_MinIndex = 0;
|
||||
m_MaxIndex = length - 1;
|
||||
m_Safety = Unity.Collections.LowLevel.Unsafe.NativeArrayUnsafeUtility.GetAtomicSafetyHandle(array);
|
||||
#endif
|
||||
|
||||
m_Stride = UnsafeUtility.SizeOf<T>();
|
||||
var ptr = (byte*) array.GetUnsafePtr() + m_Stride * start;
|
||||
m_Buffer = ptr;
|
||||
m_Length = length;
|
||||
}
|
||||
|
||||
public bool Equals(ArraySlice<T> other)
|
||||
{
|
||||
return m_Buffer == other.m_Buffer && m_Stride == other.m_Stride && m_Length == other.m_Length;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
return obj is ArraySlice<T> && Equals((ArraySlice<T>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int) m_Buffer;
|
||||
hashCode = (hashCode * 397) ^ m_Stride;
|
||||
hashCode = (hashCode * 397) ^ m_Length;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(ArraySlice<T> left, ArraySlice<T> right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ArraySlice<T> left, ArraySlice<T> right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
// These are double-whammy excluded to we can elide bounds checks in the Burst disassembly view
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
void CheckReadIndex(int index)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (index < m_MinIndex || index > m_MaxIndex)
|
||||
FailOutOfRangeError(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
void CheckWriteIndex(int index)
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
if (index < m_MinIndex || index > m_MaxIndex)
|
||||
FailOutOfRangeError(index);
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
|
||||
private void FailOutOfRangeError(int index)
|
||||
{
|
||||
if (index < Length && (m_MinIndex != 0 || m_MaxIndex != Length - 1))
|
||||
throw new System.IndexOutOfRangeException(
|
||||
$"Index {index} is out of restricted IJobParallelFor range [{m_MinIndex}...{m_MaxIndex}] in ReadWriteBuffer.\n" +
|
||||
"ReadWriteBuffers are restricted to only read & write the element at the job index. " +
|
||||
"You can use double buffering strategies to avoid race conditions due to " +
|
||||
"reading & writing in parallel to the same elements from a job.");
|
||||
|
||||
throw new System.IndexOutOfRangeException($"Index {index} is out of range of '{Length}' Length.");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public static unsafe ArraySlice<T> ConvertExistingDataToArraySlice(void* dataPointer, int stride, int length)
|
||||
{
|
||||
if (length < 0)
|
||||
throw new System.ArgumentException($"Invalid length of '{length}'. It must be greater than 0.",
|
||||
nameof(length));
|
||||
if (stride < 0)
|
||||
throw new System.ArgumentException($"Invalid stride '{stride}'. It must be greater than 0.",
|
||||
nameof(stride));
|
||||
|
||||
var newSlice = new ArraySlice<T>
|
||||
{
|
||||
m_Stride = stride,
|
||||
m_Buffer = (byte*) dataPointer,
|
||||
m_Length = length,
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
m_MinIndex = 0,
|
||||
m_MaxIndex = length - 1,
|
||||
#endif
|
||||
};
|
||||
|
||||
return newSlice;
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
CheckReadIndex(index);
|
||||
#endif
|
||||
return UnsafeUtility.ReadArrayElementWithStride<T>(m_Buffer, index, m_Stride);
|
||||
}
|
||||
|
||||
[WriteAccessRequired]
|
||||
set
|
||||
{
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
CheckWriteIndex(index);
|
||||
#endif
|
||||
UnsafeUtility.WriteArrayElementWithStride(m_Buffer, index, m_Stride, value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Stride => m_Stride;
|
||||
public int Length => m_Length;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Unity.2D.SpriteShape.Runtime",
|
||||
"references": [
|
||||
"Unity.2D.Common.Runtime",
|
||||
"Unity.Mathematics",
|
||||
"Unity.Burst"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.burst",
|
||||
"expression": "0.0.1",
|
||||
"define": "ENABLE_SPRITESHAPE_BURST"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
internal class SpriteShapePlaceholder
|
||||
{
|
||||
[Test]
|
||||
public void PlaceHolderTest()
|
||||
{
|
||||
Assert.Pass("SpriteShape tests are in a separate package.");
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Unity.2D.SpriteShape.EditorTests",
|
||||
"references": [
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": true,
|
||||
"precompiledReferences": [
|
||||
"nunit.framework.dll"
|
||||
],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [
|
||||
"UNITY_INCLUDE_TESTS"
|
||||
],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
This package contains third-party software components governed by the license(s) indicated below:
|
||||
---------
|
||||
Component Name: Predicates
|
||||
|
||||
Routines for Arbitrary Precision Floating-point Arithmetic
|
||||
and Fast Robust Geometric Predicates
|
||||
(predicates.c)
|
||||
|
||||
May 18, 1996
|
||||
|
||||
Placed in the public domain by
|
||||
Jonathan Richard Shewchuk
|
||||
---------
|
||||
|
||||
Component Name: CDT
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 Mikola Lysenko
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
---------
|
||||
|
||||
Component Name: LibTessDotNet
|
||||
|
||||
License Type: SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
|
||||
|
||||
|
||||
Copyright (C) 2011 Silicon Graphics, Inc.
|
||||
All Rights Reserved.
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice including the dates of first publication and either this
|
||||
permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
|
||||
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
|
||||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
|
||||
be used in advertising or otherwise to promote the sale, use or other dealings in
|
||||
this Software without prior written authorization from Silicon Graphics, Inc.
|
||||
|
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "com.unity.2d.spriteshape",
|
||||
"version": "6.0.0",
|
||||
"unity": "2021.1",
|
||||
"displayName": "2D SpriteShape",
|
||||
"description": "SpriteShape Runtime & Editor Package contains the tooling and the runtime component that allows you to create very organic looking spline based 2D worlds. It comes with intuitive configurator and a highly performant renderer.",
|
||||
"keywords": [
|
||||
"2d",
|
||||
"shape",
|
||||
"spriteshape",
|
||||
"smartsprite",
|
||||
"spline",
|
||||
"terrain2d"
|
||||
],
|
||||
"category": "2D",
|
||||
"dependencies": {
|
||||
"com.unity.mathematics": "1.1.0",
|
||||
"com.unity.2d.common": "5.0.0",
|
||||
"com.unity.2d.path": "5.0.0",
|
||||
"com.unity.modules.physics2d": "1.0.0"
|
||||
},
|
||||
"samples": [
|
||||
{
|
||||
"displayName": "Sprite Shape Samples",
|
||||
"description": "Samples to get started with SpriteShape",
|
||||
"path": "Samples~/Samples"
|
||||
},
|
||||
{
|
||||
"displayName": "Sprite Shape Extras",
|
||||
"description": "This sample has utility scripts that has various use-cases of SpriteShape",
|
||||
"path": "Samples~/Extras"
|
||||
}
|
||||
],
|
||||
"relatedPackages": {
|
||||
"com.unity.2d.spriteshape.tests": "6.0.0",
|
||||
"com.unity.2d.common.tests": "5.0.0"
|
||||
},
|
||||
"upmCi": {
|
||||
"footprint": "df68cb27271a44bf06e70caba34fc5db6b2618c0"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.cds.internal.unity3d.com/unity/2d.git",
|
||||
"type": "git",
|
||||
"revision": "82ee1598c2f1270bc1081c1f908364e528335bd5"
|
||||
}
|
||||
}
|