添加插件
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c75d46b0788adf543b0088b6f6275521
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,266 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Part of Tail Animator Skinning Static Meshes API
|
||||
/// Methods used by skinner to create skinned mesh renderers from static meshes
|
||||
/// </summary>
|
||||
public static class FTail_Skinning
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Calculating base vertices datas for provided bones setup
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Mesh to be weighted </param>
|
||||
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
|
||||
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
|
||||
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
|
||||
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
|
||||
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
|
||||
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Transform[] bonesCoords, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
|
||||
{
|
||||
Vector3[] pos = new Vector3[bonesCoords.Length];
|
||||
Quaternion[] rot = new Quaternion[bonesCoords.Length];
|
||||
|
||||
//for (int i = 0; i < bonesCoords.Length; i++)
|
||||
//{
|
||||
// pos[i] = bonesCoords[i].position;
|
||||
// rot[i] = bonesCoords[i].rotation;
|
||||
//}
|
||||
|
||||
// We must reset bones structure to identity space
|
||||
for (int i = 0; i < bonesCoords.Length; i++)
|
||||
{
|
||||
// Transforming from world to local space coords
|
||||
pos[i] = bonesCoords[0].parent.InverseTransformPoint(bonesCoords[i].position);
|
||||
rot[i] = FEngineering.QToLocal(bonesCoords[0].parent.rotation, bonesCoords[i].rotation);
|
||||
}
|
||||
|
||||
return CalculateVertexWeightingData(baseMesh, pos, rot, spreadOffset, weightBoneLimit, spreadValue, spreadPower);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating base vertices datas for provided bones setup
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Mesh to be weighted </param>
|
||||
/// <param name="bonesPos"> Mesh local space positions for bones </param>
|
||||
/// <param name="bonesRot"> Mesh local space rotations for bones </param>
|
||||
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
|
||||
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
|
||||
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
|
||||
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
|
||||
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
|
||||
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Vector3[] bonesPos, Quaternion[] bonesRot, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
|
||||
{
|
||||
if (weightBoneLimit < 1) weightBoneLimit = 1;
|
||||
if (weightBoneLimit > 2) weightBoneLimit = 2; // Limiting for now
|
||||
|
||||
#region Editor progress dialogs
|
||||
#if UNITY_EDITOR
|
||||
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
int vertCount = baseMesh.vertexCount;
|
||||
FTail_SkinningVertexData[] vertexDatas = new FTail_SkinningVertexData[vertCount];
|
||||
|
||||
// Computing helper segments for weighting bones
|
||||
Vector3[] boneAreas = new Vector3[bonesPos.Length];
|
||||
for (int i = 0; i < bonesPos.Length - 1; i++)
|
||||
{
|
||||
// Direction vector towards further bone
|
||||
boneAreas[i] = bonesPos[i + 1] - bonesPos[i]; //bonesCoords[i + 1].localPosition - bonesCoords[i].localPosition;
|
||||
}
|
||||
|
||||
if (boneAreas.Length > 1) boneAreas[boneAreas.Length - 1] = boneAreas[boneAreas.Length - 2];
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < vertCount; i++)
|
||||
{
|
||||
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
|
||||
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
|
||||
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
// Displaying progress bar when iteration takes too much time
|
||||
if (watch.ElapsedMilliseconds > 1500)
|
||||
if (i % 10 == 0)
|
||||
EditorUtility.DisplayProgressBar("Analizing mesh vertices...", "Analizing Vertices (" + i + "/" + vertCount + ")", ((float)i / (float)vertCount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
EditorUtility.ClearProgressBar();
|
||||
#endregion
|
||||
}
|
||||
catch (System.Exception exc)
|
||||
{
|
||||
Debug.LogError(exc);
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
EditorUtility.ClearProgressBar();
|
||||
#endregion
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < vertCount; i++)
|
||||
{
|
||||
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
|
||||
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
|
||||
}
|
||||
#endif
|
||||
|
||||
return vertexDatas;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning target mesh with helper vertex datas which you can get with CalculateVertexWeightingData() method
|
||||
/// Using transforms as guidement for bones positions and rotations
|
||||
/// </summary>
|
||||
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
|
||||
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Transform skinParent, Transform[] bonesStructure, FTail_SkinningVertexData[] vertData)
|
||||
{
|
||||
Vector3[] pos = new Vector3[bonesStructure.Length];
|
||||
Quaternion[] rot = new Quaternion[bonesStructure.Length];
|
||||
|
||||
// We must reset bones structure to identity space
|
||||
for (int i = 0; i < bonesStructure.Length; i++)
|
||||
{
|
||||
// Transforming from world to local space coords
|
||||
pos[i] = skinParent.InverseTransformPoint(bonesStructure[i].position);
|
||||
rot[i] = FEngineering.QToLocal(skinParent.rotation, bonesStructure[i].rotation);
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer skin = SkinMesh(baseMesh, pos, rot, vertData);
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning target mesh with helper vertex datas which you can
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Base static mesh to be skinned </param>
|
||||
/// <param name="bonesPositions"> Bones positions in mesh local space </param>
|
||||
/// <param name="bonesRotations"> Bones rotations in mesh local space </param>
|
||||
/// <param name="vertData"> Get it with CalculateVertexWeightingData() method </param>
|
||||
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
|
||||
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Vector3[] bonesPositions, Quaternion[] bonesRotations, FTail_SkinningVertexData[] vertData)
|
||||
{
|
||||
if (bonesPositions == null) return null;
|
||||
if (bonesRotations == null) return null;
|
||||
if (baseMesh == null) return null;
|
||||
if (vertData == null) return null;
|
||||
|
||||
// Creating copy of target mesh and refreshing it
|
||||
Mesh newMesh = GameObject.Instantiate(baseMesh);
|
||||
newMesh.name = baseMesh.name + " [FSKINNED]";
|
||||
|
||||
// Preparing new object which will have skinned mesh renderer with new mesh and bones in it
|
||||
GameObject newSkinObject = new GameObject(baseMesh.name + " [FSKINNED]");
|
||||
Transform newParent = newSkinObject.transform;
|
||||
|
||||
// Preparing skin
|
||||
SkinnedMeshRenderer skin = newParent.gameObject.AddComponent<SkinnedMeshRenderer>();
|
||||
|
||||
// Preparing bones for weighting
|
||||
Transform[] bones = new Transform[bonesPositions.Length];
|
||||
Matrix4x4[] bindPoses = new Matrix4x4[bonesPositions.Length];
|
||||
|
||||
string nameString;
|
||||
if (baseMesh.name.Length < 6) nameString = baseMesh.name; else nameString = baseMesh.name.Substring(0, 5);
|
||||
|
||||
for (int i = 0; i < bonesPositions.Length; i++)
|
||||
{
|
||||
bones[i] = new GameObject("BoneF-" + nameString + "[" + i + "]").transform;
|
||||
if (i == 0) bones[i].SetParent(newParent, true); else bones[i].SetParent(bones[i - 1], true);
|
||||
|
||||
bones[i].transform.position = bonesPositions[i];
|
||||
bones[i].transform.rotation = bonesRotations[i];
|
||||
|
||||
bindPoses[i] = bones[i].worldToLocalMatrix * newParent.localToWorldMatrix;
|
||||
}
|
||||
|
||||
BoneWeight[] weights = new BoneWeight[newMesh.vertexCount];
|
||||
for (int v = 0; v < weights.Length; v++) weights[v] = new BoneWeight();
|
||||
|
||||
// Calculating and applying weights for verices
|
||||
for (int i = 0; i < vertData.Length; i++)
|
||||
{
|
||||
for (int w = 0; w < vertData[i].weights.Length; w++)
|
||||
{
|
||||
weights[i] = SetWeightIndex(weights[i], w, vertData[i].bonesIndexes[w]);
|
||||
weights[i] = SetWeightToBone(weights[i], w, vertData[i].weights[w]);
|
||||
}
|
||||
}
|
||||
|
||||
newMesh.bindposes = bindPoses;
|
||||
newMesh.boneWeights = weights;
|
||||
|
||||
List<Vector3> normals = new List<Vector3>();
|
||||
List < Vector4> tangents = new List<Vector4>();
|
||||
baseMesh.GetNormals(normals);
|
||||
baseMesh.GetTangents(tangents);
|
||||
|
||||
newMesh.SetNormals(normals);
|
||||
newMesh.SetTangents(tangents);
|
||||
newMesh.bounds = baseMesh.bounds;
|
||||
|
||||
// Applying generated mesh to skin controller
|
||||
skin.sharedMesh = newMesh;
|
||||
skin.rootBone = bones[0];
|
||||
skin.bones = bones;
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Method which is setting certain weight variable from BoneWeight struct
|
||||
/// </summary>
|
||||
public static BoneWeight SetWeightIndex(BoneWeight weight, int bone = 0, int index = 0)
|
||||
{
|
||||
switch (bone)
|
||||
{
|
||||
case 1: weight.boneIndex1 = index; break;
|
||||
case 2: weight.boneIndex2 = index; break;
|
||||
case 3: weight.boneIndex3 = index; break;
|
||||
default: weight.boneIndex0 = index; break;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Method which is setting certain weight variable from BoneWeight struct
|
||||
/// </summary>
|
||||
public static BoneWeight SetWeightToBone(BoneWeight weight, int bone = 0, float value = 1f)
|
||||
{
|
||||
switch (bone)
|
||||
{
|
||||
case 1: weight.weight1 = value; break;
|
||||
case 2: weight.weight2 = value; break;
|
||||
case 3: weight.weight3 = value; break;
|
||||
default: weight.weight0 = value; break;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbb5fa7a5ec9084ba3564faf70c8899
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f1f543bbb761e234cbb384a09c3482cc, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,231 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Part of Tail Animator Skinning Static Meshes API
|
||||
/// Simple helper class to store vertices parameters in reference to bones
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class FTail_SkinningVertexData
|
||||
{
|
||||
public Vector3 position;
|
||||
//public Transform[] bones;
|
||||
|
||||
/// <summary> Indexes for helpers in visualization </summary>
|
||||
public int[] bonesIndexes;
|
||||
public int allMeshBonesCount;
|
||||
|
||||
// Assigned during custom weight calculations
|
||||
public float[] weights;
|
||||
|
||||
public FTail_SkinningVertexData(Vector3 pos) { position = pos; }
|
||||
|
||||
/// <summary>
|
||||
/// Distance to bone area for weighting
|
||||
/// </summary>
|
||||
public float DistanceToLine(Vector3 pos, Vector3 lineStart, Vector3 lineEnd)
|
||||
{
|
||||
Vector3 dirVector1 = pos - lineStart;
|
||||
Vector3 dirVector2 = (lineEnd - lineStart).normalized;
|
||||
|
||||
float distance = Vector3.Distance(lineStart, lineEnd);
|
||||
float dot = Vector3.Dot(dirVector2, dirVector1);
|
||||
|
||||
if (dot <= 0) return Vector3.Distance(pos, lineStart);
|
||||
if (dot >= distance) return Vector3.Distance(pos, lineEnd);
|
||||
|
||||
Vector3 dotVector = dirVector2 * dot;
|
||||
Vector3 closestPoint = lineStart + dotVector;
|
||||
|
||||
return Vector3.Distance(pos, closestPoint);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating vertex's distances to 4 nearest bones (4 bone weights is maximum count in Unity)
|
||||
/// for further custom weight calculations
|
||||
/// </summary>
|
||||
public void CalculateVertexParameters(Vector3[] bonesPos, Quaternion[] bonesRot, Vector3[] boneAreas, int maxWeightedBones, float spread, Vector3 spreadOffset, float spreadPower = 1f)
|
||||
{
|
||||
allMeshBonesCount = bonesPos.Length;
|
||||
|
||||
// Using Vector2 for simple two float values in one variable, x = bone index y = distance of vertex to this bone, later we will sort list using distances
|
||||
List<Vector2> calculatedDistances = new List<Vector2>();
|
||||
|
||||
// Check later if we don't need to transpone points to model space scale
|
||||
for (int i = 0; i < bonesPos.Length; i++)
|
||||
{
|
||||
Vector3 boneEnd;
|
||||
if (i != bonesPos.Length - 1)
|
||||
boneEnd = Vector3.Lerp(bonesPos[i], bonesPos[i + 1], 0.9f);
|
||||
else
|
||||
boneEnd = Vector3.Lerp(bonesPos[i], bonesPos[i] + (bonesPos[i] - bonesPos[i - 1]), 0.9f);
|
||||
|
||||
boneEnd += bonesRot[i] * spreadOffset;
|
||||
|
||||
float distance = DistanceToLine(position, bonesPos[i], boneEnd);
|
||||
|
||||
// Making bone offset to behave like bone area
|
||||
calculatedDistances.Add(new Vector2(i, distance));
|
||||
}
|
||||
|
||||
// Sorting by nearest all bones
|
||||
calculatedDistances.Sort((a, b) => a.y.CompareTo(b.y));
|
||||
|
||||
// Limiting vertex weight up to 4 bones
|
||||
int maxBones = (int)Mathf.Min(maxWeightedBones, bonesPos.Length);
|
||||
|
||||
// Assigning max 4 nearest bones and their distances to this vertex
|
||||
bonesIndexes = new int[maxBones];
|
||||
float[] nearestDistances = new float[maxBones];
|
||||
|
||||
for (int i = 0; i < maxBones; i++)
|
||||
{
|
||||
bonesIndexes[i] = (int)calculatedDistances[i].x;
|
||||
nearestDistances[i] = calculatedDistances[i].y;
|
||||
}
|
||||
|
||||
// Basing on spread value we spreading weight to nearest bones
|
||||
// Calculating percentage distances to bones
|
||||
float[] boneWeightsForVertex = new float[maxBones];
|
||||
|
||||
|
||||
AutoSetBoneWeights(boneWeightsForVertex, nearestDistances, spread, spreadPower, boneAreas);
|
||||
|
||||
|
||||
float weightLeft = 1f; // Must amount of weight which needs to be assigned
|
||||
weights = new float[maxBones]; // New weight parameters
|
||||
|
||||
// Applying weights to each bone assigned to vertex
|
||||
for (int i = 0; i < maxBones; i++)
|
||||
{
|
||||
if (spread == 0) if (i > 0) break;
|
||||
|
||||
if (weightLeft <= 0f) // No more weight to apply
|
||||
{
|
||||
weights[i] = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
float targetWeight = boneWeightsForVertex[i];
|
||||
|
||||
weightLeft -= targetWeight;
|
||||
if (weightLeft <= 0f) targetWeight += weightLeft; else { if (i == maxBones - 1) targetWeight += weightLeft; } // Using weight amount which is left to assign
|
||||
|
||||
weights[i] = targetWeight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float[] debugDists;
|
||||
public float[] debugDistWeights;
|
||||
public float[] debugWeights;
|
||||
|
||||
/// <summary>
|
||||
/// Spreading weights over bones for current vertex
|
||||
/// </summary>
|
||||
public void AutoSetBoneWeights(float[] weightForBone, float[] distToBone, float spread, float spreadPower, Vector3[] boneAreas)
|
||||
{
|
||||
int bonesC = weightForBone.Length;
|
||||
float[] boneLengths = new float[bonesC]; for (int i = 0; i < boneLengths.Length; i++) boneLengths[i] = boneAreas[i].magnitude;
|
||||
float[] normalizedDistanceWeights = new float[bonesC];
|
||||
for (int i = 0; i < weightForBone.Length; i++) weightForBone[i] = 0f;
|
||||
|
||||
float normalizeDistance = 0f;
|
||||
for (int i = 0; i < bonesC; i++) normalizeDistance += distToBone[i];
|
||||
for (int i = 0; i < bonesC; i++) normalizedDistanceWeights[i] = 1f - distToBone[i] / normalizeDistance; // Reversing weight power - nearest (smallest distance) must have biggest weight value
|
||||
|
||||
debugDists = distToBone;
|
||||
|
||||
if (bonesC == 1 || spread == 0f) // Simpliest ONE BONE -------------------------------------------------------------
|
||||
{
|
||||
// [0] - nearest bone
|
||||
weightForBone[0] = 1f; // Just one weight - spread does not change anything
|
||||
}
|
||||
else if (bonesC == 2) // Simple TWO BONES -------------------------------------------------------------
|
||||
{
|
||||
float normalizer = 1f;
|
||||
weightForBone[0] = 1f;
|
||||
|
||||
// distToBone[0] is zero, max spread distance is length of bone / 3
|
||||
float distRange = Mathf.InverseLerp(distToBone[0] + (boneLengths[0] / 1.25f) * spread, distToBone[0], distToBone[1]);
|
||||
debugDists[0] = distRange;
|
||||
|
||||
// 0 -> full nearest bone weight
|
||||
// 1 -> half nearest half second bone weight
|
||||
float value = DistributionIn(Mathf.Lerp(0f, 1f, distRange), Mathf.Lerp(1.5f, 16f, spreadPower));
|
||||
|
||||
weightForBone[1] = value;
|
||||
normalizer += value;
|
||||
|
||||
debugDistWeights = new float[weightForBone.Length];
|
||||
|
||||
weightForBone.CopyTo(debugDistWeights, 0);
|
||||
|
||||
for (int i = 0; i < bonesC; i++) weightForBone[i] /= normalizer;
|
||||
|
||||
debugWeights = weightForBone;
|
||||
}
|
||||
else // Complex > TWO BONES -------------------------------------------------------------
|
||||
{
|
||||
float reffVal = boneLengths[0] / 10f;
|
||||
float refLength = boneLengths[0] / 2f;
|
||||
float normalizer = 0f;
|
||||
|
||||
for (int i = 0; i < bonesC; i++)
|
||||
{
|
||||
float weight = Mathf.InverseLerp(0f, reffVal + refLength * (spread), distToBone[i]);
|
||||
float value = Mathf.Lerp(1f, 0f, weight);
|
||||
if (i == 0) if (value == 0f) value = 1f;
|
||||
|
||||
weightForBone[i] = value;
|
||||
|
||||
normalizer += value;
|
||||
}
|
||||
|
||||
debugDistWeights = new float[weightForBone.Length];
|
||||
weightForBone.CopyTo(debugDistWeights, 0);
|
||||
|
||||
for (int i = 0; i < bonesC; i++) weightForBone[i] /= normalizer;
|
||||
|
||||
debugWeights = weightForBone;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Easing weight distribution
|
||||
/// </summary>
|
||||
public static float DistributionIn(float k, float power)
|
||||
{ return Mathf.Pow(k, power + 1f); }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returning helper color for bone
|
||||
/// </summary>
|
||||
public static Color GetBoneIndicatorColor(int boneIndex, int bonesCount, float s = 0.9f, float v = 0.9f)
|
||||
{
|
||||
float h = ((float)(boneIndex) * 1.125f) / bonesCount;
|
||||
h += 0.125f * boneIndex;
|
||||
h += 0.3f;
|
||||
h %= 1f;
|
||||
|
||||
return Color.HSVToRGB(h, s, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns average color value for weight idicator for this vertex
|
||||
/// </summary>
|
||||
public Color GetWeightColor()
|
||||
{
|
||||
Color lerped = GetBoneIndicatorColor(bonesIndexes[0], allMeshBonesCount, 1f, 1f);
|
||||
|
||||
for (int i = 1; i < bonesIndexes.Length; i++)
|
||||
lerped = Color.Lerp(lerped, GetBoneIndicatorColor(bonesIndexes[i], allMeshBonesCount, 1f, 1f), weights[i]);
|
||||
|
||||
return lerped;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2595a070e1f36f49862752d4a32580f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f1f543bbb761e234cbb384a09c3482cc, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,454 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Implementation of Tail Animator Skinning Static Meshes API
|
||||
/// Class to use only in editor, it creates bones with preview static mesh then skin it to skinned mesh renderer
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("FImpossible Creations/Tail Animator Utilities/Editor Tail Skinner")]
|
||||
public class FTail_Editor_Skinner : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Inspector Variables
|
||||
|
||||
[FPD_Header("SKIN STATIC MESHES INSIDE UNITY", 3, 8, 8)]
|
||||
[BackgroundColor(0.75f, 0.75f, 1.0f, 0.7f)]
|
||||
public int AutoMarkersCount = 8;
|
||||
public float DistanceValue = 0.3f;
|
||||
public Vector3 positionOffset = new Vector3(0, 0f);
|
||||
public Vector2 startDirection = new Vector2(-90, 0f);
|
||||
public Vector2 rotationOffset = new Vector2(0f, 0f);
|
||||
|
||||
[Range(0f, 5f)]
|
||||
public float HelpScaleValue = 1f;
|
||||
|
||||
[BackgroundColor(0.85f, 0.85f, 1.0f, 0.85f)]
|
||||
public AnimationCurve DistancesFaloff = AnimationCurve.Linear(0f, 1f, 1f, 1f);
|
||||
public AnimationCurve RotationsFaloff = AnimationCurve.Linear(0f, 1f, 1f, 1f);
|
||||
|
||||
[BackgroundColor(0.5f, 1f, 0.5f, 0.8f)]
|
||||
[Space(10f, order = 0)]
|
||||
[Header("Left empty if you don't use custom markers", order = 1)]
|
||||
[Space(-7f, order = 2)]
|
||||
[Header("Moving custom markers will not trigger realtime update", order = 3)]
|
||||
public Transform[] CustomBoneMarkers;
|
||||
|
||||
[Space(7f, order = 0)]
|
||||
[FPD_Header("Weights Spread Settings", 7, 4, 4)]
|
||||
[Space(3f, order = 2)]
|
||||
[Range(0f, 1f)]
|
||||
public float SpreadValue = 0.8f;
|
||||
[Range(0f, 1f)]
|
||||
public float SpreadPower = .185f;
|
||||
[Tooltip("Offsetting spreading area, For example 0,0,1 and recommended values from 0 to 2 not bigger")]
|
||||
public Vector3 SpreadOffset = Vector3.zero;
|
||||
[Range(1, 2)]
|
||||
public int LimitBoneWeightCount = 2;
|
||||
|
||||
[BackgroundColor(0.4f, 0.8f, 0.8f, 0.8f)]
|
||||
[FPD_Header("Additional Variables", 7, 4, 4)]
|
||||
[Range(0f, 5f)]
|
||||
public float GizmoSize = 0.1f;
|
||||
[Range(0f, 1f)]
|
||||
public float GizmoAlpha = .65f;
|
||||
|
||||
[BackgroundColor()]
|
||||
[Tooltip("If your model have many vertices, turn it only when neccesary")]
|
||||
public bool RealtimeUpdate = true;
|
||||
public bool ShowPreview = true;
|
||||
public bool DebugMode = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
/// <summary> Base Mesh </summary>
|
||||
private Mesh baseMesh;
|
||||
|
||||
[HideInInspector]
|
||||
public List<Color32> baseVertexColor;
|
||||
|
||||
private MeshRenderer meshRenderer;
|
||||
|
||||
|
||||
/// <summary> Fake bones list before creating true skeleton for mesh </summary>
|
||||
private Transform[] ghostBones;
|
||||
|
||||
/// <summary> Vertex datas used for setting weights precisely</summary>
|
||||
private FTail_SkinningVertexData[] vertexDatas;
|
||||
|
||||
/// <summary> Generated marker points for automatic bone points </summary>
|
||||
internal Transform[] autoMarkers;
|
||||
|
||||
// Hide in inspector because when variables are private, they're resetted to null every time code compiles
|
||||
/// <summary> Because we can't destroy gameObjects in OnValidate, we do something similar to object pools </summary>
|
||||
[HideInInspector]
|
||||
public List<Transform> allMarkersTransforms = new List<Transform>();
|
||||
|
||||
/// <summary> Transform with components helping drawing how weights are spread on model </summary>
|
||||
[HideInInspector]
|
||||
public Transform weightPreviewTransform;
|
||||
|
||||
[HideInInspector]
|
||||
public bool popupShown = false;
|
||||
|
||||
internal bool initValues = false;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When something changes in inspector, let's recalculate parameters
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!initValues)
|
||||
{
|
||||
MeshRenderer m = GetComponent<MeshRenderer>();
|
||||
if (m) DistanceValue = m.bounds.extents.magnitude / 7f;
|
||||
initValues = true;
|
||||
}
|
||||
|
||||
if (AutoMarkersCount < 2) AutoMarkersCount = 2;
|
||||
|
||||
if (!GetBaseMesh()) return;
|
||||
|
||||
if (CustomBoneMarkers == null) CustomBoneMarkers = new Transform[0]; // Prevent error log when adding component
|
||||
|
||||
// Use only custom markers if they're assigned
|
||||
if (CustomBoneMarkers.Length > 0)
|
||||
ghostBones = CustomBoneMarkers;
|
||||
else // Use auto markers
|
||||
{
|
||||
CalculateAutoMarkers();
|
||||
ghostBones = autoMarkers;
|
||||
}
|
||||
|
||||
if (RealtimeUpdate)
|
||||
{
|
||||
vertexDatas = FTail_Skinning.CalculateVertexWeightingData(
|
||||
GetBaseMesh(), ghostBones, SpreadOffset, LimitBoneWeightCount, SpreadValue, SpreadPower);
|
||||
|
||||
UpdatePreviewMesh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Drawing helper stuff
|
||||
/// </summary>
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (CustomBoneMarkers == null)
|
||||
CustomBoneMarkers = new Transform[0];
|
||||
|
||||
if (ghostBones[0] == null)
|
||||
CalculateAutoMarkers();
|
||||
|
||||
if (CustomBoneMarkers.Length < 1)
|
||||
DrawMarkers(autoMarkers);
|
||||
else
|
||||
DrawMarkers(CustomBoneMarkers);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating auto markers transforms
|
||||
/// </summary>
|
||||
private void CalculateAutoMarkers()
|
||||
{
|
||||
#region Creation of markers' transforms
|
||||
|
||||
if (autoMarkers == null) autoMarkers = new Transform[0];
|
||||
|
||||
if (allMarkersTransforms.Count < AutoMarkersCount)
|
||||
{
|
||||
for (int i = autoMarkers.Length; i < AutoMarkersCount; i++)
|
||||
{
|
||||
GameObject newMarker = new GameObject(name + "-SkinMarker " + i);
|
||||
newMarker.transform.SetParent(transform, true);
|
||||
allMarkersTransforms.Add(newMarker.transform);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoMarkers.Length != AutoMarkersCount)
|
||||
{
|
||||
autoMarkers = new Transform[AutoMarkersCount];
|
||||
for (int i = 0; i < AutoMarkersCount; i++)
|
||||
{
|
||||
autoMarkers[i] = allMarkersTransforms[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
autoMarkers[0].position = transform.position + positionOffset;
|
||||
autoMarkers[0].rotation = Quaternion.Euler(startDirection + rotationOffset);
|
||||
|
||||
float step = 1f / (float)AutoMarkersCount;
|
||||
|
||||
for (int i = 1; i < AutoMarkersCount; i++)
|
||||
{
|
||||
float forwardMultiplier = DistanceValue;
|
||||
forwardMultiplier *= DistancesFaloff.Evaluate(i * step);
|
||||
forwardMultiplier *= HelpScaleValue;
|
||||
Vector3 targetPosition = autoMarkers[i - 1].position + autoMarkers[i - 1].rotation * Vector3.forward * forwardMultiplier;
|
||||
|
||||
Vector3 newRot = startDirection + rotationOffset * (i + 1) * RotationsFaloff.Evaluate(i * step);
|
||||
|
||||
autoMarkers[i].position = targetPosition;
|
||||
autoMarkers[i].rotation = Quaternion.Euler(newRot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Getting base mesh variable, depends if it's skinned mesh or static mesh
|
||||
/// </summary>
|
||||
private Mesh GetBaseMesh()
|
||||
{
|
||||
if (baseMesh == null)
|
||||
{
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
MeshFilter meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter) baseMesh = meshFilter.sharedMesh;
|
||||
}
|
||||
else return baseMesh;
|
||||
|
||||
if (!baseMesh)
|
||||
{
|
||||
if (!popupShown)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Tail Skinner Error", "[Tail Skinner] No base mesh! (mesh filter and mesh renderer)", "Ok");
|
||||
popupShown = true;
|
||||
}
|
||||
|
||||
Debug.LogError("No BaseMesh!");
|
||||
}
|
||||
|
||||
if (baseMesh)
|
||||
{
|
||||
if (baseVertexColor == null) baseVertexColor = new List<Color32>();
|
||||
if (baseVertexColor.Count != baseMesh.vertexCount)
|
||||
{
|
||||
baseVertexColor.Clear();
|
||||
baseMesh.GetColors(baseVertexColor);
|
||||
}
|
||||
}
|
||||
|
||||
return baseMesh;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning mesh to new skinned mesh renderer with choosed weight markers settings
|
||||
/// </summary>
|
||||
public void SkinMesh(bool addTailAnimator, Vector3 newObjectOffset)
|
||||
{
|
||||
// Remembering data and preparing objects with components
|
||||
List<Color32> baseVertColors = new List<Color32>();
|
||||
GetBaseMesh().GetColors(baseVertColors);
|
||||
|
||||
// Doing skinning
|
||||
vertexDatas = FTail_Skinning.CalculateVertexWeightingData(
|
||||
GetBaseMesh(), ghostBones, SpreadOffset, LimitBoneWeightCount, SpreadValue, SpreadPower);
|
||||
|
||||
SkinnedMeshRenderer newSkinnedMesh = FTail_Skinning.SkinMesh(baseMesh, transform, ghostBones, vertexDatas);
|
||||
|
||||
if (newSkinnedMesh == null)
|
||||
{ Debug.LogError("[Tail Animator Skinning] Creating skinned mesh failed!"); return; }
|
||||
|
||||
|
||||
// Skin renderer quality
|
||||
switch (LimitBoneWeightCount)
|
||||
{
|
||||
case 1: newSkinnedMesh.quality = SkinQuality.Bone1; break;
|
||||
case 2: newSkinnedMesh.quality = SkinQuality.Bone2; break;
|
||||
case 4: newSkinnedMesh.quality = SkinQuality.Bone4; break;
|
||||
default: newSkinnedMesh.quality = SkinQuality.Auto; break;
|
||||
}
|
||||
|
||||
// Filling new mesh with materials
|
||||
MeshRenderer meshRend = GetComponent<MeshRenderer>();
|
||||
if (meshRend)
|
||||
{
|
||||
newSkinnedMesh.materials = meshRend.sharedMaterials;
|
||||
newSkinnedMesh.sharedMaterials = meshRend.sharedMaterials;
|
||||
}
|
||||
|
||||
// Adding tail animator
|
||||
if (addTailAnimator)
|
||||
{
|
||||
TailAnimator2 t = newSkinnedMesh.bones[0].gameObject.AddComponent<TailAnimator2>();
|
||||
t.StartBone = newSkinnedMesh.bones[0];
|
||||
t.EndBone = newSkinnedMesh.bones[newSkinnedMesh.bones.Length - 1];
|
||||
}
|
||||
|
||||
// Setting new object position to be next to current model
|
||||
newSkinnedMesh.transform.position = transform.position + new Vector3(1f, 1f, 1f);
|
||||
|
||||
// Create asset for new model so it not disappear when we create prefab from this gameObject
|
||||
string newMeshPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseMesh));
|
||||
AssetDatabase.CreateAsset(newSkinnedMesh.sharedMesh, newMeshPath + "/" + newSkinnedMesh.name + ".mesh");
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Debug.Log("New skinned mesh '" + newSkinnedMesh.name + ".mesh" + "' saved under path: '" + newMeshPath + "'");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Make sure everything which was created by this script is destroyed
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
for (int i = 0; i < allMarkersTransforms.Count; i++) if (allMarkersTransforms[i] != null) DestroyImmediate(allMarkersTransforms[i].gameObject);
|
||||
if (weightPreviewTransform != null) DestroyImmediate(weightPreviewTransform.gameObject);
|
||||
|
||||
if (baseMesh == null) return;
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Drawing markers to be visible in editor window to help place bones correctly
|
||||
/// </summary>
|
||||
public void DrawMarkers(Transform[] markers)
|
||||
{
|
||||
if (markers == null) return;
|
||||
|
||||
for (int i = 0; i < markers.Length; i++)
|
||||
{
|
||||
Gizmos.color = ChangeColorAlpha(GetBoneIndicatorColor(i, markers.Length), GizmoAlpha);
|
||||
|
||||
Vector3 targetPosition = markers[i].position;
|
||||
|
||||
Gizmos.DrawWireSphere(targetPosition, GizmoSize);
|
||||
|
||||
Gizmos.color = ChangeColorAlpha(GetBoneIndicatorColor(i, markers.Length, 1f, 1f), GizmoAlpha * 0.8f);
|
||||
Gizmos.DrawSphere(targetPosition, GizmoSize * 0.7f);
|
||||
|
||||
Gizmos.DrawRay(targetPosition, markers[i].up * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, -markers[i].up * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, markers[i].right * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, -markers[i].right * GizmoSize * 1.1f);
|
||||
|
||||
Vector3 targetPoint;
|
||||
if (i < markers.Length - 1) targetPoint = markers[i + 1].position;
|
||||
else
|
||||
targetPoint = markers[i].position + (markers[i].position - markers[i - 1].position);
|
||||
|
||||
Gizmos.DrawLine(targetPosition + markers[i].up * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition - markers[i].up * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition + markers[i].right * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition - markers[i].right * GizmoSize * 1.1f, targetPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updating preview mesh to view weights correctly
|
||||
/// </summary>
|
||||
private void UpdatePreviewMesh()
|
||||
{
|
||||
#region Creation of new preview mesh when needed
|
||||
|
||||
if (weightPreviewTransform == null)
|
||||
{
|
||||
weightPreviewTransform = new GameObject(name + "[preview mesh]").transform;
|
||||
weightPreviewTransform.SetParent(transform);
|
||||
weightPreviewTransform.localPosition = Vector3.zero;
|
||||
weightPreviewTransform.localRotation = Quaternion.identity;
|
||||
weightPreviewTransform.localScale = Vector3.one;
|
||||
|
||||
weightPreviewTransform.gameObject.AddComponent<MeshFilter>().mesh = baseMesh;
|
||||
|
||||
Material[] newMaterials = new Material[meshRenderer.sharedMaterials.Length];
|
||||
|
||||
for (int i = 0; i < newMaterials.Length; i++) newMaterials[i] = new Material(Shader.Find("Particles/FVertexLit Blended"));
|
||||
weightPreviewTransform.gameObject.AddComponent<MeshRenderer>().materials = newMaterials;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (ShowPreview)
|
||||
{
|
||||
meshRenderer.enabled = false;
|
||||
weightPreviewTransform.gameObject.SetActive(true);
|
||||
List<Color> vColors = new List<Color>();
|
||||
for (int i = 0; i < vertexDatas.Length; i++) vColors.Add(vertexDatas[i].GetWeightColor());
|
||||
baseMesh.SetColors(vColors);
|
||||
weightPreviewTransform.gameObject.GetComponent<MeshFilter>().mesh = baseMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
meshRenderer.enabled = true;
|
||||
if (baseVertexColor != null) if (baseMesh) if (baseMesh.vertexCount == baseVertexColor.Count) baseMesh.SetColors(baseVertexColor);
|
||||
weightPreviewTransform.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!DebugMode) return;
|
||||
if (vertexDatas == null) return;
|
||||
if (vertexDatas.Length == 0) return;
|
||||
if (vertexDatas[0].bonesIndexes == null) return;
|
||||
|
||||
for (int i = 0; i < vertexDatas.Length; i++)
|
||||
{
|
||||
Handles.color = GetBoneIndicatorColor(vertexDatas[i].bonesIndexes[0], vertexDatas[i].bonesIndexes.Length) * new Color(1f, 1f, 1f, GizmoAlpha);
|
||||
Gizmos.color = Handles.color;
|
||||
|
||||
Handles.Label(transform.TransformPoint(vertexDatas[i].position), "[" + i + "]");
|
||||
//Handles.Label(transform.TransformPoint(vertexDatas[i].position), "[" + i + "]\n" + Math.Round(vertexDatas[i].debugDists[0], 3) + "\n" + Math.Round(vertexDatas[i].debugDists[1], 3));
|
||||
Gizmos.DrawSphere(transform.TransformPoint(vertexDatas[i].position), 0.125f * GizmoSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returning helper color for bone
|
||||
/// </summary>
|
||||
public static Color GetBoneIndicatorColor(int boneIndex, int bonesCount, float s = 0.9f, float v = 0.9f)
|
||||
{
|
||||
float h = ((float)(boneIndex) * 1.125f) / bonesCount;
|
||||
h += 0.125f * boneIndex;
|
||||
h += 0.3f;
|
||||
h %= 1f;
|
||||
return Color.HSVToRGB(h, s, v);
|
||||
}
|
||||
|
||||
|
||||
public static Color ChangeColorAlpha(Color color, float alpha)
|
||||
{ return new Color(color.r, color.g, color.b, alpha); }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FM: Editor class component to enchance controll over component from inspector window
|
||||
/// </summary>
|
||||
[UnityEditor.CustomEditor(typeof(FTail_Editor_Skinner))]
|
||||
public class FTail_Editor_SkinnerEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FTail_Editor_Skinner targetScript = (FTail_Editor_Skinner)target;
|
||||
DrawDefaultInspector();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
if (GUILayout.Button("Skin It")) targetScript.SkinMesh(false, Vector3.right);
|
||||
if (GUILayout.Button("Skin and add Tail Animator")) targetScript.SkinMesh(true, Vector3.right);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64150bf558082b8468d3f05b3e9d3b5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7958d01c62579034fa1ffbb911dd6b59, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,301 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FC: Experimental class under developement
|
||||
/// </summary>
|
||||
[AddComponentMenu("FImpossible Creations/Tail Animator Utilities/Tail Animator Wind")]
|
||||
public class TailAnimatorWind : MonoBehaviour, UnityEngine.EventSystems.IDropHandler, IFHierarchyIcon
|
||||
{
|
||||
|
||||
#region Hierarchy Icon
|
||||
|
||||
public string EditorIconPath { get { return "Tail Animator/TailAnimatorWindIconSmall"; } }
|
||||
public void OnDrop(UnityEngine.EventSystems.PointerEventData data) { }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Singleton
|
||||
|
||||
|
||||
public static TailAnimatorWind Instance { get; private set; } // { get { if (!_instance) GenerateWindComponentInstance(); return _instance; } }
|
||||
//private static TailAnimatorWind _instance;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
Instance = this;
|
||||
if (persistThroughAllScenes) DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public void OnValidate()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
//private static void GenerateWindComponentInstance()
|
||||
//{
|
||||
// GameObject windObj = new GameObject("Tail Animator Wind");
|
||||
// _instance = windObj.AddComponent<TailAnimatorWind>();
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generating wind component if needed and adding tail animator component to wind affected components list
|
||||
/// </summary>
|
||||
//public static void Refresh(TailAnimator2 tail)
|
||||
//{
|
||||
// if (!_instance) GenerateWindComponentInstance();
|
||||
// if (_instance.WindAffected == null) _instance.WindAffected = new List<TailAnimator2>();
|
||||
// if (tail != null) if (!_instance.WindAffected.Contains(tail)) _instance.WindAffected.Add(tail);
|
||||
//}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
[Header("In playmode you will find this object in DontDestroyOnLoad")]
|
||||
[FPD_Header("Main Wind Setings", 2, 4)]
|
||||
public float power = 1f;
|
||||
public float additionalTurbulence = 1f;
|
||||
public float additionalTurbSpeed = 1f;
|
||||
|
||||
[Space(7)]
|
||||
public WindZone SyncWithUnityWindZone;
|
||||
public float UnityWindZonePowerMul = 2f;
|
||||
public float UnityWindZoneTurbMul = 1f;
|
||||
|
||||
[Header("Overriding wind if value below different than 0,0,0")]
|
||||
public Vector3 overrideWind = Vector3.zero;
|
||||
|
||||
[FPD_Header("Procedural Wind Settings (if not syncing and not overriding)", 6, 4)]
|
||||
[Range(0.1f, 1f)]
|
||||
public float rapidness = 0.95f;
|
||||
[FPD_Suffix(0, 360, FPD_SuffixAttribute.SuffixMode.FromMinToMaxRounded, "°")]
|
||||
public float changesPower = 90f;
|
||||
[Header("Extra")]
|
||||
[Range(0f, 10f)] public float turbulenceSpeed = 1f;
|
||||
|
||||
[FPD_Header("World Position Turbulence", 6, 4)]
|
||||
[Tooltip("Increase to make objects next to each other wave in slightly different way")]
|
||||
public float worldTurb = 1f;
|
||||
[Tooltip("If higher no performance cost, it is just a number")]
|
||||
public float worldTurbScale = 512;
|
||||
public float worldTurbSpeed = 5f;
|
||||
|
||||
[FPD_Header("Tail Compoenents Related", 6, 4)]
|
||||
[Tooltip("When tail is longer then power of wind should be higher")]
|
||||
public bool powerDependOnTailLength = true;
|
||||
[Tooltip("Don't destroy on load")]
|
||||
public bool persistThroughAllScenes = false;
|
||||
//[Tooltip("Finding all TailAnimato2 compoents at start")]
|
||||
//public bool collectFromSceneAtStart = false;
|
||||
|
||||
//public List<TailAnimator2> WindAffected;
|
||||
|
||||
private Vector3 targetWind = Vector3.zero;
|
||||
private Vector3 smoothWind = Vector3.zero;
|
||||
private Vector3 windVeloHelper = Vector3.zero;
|
||||
private Quaternion windOrientation = Quaternion.identity;
|
||||
private Quaternion smoothWindOrient = Quaternion.identity;
|
||||
private Quaternion smoothWindOrientHelper = Quaternion.identity;
|
||||
|
||||
private float[] randNumbers;
|
||||
private float[] randTimes;
|
||||
private float[] randSpeeds;
|
||||
|
||||
private int frameOffset = 2;
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (frameOffset > 0) { frameOffset--; return; }
|
||||
|
||||
//if (collectFromSceneAtStart)
|
||||
//{
|
||||
// collectFromSceneAtStart = false;
|
||||
// GetTailAnimatorsFromScene();
|
||||
//}
|
||||
|
||||
ComputeWind();
|
||||
|
||||
#region Hidden backup
|
||||
|
||||
//TailAnimator2 t;
|
||||
//for (int i = 0; i < WindAffected.Count; i++)
|
||||
//{
|
||||
// t = WindAffected[i];
|
||||
|
||||
// if (!t.UseWind) continue;
|
||||
// if (t.WindEffectPower <= 0f) continue;
|
||||
// if (t.TailSegments.Count <= 0) continue;
|
||||
|
||||
// float lengthRatio = 1f;
|
||||
// if (powerDependOnTailLength)
|
||||
// {
|
||||
// lengthRatio = (t._TC_TailLength * t.TailSegments[0].transform.lossyScale.z) / 5f;
|
||||
// if (t.TailSegments.Count > 3) lengthRatio *= Mathf.Lerp(0.7f, 3f, t.TailSegments.Count / 14f);
|
||||
// }
|
||||
|
||||
// if (t.WindWorldNoisePower > 0f)
|
||||
// {
|
||||
// float wTurb = worldTurbSpeed;
|
||||
// if (SyncWithUnityWindZone) wTurb *= SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul;
|
||||
|
||||
// float worldPosTurbulence = (.5f + Mathf.Sin(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.x * worldTurbScale) / 2f) + (.5f + Mathf.Cos(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.z * worldTurbScale) / 2f);
|
||||
// lengthRatio += worldPosTurbulence * worldTurb * t.WindWorldNoisePower;
|
||||
// }
|
||||
|
||||
// lengthRatio *= t.WindEffectPower;
|
||||
|
||||
// if (t.WindTurbulencePower > 0f)
|
||||
// t.WindEffect = new Vector3(targetWind.x * lengthRatio + finalAddTurbulence.x * t.WindTurbulencePower, targetWind.y * lengthRatio + finalAddTurbulence.y * t.WindTurbulencePower, targetWind.z * lengthRatio + finalAddTurbulence.z * t.WindTurbulencePower);
|
||||
// else
|
||||
// t.WindEffect = new Vector3(targetWind.x * lengthRatio, targetWind.y * lengthRatio, targetWind.z * lengthRatio);
|
||||
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
public static void Refresh()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
UnityEngine.Debug.Log("[Tail Animator Wind] No Tail Animator Wind component on the scene!");
|
||||
UnityEngine.Debug.LogWarning("[Tail Animator Wind] No Tail Animator Wind component on the scene!");
|
||||
}
|
||||
}
|
||||
|
||||
public void AffectTailWithWind(TailAnimator2 t)
|
||||
{
|
||||
if (!t.UseWind) return;
|
||||
if (t.WindEffectPower <= 0f) return;
|
||||
if (t.TailSegments.Count <= 0) return;
|
||||
|
||||
float lengthRatio = 1f;
|
||||
if (powerDependOnTailLength)
|
||||
{
|
||||
lengthRatio = (t._TC_TailLength * t.TailSegments[0].transform.lossyScale.z) / 5f;
|
||||
if (t.TailSegments.Count > 3) lengthRatio *= Mathf.Lerp(0.7f, 3f, t.TailSegments.Count / 14f);
|
||||
}
|
||||
|
||||
if (t.WindWorldNoisePower > 0f)
|
||||
{
|
||||
float wTurb = worldTurbSpeed;
|
||||
if (SyncWithUnityWindZone) wTurb *= SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul;
|
||||
|
||||
float worldPosTurbulence = (.5f + Mathf.Sin(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.x * worldTurbScale) / 2f) + (.5f + Mathf.Cos(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.z * worldTurbScale) / 2f);
|
||||
lengthRatio += worldPosTurbulence * worldTurb * t.WindWorldNoisePower;
|
||||
}
|
||||
|
||||
lengthRatio *= t.WindEffectPower;
|
||||
|
||||
if (t.WindTurbulencePower > 0f)
|
||||
t.WindEffect = new Vector3(targetWind.x * lengthRatio + finalAddTurbulence.x * t.WindTurbulencePower, targetWind.y * lengthRatio + finalAddTurbulence.y * t.WindTurbulencePower, targetWind.z * lengthRatio + finalAddTurbulence.z * t.WindTurbulencePower);
|
||||
else
|
||||
t.WindEffect = new Vector3(targetWind.x * lengthRatio, targetWind.y * lengthRatio, targetWind.z * lengthRatio);
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
int numCount = 10;
|
||||
randNumbers = new float[numCount];
|
||||
randTimes = new float[numCount];
|
||||
randSpeeds = new float[numCount];
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
randNumbers[i] = Random.Range(-1000f, 1000f);
|
||||
randTimes[i] = Random.Range(-1000f, 1000f);
|
||||
randSpeeds[i] = Random.Range(0.18f, 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComputeWind()
|
||||
{
|
||||
|
||||
Vector3 newWind;
|
||||
|
||||
if (SyncWithUnityWindZone)
|
||||
{
|
||||
newWind = SyncWithUnityWindZone.transform.forward * SyncWithUnityWindZone.windMain * UnityWindZonePowerMul;
|
||||
transform.rotation = SyncWithUnityWindZone.transform.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overrideWind != Vector3.zero) newWind = overrideWind;
|
||||
else // Procedural wind
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turbulenceSpeed;
|
||||
|
||||
Quaternion windDir = windOrientation;
|
||||
|
||||
float x = -1f + Mathf.PerlinNoise(randTimes[0], 256f + randTimes[1]) * 2f;
|
||||
float y = -1f + Mathf.PerlinNoise(-randTimes[1], 55f + randTimes[2]) * 2f;
|
||||
float z = -1f + Mathf.PerlinNoise(-randTimes[3], 55f + randTimes[0]) * 2f;
|
||||
windDir *= Quaternion.Euler(new Vector3(0, y, 0) * changesPower);
|
||||
windDir = Quaternion.Euler(x * (changesPower / 6f), windDir.eulerAngles.y, z * (changesPower / 6f));
|
||||
|
||||
smoothWindOrient = FEngineering.SmoothDampRotation(smoothWindOrient, windDir, ref smoothWindOrientHelper, 1f - rapidness, Time.deltaTime);
|
||||
|
||||
transform.rotation = smoothWindOrient;
|
||||
newWind = smoothWindOrient * Vector3.forward;
|
||||
}
|
||||
}
|
||||
|
||||
// Additional turbulence
|
||||
smoothAddTurbulence = Vector3.SmoothDamp(smoothAddTurbulence, GetAddTurbulence() * additionalTurbulence, ref addTurbHelper, 0.05f, Mathf.Infinity, Time.deltaTime);
|
||||
|
||||
// Smooth out
|
||||
smoothWind = Vector3.SmoothDamp(smoothWind, newWind, ref windVeloHelper, 0.1f, Mathf.Infinity, Time.deltaTime);
|
||||
|
||||
for (int i = 7; i < 10; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turbulenceSpeed;
|
||||
|
||||
float turbulencedPower = power * 0.015f;
|
||||
turbulencedPower *= 0.5f + Mathf.PerlinNoise(randTimes[7] * 2f, 25 + randTimes[8] * 0.5f);
|
||||
|
||||
finalAddTurbulence = smoothAddTurbulence * turbulencedPower;
|
||||
targetWind = smoothWind * turbulencedPower;
|
||||
}
|
||||
|
||||
Vector3 finalAddTurbulence = Vector3.zero;
|
||||
Vector3 addTurbHelper = Vector3.zero;
|
||||
private Vector3 GetAddTurbulence()
|
||||
{
|
||||
float turb = additionalTurbSpeed;
|
||||
if (SyncWithUnityWindZone) turb *= (SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul);
|
||||
|
||||
for (int i = 4; i < 7; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turb;
|
||||
|
||||
float x = -1f + Mathf.PerlinNoise(randTimes[4] + 7.123f, -2.324f + Time.time * 0.24f) * 2f;
|
||||
float y = -1f + Mathf.PerlinNoise(randTimes[5] - 4.7523f, -25.324f + Time.time * 0.54f) * 2f;
|
||||
float z = -1f + Mathf.PerlinNoise(randTimes[6] + 1.123f, -63.324f + Time.time * -0.49f) * 2f;
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
Vector3 smoothAddTurbulence = Vector3.zero;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collecting tail animator components from scene (WARNING: Don't execute it every frame)
|
||||
/// </summary>
|
||||
//public void GetTailAnimatorsFromScene()
|
||||
//{
|
||||
// TailAnimator2[] tails = FindObjectsOfType<TailAnimator2>();
|
||||
// for (int i = 0; i < tails.Length; i++)
|
||||
// {
|
||||
// if (!WindAffected.Contains(tails[i])) WindAffected.Add(tails[i]);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca32c544193be8047ad129a8e7f52f78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 73e848e8b105ad4419739438d540ff07, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using UnityEngine;
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
using UnityEngine.Tilemaps;
|
||||
#endif
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FM: Simple class sending collision events to main script
|
||||
/// </summary>
|
||||
[AddComponentMenu("FImpossible Creations/Hidden/Tail Collision Helper")]
|
||||
public class TailCollisionHelper : MonoBehaviour
|
||||
{
|
||||
public TailAnimator2 ParentTail;
|
||||
public Collider TailCollider;
|
||||
public Collider2D TailCollider2D;
|
||||
public int Index;
|
||||
|
||||
internal Rigidbody RigBody { get; private set; }
|
||||
internal Rigidbody2D RigBody2D { get; private set; }
|
||||
Transform previousCollision;
|
||||
|
||||
internal TailCollisionHelper Init(bool addRigidbody = true, float mass = 1f, bool kinematic = false)
|
||||
{
|
||||
if (TailCollider2D == null)
|
||||
{
|
||||
if (addRigidbody)
|
||||
{
|
||||
Rigidbody rig = GetComponent<Rigidbody>();
|
||||
if (!rig) rig = gameObject.AddComponent<Rigidbody>();
|
||||
rig.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
rig.useGravity = false;
|
||||
rig.isKinematic = kinematic;
|
||||
rig.constraints = RigidbodyConstraints.FreezeAll;
|
||||
rig.mass = mass;
|
||||
RigBody = rig;
|
||||
}
|
||||
else
|
||||
{
|
||||
RigBody = GetComponent<Rigidbody>();
|
||||
if (RigBody) RigBody.mass = mass;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (addRigidbody)
|
||||
{
|
||||
Rigidbody2D rig = GetComponent<Rigidbody2D>();
|
||||
if (!rig) rig = gameObject.AddComponent<Rigidbody2D>();
|
||||
rig.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
rig.gravityScale = 0f;
|
||||
rig.isKinematic = kinematic;
|
||||
rig.constraints = RigidbodyConstraints2D.FreezeAll;
|
||||
rig.mass = mass;
|
||||
RigBody2D = rig;
|
||||
}
|
||||
else
|
||||
{
|
||||
RigBody2D = GetComponent<Rigidbody2D>();
|
||||
if (RigBody2D) RigBody2D.mass = mass;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (ParentTail == null)
|
||||
{
|
||||
GameObject.Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
TailCollisionHelper helper = collision.transform.GetComponent<TailCollisionHelper>();
|
||||
|
||||
if (helper)
|
||||
{
|
||||
if (ParentTail.CollideWithOtherTails == false) return;
|
||||
if (helper.ParentTail == ParentTail) return;
|
||||
}
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(collision.transform)) return;
|
||||
if (ParentTail.IgnoredColliders.Contains(collision.collider)) return;
|
||||
|
||||
ParentTail.CollisionDetection(Index, collision);
|
||||
previousCollision = collision.transform;
|
||||
}
|
||||
|
||||
|
||||
void OnCollisionExit(Collision collision)
|
||||
{
|
||||
if (collision.transform == previousCollision)
|
||||
{
|
||||
ParentTail.ExitCollision(Index);
|
||||
previousCollision = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.isTrigger) return;
|
||||
|
||||
if (ParentTail.IgnoreMeshColliders)
|
||||
if (other is MeshCollider) return;
|
||||
|
||||
if (other is CharacterController) return;
|
||||
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(other.transform)) return;
|
||||
if (ParentTail.IgnoredColliders.Contains(other)) return;
|
||||
|
||||
if (ParentTail.CollideWithOtherTails == false)
|
||||
{
|
||||
TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
if (helper) return;
|
||||
//if (ParentTail.CollideWithOtherTails == false) return;
|
||||
//if (helper.ParentTail == ParentTail) return;
|
||||
}
|
||||
|
||||
ParentTail.AddCollider(other);
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (ParentTail.IncludedColliders.Contains(other))
|
||||
{
|
||||
if (!ParentTail.DynamicAlwaysInclude.Contains(other))
|
||||
ParentTail.IncludedColliders.Remove(other);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (other.isTrigger) return;
|
||||
|
||||
if (other is CompositeCollider2D) return;
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
if (other is TilemapCollider2D) return;
|
||||
#endif
|
||||
if (other is EdgeCollider2D) return;
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(other.transform)) return;
|
||||
if (ParentTail.IgnoredColliders2D.Contains(other)) return;
|
||||
|
||||
//TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
//if (helper)
|
||||
//{
|
||||
// if (ParentTail.CollideWithOtherTails == false) return;
|
||||
// if (helper.ParentTail == ParentTail) return;
|
||||
//}
|
||||
|
||||
if (ParentTail.CollideWithOtherTails == false)
|
||||
{
|
||||
TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
if (helper) return;
|
||||
}
|
||||
|
||||
ParentTail.AddCollider(other);
|
||||
}
|
||||
|
||||
void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
if (ParentTail.IncludedColliders2D.Contains(other))
|
||||
{
|
||||
if (!ParentTail.DynamicAlwaysInclude.Contains(other))
|
||||
ParentTail.IncludedColliders2D.Remove(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea3bd5313553529418ec44a77ec6f94f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a05a4f2884a2af44eaeedea1ea5ca396, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user