365 lines
14 KiB
C#
365 lines
14 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using Unity.Collections;
|
||
|
using Unity.Jobs;
|
||
|
using Unity.Mathematics;
|
||
|
using UnityEditor.U2D.Common;
|
||
|
using UnityEditor.U2D.Layout;
|
||
|
using UnityEngine;
|
||
|
|
||
|
namespace UnityEditor.U2D.Animation
|
||
|
{
|
||
|
internal class GenerateGeometryTool : MeshToolWrapper
|
||
|
{
|
||
|
private const float kWeightTolerance = 0.1f;
|
||
|
private SpriteMeshDataController m_SpriteMeshDataController = new SpriteMeshDataController();
|
||
|
private ITriangulator m_Triangulator;
|
||
|
private IOutlineGenerator m_OutlineGenerator;
|
||
|
private IWeightsGenerator m_WeightGenerator;
|
||
|
private GenerateGeometryPanel m_GenerateGeometryPanel;
|
||
|
|
||
|
internal override void OnCreate()
|
||
|
{
|
||
|
m_Triangulator = new Triangulator();
|
||
|
m_OutlineGenerator = new OutlineGenerator();
|
||
|
m_WeightGenerator = new BoundedBiharmonicWeightsGenerator();
|
||
|
}
|
||
|
|
||
|
public override void Initialize(LayoutOverlay layout)
|
||
|
{
|
||
|
base.Initialize(layout);
|
||
|
|
||
|
m_GenerateGeometryPanel = GenerateGeometryPanel.GenerateFromUXML();
|
||
|
m_GenerateGeometryPanel.skinningCache = skinningCache;
|
||
|
|
||
|
layout.rightOverlay.Add(m_GenerateGeometryPanel);
|
||
|
|
||
|
BindElements();
|
||
|
Hide();
|
||
|
}
|
||
|
|
||
|
private void BindElements()
|
||
|
{
|
||
|
Debug.Assert(m_GenerateGeometryPanel != null);
|
||
|
|
||
|
m_GenerateGeometryPanel.onAutoGenerateGeometry += (float detail, byte alpha, float subdivide) =>
|
||
|
{
|
||
|
var selectedSprite = skinningCache.selectedSprite;
|
||
|
if (selectedSprite != null)
|
||
|
GenerateGeometryForSprites(new[] { selectedSprite }, detail, alpha, subdivide);
|
||
|
};
|
||
|
|
||
|
m_GenerateGeometryPanel.onAutoGenerateGeometryAll += (float detail, byte alpha, float subdivide) =>
|
||
|
{
|
||
|
var sprites = skinningCache.GetSprites();
|
||
|
GenerateGeometryForSprites(sprites, detail, alpha, subdivide);
|
||
|
};
|
||
|
}
|
||
|
|
||
|
void GenerateGeometryForSprites(SpriteCache[] sprites, float detail, byte alpha, float subdivide)
|
||
|
{
|
||
|
var cancelProgress = false;
|
||
|
|
||
|
using (skinningCache.UndoScope(TextContent.generateGeometry))
|
||
|
{
|
||
|
|
||
|
float progressMax = sprites.Length * 4; // for ProgressBar
|
||
|
int validSpriteCount = 0;
|
||
|
|
||
|
//
|
||
|
// Generate Outline
|
||
|
//
|
||
|
for (var i = 0; i < sprites.Length; ++i)
|
||
|
{
|
||
|
var sprite = sprites[i];
|
||
|
if (!sprite.IsVisible())
|
||
|
continue;
|
||
|
|
||
|
Debug.Assert(sprite != null);
|
||
|
var mesh = sprite.GetMesh();
|
||
|
Debug.Assert(mesh != null);
|
||
|
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
validSpriteCount++;
|
||
|
|
||
|
cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.generatingOutline, sprite.name, i / progressMax);
|
||
|
if (cancelProgress)
|
||
|
break;
|
||
|
m_SpriteMeshDataController.OutlineFromAlpha(m_OutlineGenerator, mesh.textureDataProvider, detail / 100f, alpha);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Generate Base Mesh Threaded.
|
||
|
//
|
||
|
const int maxDataCount = 65536;
|
||
|
var spriteList = new List<SpriteJobData>();
|
||
|
var jobHandles = new NativeArray<JobHandle>(validSpriteCount, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||
|
int jobCount = 0;
|
||
|
|
||
|
for (var i = 0; i < sprites.Length; ++i)
|
||
|
{
|
||
|
var sprite = sprites[i];
|
||
|
if (!sprite.IsVisible())
|
||
|
continue;
|
||
|
|
||
|
cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.triangulatingGeometry, sprite.name, 0.25f + (i / progressMax));
|
||
|
if (cancelProgress)
|
||
|
break;
|
||
|
|
||
|
var mesh = sprite.GetMesh();
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
|
||
|
SpriteJobData sd = new SpriteJobData();
|
||
|
sd.spriteMesh = mesh;
|
||
|
sd.vertices = new NativeArray<float2>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||
|
sd.edges = new NativeArray<int2>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||
|
sd.indices = new NativeArray<int>(maxDataCount, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||
|
sd.weights = new NativeArray<BoneWeight>(maxDataCount, Allocator.Persistent);
|
||
|
sd.result = new NativeArray<int4>(1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
|
||
|
sd.result[0] = int4.zero;
|
||
|
spriteList.Add(sd);
|
||
|
if (m_GenerateGeometryPanel.generateWeights)
|
||
|
{
|
||
|
jobHandles[jobCount] = m_SpriteMeshDataController.TriangulateJob(m_Triangulator, sd);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
jobHandles[jobCount] = default(JobHandle);
|
||
|
m_SpriteMeshDataController.Triangulate(m_Triangulator);
|
||
|
}
|
||
|
jobCount++;
|
||
|
}
|
||
|
JobHandle.CombineDependencies(jobHandles).Complete();
|
||
|
|
||
|
//
|
||
|
// Generate Base Mesh Fallback.
|
||
|
//
|
||
|
for (var i = 0; i < spriteList.Count; i++)
|
||
|
{
|
||
|
var sd = spriteList[i];
|
||
|
if (math.all(sd.result[0].xy))
|
||
|
{
|
||
|
sd.spriteMesh.Clear();
|
||
|
|
||
|
var edges = new int2[sd.result[0].z];
|
||
|
var indices = new int[sd.result[0].y];
|
||
|
|
||
|
for (var j = 0; j < sd.result[0].x; ++j)
|
||
|
sd.spriteMesh.AddVertex(sd.vertices[j], default(BoneWeight));
|
||
|
for (var j = 0; j < sd.result[0].y; ++j)
|
||
|
indices[j] = sd.indices[j];
|
||
|
for (var j = 0; j < sd.result[0].z; ++j)
|
||
|
edges[j] = sd.edges[j];
|
||
|
|
||
|
sd.spriteMesh.SetEdges(edges);
|
||
|
sd.spriteMesh.SetIndices(indices);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
m_SpriteMeshDataController.spriteMeshData = sd.spriteMesh;
|
||
|
m_SpriteMeshDataController.Triangulate(m_Triangulator);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Subdivide.
|
||
|
//
|
||
|
|
||
|
jobCount = 0;
|
||
|
if (subdivide > 0f)
|
||
|
{
|
||
|
var largestAreaFactor = subdivide != 0 ? Mathf.Lerp(0.5f, 0.05f, Math.Min(subdivide, 100f) / 100f) : subdivide;
|
||
|
|
||
|
for (var i = 0; i < sprites.Length; ++i)
|
||
|
{
|
||
|
var sprite = sprites[i];
|
||
|
if (!sprite.IsVisible())
|
||
|
continue;
|
||
|
|
||
|
cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.subdividingGeometry, sprite.name, 0.5f + (i / progressMax));
|
||
|
if (cancelProgress)
|
||
|
break;
|
||
|
|
||
|
var mesh = sprite.GetMesh();
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
|
||
|
var sd = spriteList[i];
|
||
|
sd.spriteMesh = mesh;
|
||
|
sd.result[0] = int4.zero;
|
||
|
m_SpriteMeshDataController.Subdivide(m_Triangulator, sd, largestAreaFactor, 0f);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Weight.
|
||
|
//
|
||
|
jobCount = 0;
|
||
|
if (m_GenerateGeometryPanel.generateWeights)
|
||
|
{
|
||
|
|
||
|
for (var i = 0; i < sprites.Length; i++)
|
||
|
{
|
||
|
var sprite = sprites[i];
|
||
|
if (!sprite.IsVisible())
|
||
|
continue;
|
||
|
|
||
|
var mesh = sprite.GetMesh();
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
|
||
|
cancelProgress = EditorUtility.DisplayCancelableProgressBar(TextContent.generatingWeights, sprite.name, 0.75f + (i / progressMax));
|
||
|
if (cancelProgress)
|
||
|
break;
|
||
|
|
||
|
var sd = spriteList[i];
|
||
|
jobHandles[jobCount] = GenerateWeights(sprite, sd);
|
||
|
jobCount++;
|
||
|
}
|
||
|
|
||
|
// Weight
|
||
|
JobHandle.CombineDependencies(jobHandles).Complete();
|
||
|
|
||
|
for (var i = 0; i < sprites.Length; i++)
|
||
|
{
|
||
|
var sprite = sprites[i];
|
||
|
if (!sprite.IsVisible())
|
||
|
continue;
|
||
|
|
||
|
var mesh = sprite.GetMesh();
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
var sd = spriteList[i];
|
||
|
|
||
|
for (var j = 0; j < mesh.vertexCount; ++j)
|
||
|
{
|
||
|
var editableBoneWeight = EditableBoneWeightUtility.CreateFromBoneWeight(sd.weights[j]);
|
||
|
|
||
|
if (kWeightTolerance > 0f)
|
||
|
{
|
||
|
editableBoneWeight.FilterChannels(kWeightTolerance);
|
||
|
editableBoneWeight.Normalize();
|
||
|
}
|
||
|
|
||
|
mesh.vertexWeights[j] = editableBoneWeight;
|
||
|
}
|
||
|
if (null != sprite.GetCharacterPart())
|
||
|
sprite.DeassociateUnusedBones();
|
||
|
m_SpriteMeshDataController.SortTrianglesByDepth();
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < spriteList.Count; i++)
|
||
|
{
|
||
|
var sd = spriteList[i];
|
||
|
sd.result.Dispose();
|
||
|
sd.indices.Dispose();
|
||
|
sd.edges.Dispose();
|
||
|
sd.vertices.Dispose();
|
||
|
sd.weights.Dispose();
|
||
|
}
|
||
|
|
||
|
if (!cancelProgress)
|
||
|
{
|
||
|
skinningCache.vertexSelection.Clear();
|
||
|
foreach(var sprite in sprites)
|
||
|
skinningCache.events.meshChanged.Invoke(sprite.GetMesh());
|
||
|
}
|
||
|
|
||
|
EditorUtility.ClearProgressBar();
|
||
|
}
|
||
|
|
||
|
if(cancelProgress)
|
||
|
Undo.PerformUndo();
|
||
|
}
|
||
|
|
||
|
protected override void OnActivate()
|
||
|
{
|
||
|
base.OnActivate();
|
||
|
UpdateButton();
|
||
|
Show();
|
||
|
skinningCache.events.selectedSpriteChanged.AddListener(OnSelectedSpriteChanged);
|
||
|
}
|
||
|
|
||
|
protected override void OnDeactivate()
|
||
|
{
|
||
|
base.OnDeactivate();
|
||
|
Hide();
|
||
|
skinningCache.events.selectedSpriteChanged.RemoveListener(OnSelectedSpriteChanged);
|
||
|
}
|
||
|
|
||
|
private void Show()
|
||
|
{
|
||
|
m_GenerateGeometryPanel.SetHiddenFromLayout(false);
|
||
|
}
|
||
|
|
||
|
private void Hide()
|
||
|
{
|
||
|
m_GenerateGeometryPanel.SetHiddenFromLayout(true);
|
||
|
}
|
||
|
|
||
|
private void UpdateButton()
|
||
|
{
|
||
|
var selectedSprite = skinningCache.selectedSprite;
|
||
|
|
||
|
if (selectedSprite == null)
|
||
|
m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Multiple);
|
||
|
else
|
||
|
m_GenerateGeometryPanel.SetMode(GenerateGeometryPanel.GenerateMode.Single);
|
||
|
}
|
||
|
|
||
|
private void OnSelectedSpriteChanged(SpriteCache sprite)
|
||
|
{
|
||
|
UpdateButton();
|
||
|
}
|
||
|
|
||
|
private JobHandle GenerateWeights(SpriteCache sprite, SpriteJobData sd)
|
||
|
{
|
||
|
Debug.Assert(sprite != null);
|
||
|
|
||
|
var mesh = sprite.GetMesh();
|
||
|
|
||
|
Debug.Assert(mesh != null);
|
||
|
|
||
|
using (new DefaultPoseScope(skinningCache.GetEffectiveSkeleton(sprite)))
|
||
|
{
|
||
|
sprite.AssociatePossibleBones();
|
||
|
return GenerateWeights(mesh, sd);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// todo: Remove. This function seems dubious. Only associate if boneCount is 0 or if boneCount 1 and first bone matches ?
|
||
|
private bool NeedsAssociateBones(CharacterPartCache characterPart)
|
||
|
{
|
||
|
if (characterPart == null)
|
||
|
return false;
|
||
|
|
||
|
var skeleton = characterPart.skinningCache.character.skeleton;
|
||
|
|
||
|
return characterPart.boneCount == 0 ||
|
||
|
(characterPart.boneCount == 1 && characterPart.GetBone(0) == skeleton.GetBone(0));
|
||
|
}
|
||
|
|
||
|
private JobHandle GenerateWeights(MeshCache mesh, SpriteJobData sd)
|
||
|
{
|
||
|
Debug.Assert(mesh != null);
|
||
|
|
||
|
m_SpriteMeshDataController.spriteMeshData = mesh;
|
||
|
var JobHandle = m_SpriteMeshDataController.CalculateWeightsJob(m_WeightGenerator, null, kWeightTolerance, sd);
|
||
|
|
||
|
return JobHandle;
|
||
|
}
|
||
|
|
||
|
protected override void OnGUI()
|
||
|
{
|
||
|
m_MeshPreviewBehaviour.showWeightMap = m_GenerateGeometryPanel.generateWeights;
|
||
|
m_MeshPreviewBehaviour.overlaySelected = m_GenerateGeometryPanel.generateWeights;
|
||
|
|
||
|
skeletonTool.skeletonStyle = SkeletonStyles.Default;
|
||
|
|
||
|
if (m_GenerateGeometryPanel.generateWeights)
|
||
|
skeletonTool.skeletonStyle = SkeletonStyles.WeightMap;
|
||
|
|
||
|
DoSkeletonGUI();
|
||
|
}
|
||
|
}
|
||
|
}
|