300 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
// ver 1.0.1
// Copyright (c) 2019 gatosyocora
namespace VRCDeveloperTool
{
public class SubMeshDeleter : EditorWindow
{
private SkinnedMeshRenderer renderer;
private List<SubMeshInfo> subMeshList;
private int triangleCount = 0;
private string saveFolder = "Assets/";
private bool isOpenedSubMesh = true;
private Vector2 subMeshScrollPos = Vector2.zero;
[MenuItem("VRCDeveloperTool/Mesh/SubMesh Deleter")]
private static void Open()
{
GetWindow<SubMeshDeleter>("SubMeshDeleter");
}
private void OnEnable()
{
renderer = null;
subMeshList = null;
triangleCount = 0;
}
private void OnGUI()
{
using (var check = new EditorGUI.ChangeCheckScope())
{
renderer = EditorGUILayout.ObjectField(
"SkinnedMeshRenderer",
renderer,
typeof(SkinnedMeshRenderer),
true
) as SkinnedMeshRenderer;
if (check.changed)
{
if (renderer != null)
{
var mesh = renderer.sharedMesh;
if (mesh != null)
{
subMeshList = GetSubMeshList(mesh);
triangleCount = GetMeshTriangleCount(mesh);
saveFolder = GetMeshPath(mesh);
}
}
else
{
subMeshList = null;
}
}
}
if (subMeshList != null)
{
isOpenedSubMesh = EditorGUILayout.Foldout(isOpenedSubMesh, "SubMesh");
if (isOpenedSubMesh)
{
using (var scroll = new EditorGUILayout.ScrollViewScope(subMeshScrollPos))
{
subMeshScrollPos = scroll.scrollPosition;
for (int i = 0; i < subMeshList.Count(); i++)
{
subMeshList[i].selected = EditorGUILayout.ToggleLeft("subMesh " + (i + 1) + "(" + renderer.sharedMaterials[i].name + "):" + subMeshList[i].triangleCount, subMeshList[i].selected);
}
}
}
}
EditorGUILayout.LabelField("Triangle Count", triangleCount+"");
using (new EditorGUILayout.HorizontalScope())
{
EditorGUILayout.LabelField("Mesh SaveFolder", saveFolder);
if (GUILayout.Button("Select Folder", GUILayout.Width(100)))
{
saveFolder = EditorUtility.OpenFolderPanel("Select saved folder", saveFolder, "");
var match = Regex.Match(saveFolder, @"Assets/.*");
saveFolder = match.Value + "/";
if (saveFolder == "/") saveFolder = "Assets/";
}
}
using (new EditorGUI.DisabledGroupScope(subMeshList == null || subMeshList.Count() <= 1))
{
if (GUILayout.Button("Delete SubMesh"))
{
DeleteSelectedSubMesh(renderer, subMeshList);
var mesh = renderer.sharedMesh;
if (mesh != null)
{
subMeshList = GetSubMeshList(mesh);
triangleCount = GetMeshTriangleCount(mesh);
}
}
}
}
/// <summary>
/// 選択中のサブメッシュを削除する
/// </summary>
/// <param name="renderer"></param>
/// <param name="subMeshList"></param>
/// <returns></returns>
private bool DeleteSelectedSubMesh(SkinnedMeshRenderer renderer, List<SubMeshInfo> subMeshList)
{
// 削除する頂点インデックスのリスト(読み取り専用, 降順)
var deleteVerticesIndicesUniqueDescending
= subMeshList
.Where(x => x.selected)
.SelectMany(x => x.verticesIndices)
.Distinct()
.OrderByDescending(x => x)
.ToList()
.AsReadOnly();
// 削除するサブメッシュのインデックスのリスト
var deleteSubMeshIndexList
= subMeshList
.Select((value, index) => new { Value = value, Index = index })
.Where(x => x.Value.selected)
.Select(x => x.Index)
.ToList()
.AsReadOnly();
var mesh = renderer.sharedMesh;
var mesh_custom = Instantiate(mesh);
mesh_custom.Clear();
// 頂点を削除
var vertices = mesh.vertices.ToList();
var boneWeights = mesh.boneWeights.ToList();
var uvs = mesh.uv.ToList();
var normals = mesh.normals.ToList();
var tangents = mesh.tangents.ToList();
var uv2s = mesh.uv2.ToList();
var uv3s = mesh.uv3.ToList();
var uv4s = mesh.uv4.ToList();
foreach (var deleteVertexIndex in deleteVerticesIndicesUniqueDescending)
{
vertices.RemoveAt(deleteVertexIndex);
boneWeights.RemoveAt(deleteVertexIndex);
normals.RemoveAt(deleteVertexIndex);
tangents.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uvs.Count())
uvs.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv2s.Count())
uv2s.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv3s.Count())
uv3s.RemoveAt(deleteVertexIndex);
if (deleteVertexIndex < uv4s.Count())
uv4s.RemoveAt(deleteVertexIndex);
}
mesh_custom.SetVertices(vertices);
mesh_custom.boneWeights = boneWeights.ToArray();
mesh_custom.normals = normals.ToArray();
mesh_custom.tangents = tangents.ToArray();
mesh_custom.SetUVs(0, uvs);
mesh_custom.SetUVs(1, uv2s);
mesh_custom.SetUVs(2, uv3s);
mesh_custom.SetUVs(3, uv4s);
// サブメッシュごとにポリゴンを処理
mesh_custom.subMeshCount = mesh.subMeshCount - deleteSubMeshIndexList.Count();
var subMeshNumber = 0;
for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
if (deleteSubMeshIndexList.Contains(subMeshIndex)) continue;
var subMeshTriangles = mesh.GetTriangles(subMeshIndex);
// インデックスがずれるので各頂点への対応付けが必要
// インデックスが大きいものから順に処理していく
// O(n*m)
foreach (var deleteVerticesIndex in deleteVerticesIndicesUniqueDescending) // n
for (int i = 0; i < subMeshTriangles.Count(); i++) // m
if (subMeshTriangles[i] > deleteVerticesIndex)
subMeshTriangles[i]--;
mesh_custom.SetTriangles(subMeshTriangles, subMeshNumber++);
}
// BlendShapeを設定する
string blendShapeName;
float frameWeight;
var deltaVertices = new Vector3[mesh.vertexCount];
var deltaNormals = new Vector3[mesh.vertexCount];
var deltaTangents = new Vector3[mesh.vertexCount];
List<Vector3> deltaVerticesList, deltaNormalsList, deltaTangentsList;
for (int blendshapeIndex = 0; blendshapeIndex < mesh.blendShapeCount; blendshapeIndex++)
{
blendShapeName = mesh.GetBlendShapeName(blendshapeIndex);
frameWeight = mesh.GetBlendShapeFrameWeight(blendshapeIndex, 0);
mesh.GetBlendShapeFrameVertices(blendshapeIndex, 0, deltaVertices, deltaNormals, deltaTangents);
deltaVerticesList = deltaVertices.ToList();
deltaNormalsList = deltaNormals.ToList();
deltaTangentsList = deltaTangents.ToList();
foreach (var deleteVertexIndex in deleteVerticesIndicesUniqueDescending)
{
deltaVerticesList.RemoveAt(deleteVertexIndex);
deltaNormalsList.RemoveAt(deleteVertexIndex);
deltaTangentsList.RemoveAt(deleteVertexIndex);
}
mesh_custom.AddBlendShapeFrame(blendShapeName, frameWeight,
deltaVerticesList.ToArray(),
deltaNormalsList.ToArray(),
deltaTangentsList.ToArray());
}
AssetDatabase.CreateAsset(mesh_custom, AssetDatabase.GenerateUniqueAssetPath(saveFolder + mesh.name + "_deleteSubmesh.asset"));
AssetDatabase.SaveAssets();
Undo.RecordObject(renderer, "Change mesh " + mesh_custom.name);
renderer.sharedMesh = mesh_custom;
// 削除したサブメッシュのマテリアルを参照から外す
var materials = renderer.sharedMaterials.ToList();
for (var index = materials.Count() - 1; index >= 0; index--)
if (deleteSubMeshIndexList.Contains(index))
materials.RemoveAt(index);
renderer.sharedMaterials = materials.ToArray();
return true;
}
/// <summary>
/// サブメッシュのリストを取得する
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
private List<SubMeshInfo> GetSubMeshList(Mesh mesh)
{
List<SubMeshInfo> subMeshList = new List<SubMeshInfo>();
for (int subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; subMeshIndex++)
{
var meshInfo = new SubMeshInfo(mesh, subMeshIndex);
subMeshList.Add(meshInfo);
}
return subMeshList;
}
/// <summary>
/// Meshのポリゴン数を取得する
/// </summary>
/// <param name="mesh"></param>
/// <returns></returns>
private int GetMeshTriangleCount(Mesh mesh)
{
return mesh.triangles.Length / 3;
}
/// <summary>
/// mesh保存先のパスを取得する
/// </summary>
/// <param name="Mesh"></param>
/// <returns></returns>
private string GetMeshPath(Mesh mesh)
{
return Path.GetDirectoryName(AssetDatabase.GetAssetPath(mesh))+"/";
}
public class SubMeshInfo
{
public int subMeshIndex;
public int[] verticesIndices;
public int vertexCount;
public int triangleCount;
public bool selected = false;
public SubMeshInfo(Mesh mesh, int subMeshIndex)
{
this.subMeshIndex = subMeshIndex;
this.verticesIndices = mesh.GetIndices(subMeshIndex);
vertexCount = verticesIndices.Length;
triangleCount = mesh.GetTriangles(subMeshIndex).Length / 3;
}
}
}
}