rimworld-animation-studio/Library/PackageCache/com.unity.2d.spriteshape@3.0.14/Runtime/SpriteShapeController.cs
2022-09-13 00:36:34 -05:00

801 lines
27 KiB
C#

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;
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;
#region GetSet
internal bool geometryCached
{
get { return m_GeometryCached; }
}
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 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_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 OnWillRenderObject()
{
BakeCollider();
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 != spriteShapeGeometryCache.maxArrayCount && 0 != m_ActiveSplineHash)
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<SpriteShapeMetaData> GetSpriteShapeMetaData()
{
int pointCount = spline.GetPointCount();
NativeArray<SpriteShapeMetaData> shapeMetaData = new NativeArray<SpriteShapeMetaData>(pointCount, Allocator.Temp);
for (int i = 0; i < pointCount; ++i)
{
SpriteShapeMetaData metaData = shapeMetaData[i];
metaData.height = m_Spline.GetHeight(i);
metaData.spriteIndex = (uint)m_Spline.GetSpriteIndex(i);
metaData.corner = m_Spline.GetCorner(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<SpriteShapeMetaData> shapeMetaData = GetSpriteShapeMetaData();
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 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);
jobHandle = spriteShapeJob.Schedule();
spriteShapeRenderer.Prepare(jobHandle, m_ActiveShapeParameters, m_SpriteArray);
#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()
{
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();
}
}
// Dispose Collider as its no longer needed.
m_ColliderData.Dispose();
#if UNITY_EDITOR
if (UnityEditor.SceneView.lastActiveSceneView != null)
UnityEditor.SceneView.lastActiveSceneView.Repaint();
#endif
}
}
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;
}
}
}