Files
SimUL/Assets/UMA/Core/Decals/Scripts/SimpleDecal.cs
T
2025-01-07 18:54:46 +02:00

642 lines
22 KiB
C#

using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
using System.IO;
using UnityEngine.SceneManagement;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace UMA
{
public class SimpleDecal : MonoBehaviour, IUMAEventHookup
{
// These need to be set by the editor.
// ****************************************************************************
public int[] boneHashes; // hash of bone names
public string[] boneNames; // the names of the bones.
public byte[] bonesPerVertex; // 1 per vertex
public BoneWeight1[] capturedBoneWeights; // weight / boneIndex
// ****************************************************************************
public BoneWeight1[] finalBoneWeights = new BoneWeight1[0]; // weight / boneIndex
private Dictionary<int, int> NameToBone = new Dictionary<int, int>();
Vector3[] translated = new Vector3[0];
public UMAMeshData meshData;
public Vector3 Offset = Vector3.zero;
public Vector3 Rotation = Vector3.zero;
// public Quaternion Orientation = Quaternion.identity;
// temp
private GameObject vmarker;
private GameObject sceneRoot;
private Scene editorScene;
private Vector3 InitialSpot;
public void Configure(string[] _boneNames, int[] _boneHashes, byte[] _bonesPerVertex, BoneWeight1[] _boneWeights)
{
// this
boneHashes = _boneHashes;
boneNames = _boneNames;
capturedBoneWeights = _boneWeights;
bonesPerVertex = _bonesPerVertex;
}
#if UNITY_EDITOR
public void SaveDecal(string Name, string newAssetPath, bool addToLibrary, SkinnedMeshRenderer smr, DecalDefinition decal,GameObject marker1, Scene editScene,GameObject Root)
{
if (newAssetPath.StartsWith(Application.dataPath))
{
newAssetPath = "Assets"+ newAssetPath.Substring(Application.dataPath.Length);
}
string meshPath = Path.Combine(newAssetPath, Name + "_Mesh.asset");
string prefabPath = Path.Combine(newAssetPath, Name+".prefab");
string slotPath = Path.Combine(newAssetPath, Name + ".asset");
/*
MeshFilter mf = decal.GetComponent<MeshFilter>();
AssetDatabase.CreateAsset(mf.mesh, meshPath );
AssetDatabase.SaveAssets();
*/
InitialSpot = decal.WorldImpactPoint;
vmarker = marker1;
editScene = editorScene;
sceneRoot = Root;
meshData = new UMAMeshData();
#if USE_TestMesh
MeshFilter mf = this.gameObject.GetComponent<MeshFilter>();
meshData.RetrieveDataFromUnityMesh(mf.sharedMesh);
// local to world (object)
// world to local (root)
Matrix4x4 mat = new Matrix4x4();
Transform root = smr.rootBone;
if (root == null)
{
foreach(Transform t in smr.transform.parent)
{
if (t.name.ToLower() == "root")
{
root = t;
break;
}
}
}
if (root == null)
{
foreach (Transform t in smr.transform.parent)
{
if (t.gameObject.GetComponent<SkinnedMeshRenderer>() == null)
{
// Maybe it's this one?
if (t.childCount > 0)
{
root = t;
break;
}
}
}
}
if (root == null)
{
mat.SetTRS(Vector3.zero, Quaternion.identity, Vector3.one);
}
else
{
mat.SetTRS(Vector3.zero, root.localRotation, Vector3.one);
}
/*
for(int i=0;i<meshData.vertices.Length;i++)
{
meshData.vertices[i] = mat.inverse * meshData.vertices[i];
}
*/
meshData.UpdateBones(smr.rootBone, smr.bones);
meshData.ManagedBoneWeights = this.finalBoneWeights; // Not right
meshData.ManagedBonesPerVertex = this.bonesPerVertex;
meshData.bindPoses = smr.sharedMesh.bindposes;
meshData.SlotName = "Decal";
meshData.clothSkinningSerialized = new Vector2[0];
#else
// get all the triangles inside the radius that face the ray.
meshData.subMeshCount = 1;
meshData.submeshes = new SubMeshTriangles[1];
meshData.submeshes[0].SetTriangles(AccumulateTriangles(decal.bakedMesh,decal.planesInWorldSpace,smr,decal.WorldImpactPoint));
meshData.vertices = smr.sharedMesh.vertices;
meshData.normals = smr.sharedMesh.normals;
meshData.uv = new Vector2[smr.sharedMesh.vertices.Length];
meshData.uv2 = smr.sharedMesh.uv2;
meshData.uv3 = smr.sharedMesh.uv3;
meshData.uv4 = smr.sharedMesh.uv4;
meshData.colors32 = smr.sharedMesh.colors32;
meshData.vertexCount = meshData.vertices.Length;
meshData.tangents = smr.sharedMesh.tangents;
meshData.bindPoses = smr.sharedMesh.bindposes;
meshData.ManagedBoneWeights = smr.sharedMesh.GetAllBoneWeights().ToArray();
meshData.ManagedBonesPerVertex = smr.sharedMesh.GetBonesPerVertex().ToArray();
meshData.SlotName = "Decal";
meshData.clothSkinningSerialized = new Vector2[0];
ProjectUV(meshData, decal.bakedMesh, decal.planesInWorldSpace,smr);
#endif
gameObject.name = Name;
GameObject prefab = PrefabUtility.SaveAsPrefabAsset(this.gameObject, prefabPath);
SlotDataAsset sda = CustomAssetUtility.CreateAsset<SlotDataAsset>(slotPath, false, Name);
sda.slotName = Name;
sda.SlotObject = prefab;
/*
mf = prefab.GetComponent<MeshFilter>();
mf.mesh = AssetDatabase.LoadAssetAtPath<Mesh>(meshPath);
SkinnedMeshRenderer smr = prefab.GetComponent<SkinnedMeshRenderer>();
smr.sharedMesh = mf.mesh;
*/
EditorUtility.SetDirty(sda);
AssetDatabase.SaveAssets();
if (addToLibrary)
{
UMAAssetIndexer.Instance.AddAsset(typeof(SlotDataAsset),Name, slotPath, sda);
EditorUtility.DisplayDialog("UMA", "Decal Created and added to library", "OK");
}
else
{
EditorUtility.DisplayDialog("UMA", "Decal Created. Don't forget to add it to the library", "OK");
}
}
private Vector2 GetUV(Plane[] planesinWS,Vector3 vertinWS)
{
Plane u0 = planesinWS[0];
Plane u1 = planesinWS[1];
Plane v0 = planesinWS[2];
Plane v1 = planesinWS[3];
float ud0 = u0.GetDistanceToPoint(vertinWS);
float ud1 = u1.GetDistanceToPoint(vertinWS);
float vd0 = v0.GetDistanceToPoint(vertinWS);
float vd1 = v1.GetDistanceToPoint(vertinWS);
float U_One = Mathf.Abs((u0.ClosestPointOnPlane(vertinWS) - u1.ClosestPointOnPlane(vertinWS)).magnitude);
float V_One = Mathf.Abs((v0.ClosestPointOnPlane(vertinWS) - v1.ClosestPointOnPlane(vertinWS)).magnitude);
return new Vector2(ud0 / U_One, vd0 / V_One);
}
/// <summary>
/// planes needs to be in worldspace!!!!!
/// </summary>
/// <param name="meshData"></param>
/// <param name="bakedMesh"></param>
/// <param name="planes"></param>
/// <param name="smr"></param>
private void ProjectUV(UMAMeshData meshData, Mesh bakedMesh, Plane[] planes, SkinnedMeshRenderer smr)
{
Matrix4x4 mat = smr.gameObject.transform.localToWorldMatrix;
NativeArray<int> tris = meshData.submeshes[0].GetTriangles();
for (int tri = 0; tri < tris.Length; tri+=3)
{
int v0index = tris[tri];
int v1index = tris[tri+1];
int v2index = tris[tri+2];
Vector3 v0 = mat * meshData.vertices[v0index];
Vector3 v1 = mat * meshData.vertices[v1index];
Vector3 v2 = mat * meshData.vertices[v2index];
Vector2 uv0 = GetUV(planes, v0);
Vector2 uv1 = GetUV(planes, v1);
Vector2 uv2 = GetUV(planes, v2);
meshData.uv[v0index] = uv0;
meshData.uv[v1index] = uv1;
meshData.uv[v2index] = uv2;
}
}
private bool PlanesContains(Plane[] planes,Vector3 vert)
{
foreach(Plane p in planes)
{
float dist = p.GetDistanceToPoint(vert);
if (dist < 0.0f)
{
return false;
}
}
return true;
}
private int[] AccumulateTriangles(Mesh bakedMesh, Plane[] planes, SkinnedMeshRenderer smr,Vector3 worldPoint)
{
//List<int> insideVertexes = new List<int>();
HashSet<int> insideVertexes = new HashSet<int>();
List<int> newTriangles = new List<int>();
/* Vector3 VertMax = new Vector3();
for(int i=0;i<bakedMesh.vertices.Length;i++)
{
Vector3 vert = smr.gameObject.transform.TransformPoint(bakedMesh.vertices[i]);
if (vert.y > VertMax.y)
{
VertMax = vert;
}
if (PlanesContains(planes,vert))
{
insideVertexes.Add(i);
}
}
GameObject.Instantiate(vmarker, VertMax, Quaternion.identity,sceneRoot.transform); */
for (int i=0;i<bakedMesh.subMeshCount;i++)
{
int[] triangles = bakedMesh.GetTriangles(i);
for (int tri = 0; tri < triangles.Length; tri += 3)
{
bool isAffected = false;
if (TriangleIntersects(tri, triangles, bakedMesh, smr, planes, worldPoint))
{
isAffected = true;
}
//if (insideVertexes.Contains(triangles[tri])) isAffected = true;
//if (insideVertexes.Contains(triangles[tri+1])) isAffected = true;
//if (insideVertexes.Contains(triangles[tri+2])) isAffected = true;
// if any indexes are inside the space,
// then we need that triangle.
if (isAffected)
{
newTriangles.Add(triangles[tri]);
newTriangles.Add(triangles[tri+1]);
newTriangles.Add(triangles[tri+2]);
}
}
}
return newTriangles.ToArray();
}
private bool TriangleIntersects(int tri, int[] triangles, Mesh bakedMesh, SkinnedMeshRenderer smr,Plane[] planes, Vector3 worldPoint)
{
// if all vertexes are on outside U1 or U2
// if all vertexes are on outside V1 or V2
// return false
//
// else return true.
Transform transform = smr.gameObject.transform;
int backu1count = 0;
int backu2count = 0;
int backv1count = 0;
int backv2count = 0;
int backf1count = 0;
int backb1count = 0;
Plane u1 = planes[0];
Plane u2 = planes[1];
Plane v1 = planes[2];
Plane v2 = planes[3];
Plane f1 = planes[4];
Plane b1 = planes[5];
for (int i=0;i<3;i++)
{
int vertexnum = triangles[tri + i];
Vector3 worldvert = transform.TransformPoint(bakedMesh.vertices[vertexnum]);
float u1dist = u1.GetDistanceToPoint(worldvert);
float u2dist = u2.GetDistanceToPoint(worldvert);
float v1dist = v1.GetDistanceToPoint(worldvert);
float v2dist = v2.GetDistanceToPoint(worldvert);
float f1dist = f1.GetDistanceToPoint(worldvert);
float b1dist = b1.GetDistanceToPoint(worldvert);
if (u1dist < 0)
{
backu1count++;
}
if (u2dist < 0)
{
backu2count++;
}
if (v1dist < 0)
{
backv1count++;
}
if (v2dist < 0)
{
backv2count++;
}
if (f1dist < 0)
{
backf1count++;
}
if (b1dist < 0)
{
backb1count++;
}
}
// Calculate plane from verts.
// if ALL vertexes are "too far", then do not include
if (backu1count == 3 || backu2count == 3)
{
return false;
}
if (backv1count == 3 || backv2count == 3)
{
return false;
}
if (backb1count == 3 || backf1count == 3)
{
return false;
}
// Get the triangle in world space
Vector3 t1 = transform.TransformPoint(bakedMesh.vertices[triangles[tri]]);
Vector3 t2 = transform.TransformPoint(bakedMesh.vertices[triangles[tri+1]]);
Vector3 t3 = transform.TransformPoint(bakedMesh.vertices[triangles[tri+2]]);
// if it faces away, don't make a decal
Plane TriWorldPlane = new Plane(t1, t2, t3);
if (TriWorldPlane.GetDistanceToPoint(worldPoint) < 0.0f)
{
return false;
}
// in the volume.
// faces the indicator.
return true;
}
#endif
#region runtime
public void UpdateBones(UMAData umaData)
{
Dictionary<int, int> HashToPosition = new Dictionary<int, int>();
SkinnedMeshRenderer renderer = umaData.GetRenderer(0);
// No new bones added
if (boneHashes.Length == renderer.bones.Length)
{
finalBoneWeights = new BoneWeight1[0];
return;
}
// build translation table if needed
// if (NameToBone.Count != renderer.bones.Length)
// {
NameToBone.Clear();
for (int i = 0; i < renderer.bones.Length; i++)
{
Transform t = renderer.bones[i];
NameToBone.Add(UMAUtils.StringToHash(t.name), i);
}
// }
// new bones added... need to translate
finalBoneWeights = new BoneWeight1[capturedBoneWeights.Length];
for (int i = 0; i < capturedBoneWeights.Length; i++)
{
BoneWeight1 oldbw = capturedBoneWeights[i];
int Hash = capturedBoneWeights[i].boneIndex;
if (umaData.skeleton.boneHashData.ContainsKey(Hash))
{
var boneData = umaData.skeleton.boneHashData[Hash];
if (NameToBone.ContainsKey(boneData.boneNameHash))
{
finalBoneWeights[i].boneIndex = NameToBone[boneData.boneNameHash];
finalBoneWeights[i].weight = capturedBoneWeights[i].weight;
//Debug.Log($"UMA Bone Found with hash {boneData.boneNameHash} name {boneData.boneTransform.name}");
}
else
{
Debug.LogError($"UMA Bone not found with hash {boneData.boneNameHash} name {boneData.boneTransform.name}");
}
}
else
{
Debug.LogError($"Decal bone not found with hash {Hash}");
}
/*
int Hash = boneHashes[boneWeights[i].boneIndex];
int Hash = capturedBoneWeights[i].boneIndex;
if (umaData.skeleton.boneHashData.ContainsKey(Hash))
{
var boneData = umaData.skeleton.boneHashData[Hash];
if (NameToBone.ContainsKey(boneData.boneNameHash))
{
finalBoneWeights[i].boneIndex = NameToBone[boneData.boneNameHash];
finalBoneWeights[i].weight = capturedBoneWeights[i].weight;
Debug.Log($"UMA Bone Found with hash {boneData.boneNameHash} name {boneData.boneTransform.name}");
}
else
{
Debug.LogError($"UMA Bone not found with hash {boneData.boneNameHash} name {boneData.boneTransform.name}");
}
}
else
{
Debug.LogError($"Decal bone not found with hash {Hash}");
} */
}
}
public bool invert;
public bool root;
public bool global;
public bool position;
Matrix4x4 GetBoneTransform(UMAData umaData)
{
Matrix4x4 mat = Matrix4x4.identity;
Quaternion rot = Quaternion.Euler(Rotation);
mat.SetTRS(Offset, rot, Vector3.one);
if (position)
{
Transform pos = umaData.skeleton.GetBoneTransform(UMAUtils.StringToHash("Position"));
if (pos != null)
{
Matrix4x4 posMat = new Matrix4x4();
posMat.SetTRS(pos.localPosition, Quaternion.identity /*pos.localRotation*/, Vector3.one);
if (invert)
{
posMat = posMat.inverse;
}
mat = mat * posMat;
}
else
{
Debug.Log("Position bone not found?");
}
}
if (global)
{
Transform global = umaData.skeleton.GetBoneTransform(UMAUtils.StringToHash("Global"));
if (global != null)
{
Matrix4x4 globalMat = new Matrix4x4();
globalMat.SetTRS(global.localPosition, Quaternion.identity/*global.localRotation*/, Vector3.one);
if (invert)
{
globalMat = globalMat.inverse;
}
mat = mat * globalMat;
}
else
{
Debug.Log("Global bone not found?");
}
}
if (root)
{
Transform root = umaData.umaRoot.transform;
if (root != null)
{
Matrix4x4 rootmat = new Matrix4x4();
rootmat.SetTRS(Vector3.zero, root.localRotation, Vector3.one);
if (invert)
{
rootmat = rootmat.inverse;
}
mat = mat * rootmat;
}
else
{
Debug.Log("Root bone not found?");
}
}
return mat;
}
Vector3[] TranslateVertices(Vector3[] verts, UMAData umaData)
{
Vector3[] tverts = new Vector3[verts.Length];
Matrix4x4 mat = GetBoneTransform(umaData);
for(int i=0;i<verts.Length;i++)
{
tverts[i] = mat * (verts[i]+Offset);
}
return tverts;
}
private void ApplyToMesh(Mesh mesh,UMAData umaData)
{
mesh.subMeshCount = 1;
mesh.triangles = new int[0];
mesh.vertices = TranslateVertices(meshData.vertices,umaData);
mesh.normals = meshData.normals;
mesh.tangents = meshData.tangents;
mesh.uv = meshData.uv;
mesh.uv2 = meshData.uv2;
mesh.uv3 = meshData.uv3;
mesh.uv4 = meshData.uv4;
mesh.colors32 = meshData.colors32;
mesh.bindposes = meshData.bindPoses;
var subMeshCount = meshData.submeshes.Length;
mesh.subMeshCount = subMeshCount;
for (int i = 0; i < subMeshCount; i++)
{
mesh.SetIndices(meshData.submeshes[i].GetTriangles(), MeshTopology.Triangles, i);
}
mesh.RecalculateBounds();
}
public GameObject ApplyTo(UMAData umaData, SkinnedMeshRenderer baseRenderer,GameObject slotObject)
{
// instantiate a new one here???
GameObject newDecal = GameObject.Instantiate(slotObject, umaData.transform);
SkinnedMeshRenderer smr = newDecal.GetComponent<SkinnedMeshRenderer>();
if (smr == null)
{
Debug.LogWarning("Unable to instantiate decal - no SMR");
return null;
}
Mesh m = new Mesh();
ApplyToMesh(m,umaData);
// Copy bindposes from main.
// copy bones from main.
smr.sharedMesh = m;
smr.sharedMesh.bindposes = baseRenderer.sharedMesh.bindposes;
smr.bones = baseRenderer.bones;
smr.sharedMesh = m;
smr.rootBone = baseRenderer.rootBone;
/* OLD BANDAGE WAY
NativeArray<byte> bpv = new NativeArray<byte>(bonesPerVertex, Allocator.Temp);
NativeArray<BoneWeight1> bweights;
bweights = new NativeArray<BoneWeight1>(finalBoneWeights, Allocator.Temp);
smr.sharedMesh.SetBoneWeights(bpv, bweights); */
smr.gameObject.SetActive(false);
smr.gameObject.SetActive(true);
return newDecal;
}
public void Begun(UMAData umaData)
{
}
public void Completed(UMAData umaData,GameObject slotObject)
{
UpdateBones(umaData);
ApplyTo(umaData, umaData.GetRenderer(0), slotObject);
}
public void HookupEvents(SlotDataAsset slot)
{
}
#endregion
}
}