res-avatar-unity/Assets/_PoiyomiShaders/TPS/Editor/TPS_Setup.cs

2411 lines
120 KiB
C#
Raw Normal View History

2023-07-16 02:51:23 +00:00
// TPS Setup System. Setups up penetrators, orifices, and the animator. Uses a ton of VRC Functions so it doesnt make sense to make it non vrc compatible
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.Animations;
using UnityEngine;
using static Thry.TPS.ThryAnimatorFunctions;
using System.Text.RegularExpressions;
using static Thry.TPS.BakeToVertexColors;
using UnityEditor.SceneManagement;
using UnityEngine.SceneManagement;
#if VRC_SDK_VRCSDK3 && !UDON
using VRC.SDK3.Avatars.Components;
using VRC.SDK3.Dynamics.Contact.Components;
using static VRC.SDK3.Avatars.Components.VRCAvatarDescriptor;
using static VRC.SDKBase.VRC_AvatarParameterDriver;
#endif
namespace Thry.TPS
{
public class TPS_Setup : EditorWindow
{
const string VERSION = "1.2.12";
[MenuItem("Poi/TPS Setup Wizard", priority = 100)]
static void Init()
{
TPS_Setup window = (TPS_Setup)EditorWindow.GetWindow(typeof(TPS_Setup));
window.titleContent = new GUIContent("TPS Setup Wizard");
window.Show();
}
string UniquePath(string path, string postFix)
{
if (File.Exists(path + postFix))
{
int i = 0;
while (File.Exists(path + i + postFix)) i++;
path = path + i;
}
return path + postFix;
}
void FindAvatarDirectory()
{
string path = AssetDatabase.GetAssetPath(_avatar);
if (string.IsNullOrEmpty(path) && _avatar.GetComponent<Animator>()) path = AssetDatabase.GetAssetPath(_avatar.GetComponent<Animator>().avatar);
if (string.IsNullOrEmpty(path) && _animator != null) path = AssetDatabase.GetAssetPath(_animator);
if (string.IsNullOrEmpty(path))
{
Debug.LogError("[TPS] Could not find avatar file path. Using Assets folder. Make sure your avatar is a prefab or your animator has an avatar assigned in the future.");
_avatarDirectory = "Assets";
return;
}
_avatarDirectory = Path.GetDirectoryName(path);
}
const float ORF_HOLE_RANGE_ID = 0.41f;
const float ORF_RING_RANGE_ID = 0.42f;
const float ORF_NORM_RANGE_ID = 0.45f;
public enum OrificeType
{
Hole, Ring
}
public class PenetratorConfig
{
public Transform Transform;
public Transform TransformTip;
public bool IsBaked;
public bool HasMesh;
public Renderer Renderer;
public Texture2D Mask;
public bool EditMask;
public bool Remove;
public PenetratorConfig(Transform t)
{
Transform = t;
Renderer = t.GetComponentInChildren<Renderer>();
OnRendererChanged();
}
public void SetTransform(Transform t)
{
Transform = t;
Renderer = t.GetComponentInChildren<Renderer>();
OnRendererChanged();
}
void OnRendererChanged()
{
if (Renderer == null) return;
HasMesh = GetMesh(Renderer) != null;
if (!HasMesh) SetBaked(false);
else if (Renderer is SkinnedMeshRenderer) SetBaked(AreVerteciesBaked(Renderer));
else if (Renderer is MeshRenderer) SetBaked(false);
}
public void SetBaked(bool b)
{
if (Renderer == null) return;
IsBaked = b;
foreach (Material m in Renderer.sharedMaterials.Where(m => m != null))
{
m.SetFloat("_TPS_IsSkinnedMeshRenderer", b ? 1 : 0);
if(b) m.EnableKeyword("TPS_IsSkinnedMesh");
else m.DisableKeyword("TPS_IsSkinnedMesh");
}
}
}
public class OrificeConfig
{
public Transform Transform;
public OrificeType OrificeType;
public Renderer Renderer;
public string[] BlendshapeNames = new string[] { "none" };
public int BlendShapeIndexEnter = 1;
public int BlendShapeIndexIn = 2;
public float MaxOpeningWidth = 1;
public bool ScaleBlendshapesByWidth = true;
public bool DoAnimatorSetup = true;
public float MaxDepth = 1;
public bool AllowTransformEditing;
public bool Remove;
public OrificeConfig()
{
AllowTransformEditing = true;
}
public OrificeConfig(Transform t)
{
Transform = t;
Renderer = t.GetComponentsInChildren<Renderer>().Where(r => r != null && GetMesh(r) != null).OrderBy(r => r is SkinnedMeshRenderer ? GetMesh(r).blendShapeCount : 0).Reverse().FirstOrDefault();
if (Renderer == null && t.parent != null) Renderer = t.parent.GetComponent<Renderer>();
if (Renderer != null) SetRenderer(Renderer);
OrificeType = t.GetComponentsInChildren<Light>().Any(l => l.range == ORF_RING_RANGE_ID) ? OrificeType.Ring : OrificeType.Hole;
ConfigureLights();
}
public void SetRenderer(Renderer r)
{
Renderer = r;
LoadBlendshapes();
ChangedSelectedShapekeys();
CalculateMaxDepth();
}
void CalculateMaxDepth()
{
if (Renderer == null) return;
Mesh mesh = GetMesh(Renderer); ;
if (mesh == null)
{
Debug.LogWarning("[TPS][SetupPenetrator] Mesh is null.");
return;
}
Vector3 forwardVec = (Renderer.transform.worldToLocalMatrix * Transform.forward).normalized;
IEnumerable<float> zDistances = mesh.vertices.Select(v => Vector3.Dot(v, forwardVec));
MaxDepth = (zDistances.Max() - zDistances.Min()) * Renderer.transform.lossyScale.z;
}
public void LoadBlendshapes()
{
BlendshapeNames = new string[] { "none" };
if (Renderer != null && Renderer is SkinnedMeshRenderer)
{
Mesh skinnedMesh = (Renderer as SkinnedMeshRenderer).sharedMesh;
if (skinnedMesh != null && skinnedMesh.blendShapeCount > 0)
{
BlendshapeNames = new string[skinnedMesh.blendShapeCount + 1];
for (int b = 0; b < skinnedMesh.blendShapeCount; b++)
BlendshapeNames[b + 1] = skinnedMesh.GetBlendShapeName(b);
BlendshapeNames[0] = "~none~";
}
}
}
public void ChangedSelectedShapekeys()
{
if (Renderer == null) return;
if (Renderer is SkinnedMeshRenderer)
{
MaxOpeningWidth = 0;
Mesh m = (Renderer as SkinnedMeshRenderer).sharedMesh;
Vector3[] vertecies = new Vector3[m.vertexCount];
Vector3[] normals = new Vector3[m.vertexCount];
Vector3[] tangents = new Vector3[m.vertexCount];
if (BlendShapeIndexEnter - 1 < m.blendShapeCount)
{
m.GetBlendShapeFrameVertices(BlendShapeIndexEnter - 1, m.GetBlendShapeFrameCount(BlendShapeIndexEnter - 1) - 1, vertecies, normals, tangents);
MaxOpeningWidth = Mathf.Max(MaxOpeningWidth, vertecies.Select(v => new Vector3(v.x * Renderer.transform.lossyScale.x, v.y * Renderer.transform.lossyScale.y, 0).magnitude).Max() * 2);
}
if (BlendShapeIndexIn - 1 < m.blendShapeCount)
{
m.GetBlendShapeFrameVertices(BlendShapeIndexIn - 1, m.GetBlendShapeFrameCount(BlendShapeIndexIn - 1) - 1, vertecies, normals, tangents);
MaxOpeningWidth = Mathf.Max(MaxOpeningWidth, vertecies.Select(v => new Vector3(v.x * Renderer.transform.lossyScale.x, v.y * Renderer.transform.lossyScale.y, 0).magnitude).Max() * 2);
}
}
}
public string GetBlendshapeNameEnter()
{
return BlendshapeNames[BlendShapeIndexEnter];
}
public string GetBlendshapeNameIn()
{
return BlendshapeNames[BlendShapeIndexIn];
}
public void ConfigureLights()
{
float idPos = OrificeType == OrificeType.Hole ? ORF_HOLE_RANGE_ID : ORF_RING_RANGE_ID;
float idNor = ORF_NORM_RANGE_ID;
bool foundPos = false, foundNor = false;
Light[] lights = Transform.GetComponentsInChildren<Light>(true);
foreach (Light l in lights)
{
if (!foundPos && l.range == idPos) foundPos = true;
else if (!foundNor && l.range == idNor) foundNor = true;
else if (PrefabUtility.IsPartOfAnyPrefab(l.gameObject)) DestroyImmediate(l);
else DestroyImmediate(l.gameObject);
}
if (!foundPos)
{
Transform lt = SingletonChild(Transform, "Position");
if (OrificeType == OrificeType.Hole) AddLight(lt, ORF_HOLE_RANGE_ID);
else AddLight(lt, ORF_RING_RANGE_ID);
}
if (!foundNor)
{
Transform lt = SingletonChild(Transform, "Normal");
lt.localPosition = Vector3.forward * 0.01f / Transform.lossyScale.z;
AddLight(lt, ORF_NORM_RANGE_ID);
}
}
void AddLight(Transform t, float range)
{
Light l = t.gameObject.AddComponent<Light>();
l.type = LightType.Point;
l.color = Color.black;
l.range = range;
l.shadows = LightShadows.None;
l.renderMode = LightRenderMode.ForceVertex;
}
}
#region GUI
static GUIStyle s_styleRichtText;
static GUIStyle s_styleRichtTextCentered;
void InitStyles()
{
s_styleRichtText = new GUIStyle(EditorStyles.boldLabel) { richText = true };
s_styleRichtTextCentered = new GUIStyle(EditorStyles.boldLabel) { richText = true, alignment = TextAnchor.LowerCenter };
}
Transform _avatar;
string _avatarDirectory;
bool _doesAnimatorHaveWDOn;
AnimatorController _animator;
List<PenetratorConfig> _penetrators;
List<OrificeConfig> _orifices;
bool _doClear;
Vector2 _scrolling;
Prefab[] _prefabsOrifice;
Prefab[] _prefabsPenetrator;
Color _backgroundColor;
private void OnGUI()
{
InitStyles();
_backgroundColor = GUI.backgroundColor;
GUILayout.Space(10);
EditorGUILayout.LabelField($"<color=fuchsia><size=25> Thry's Penetration System </size><size=16>v{VERSION}</size></color>", s_styleRichtTextCentered, GUILayout.Height(30));
//EditorGUILayout.HelpBox("Follow this tool to setup TPS on your avatar.", MessageType.None);
_scrolling = EditorGUILayout.BeginScrollView(_scrolling);
GUILayout.Space(10);
if(Tools.pivotMode != PivotMode.Pivot || !SceneView.lastActiveSceneView.sceneLighting)
{
Box("<size=20>0. Unity Settings Problems</size>", 25, Color.red, GUI_EditorProblems, null);
GUILayout.Space(10);
}
Box("<size=20>1. Add Prefabs to avatar</size> <size=10>Click here to refresh</size>", 25, Color.green, GUI_PrefabsList, FindTPSPrefabs);
GUILayout.Space(5);
Box("<size=20>2. Scan Avatar</size>", 25, Color.blue, GUI_Setup, null);
#if VRC_SDK_VRCSDK3 && !UDON
if (_penetrators == null || _animator == null || _avatar == null)
#else
if (_penetrators == null || _avatar == null)
#endif
{
EditorGUILayout.EndScrollView();
return;
}
GUILayout.Space(5);
Box("<size=20>2.3. Penetrators: </size><size=15>Make sure they have their vertex colors baked</size>", 25, Color.cyan, GUI_Penetrators, null);
GUILayout.Space(5);
Box("<size=20>2.4. Orifices: </size><size=15>Configure your orifice options</size>", 25, Color.yellow, GUI_Orifices, null);
GUILayout.Space(5);
Box("<size=20>3. !Make sure to apply your setup!</size>", 25, Color.red, GUI_Button_Apply, null);
GUILayout.Space(5);
Box("<size=20>Help and Information</size>", 25, Color.gray, GUI_Information, null);
GUILayout.Space(5);
Box("<size=20>Removal options</size>", 25, Color.gray, GUI_Buttons_Remove, null);
EditorGUILayout.EndScrollView();
}
void Box(string label, float labelHeight, Color color, Action guiFunction, Action onHeaderClick)
{
GUI.backgroundColor = color;
using (new GUILayout.VerticalScope("Box"))
{
EditorGUILayout.LabelField(label, s_styleRichtText, GUILayout.Height(labelHeight));
if (onHeaderClick != null && Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
onHeaderClick.Invoke();
GUI.backgroundColor = _backgroundColor;
if (guiFunction != null) guiFunction.Invoke();
}
}
void GUI_EditorProblems()
{
if (!SceneView.lastActiveSceneView.sceneLighting)
{
EditorGUILayout.HelpBox("Scene Lighting is not turned on. The penetrators will not move towards orifices.", MessageType.Error);
if (GUILayout.Button("Fix SceneLighting")) SceneView.lastActiveSceneView.sceneLighting = true;
}
if (Tools.pivotMode != PivotMode.Pivot)
{
EditorGUILayout.HelpBox("Pivot Mode is set not set to pivot. This might be confusing while setting up penetrators.", MessageType.Warning);
if (GUILayout.Button("Fix Pivot Mode")) Tools.pivotMode = PivotMode.Pivot;
}
}
void GUI_PrefabsList()
{
if (_prefabsOrifice == null) FindTPSPrefabs();
EditorGUI.indentLevel += 2;
EditorGUILayout.LabelField("You can drag the listed assets directly from here into the scene", s_styleRichtText);
EditorGUI.indentLevel -= 2;
GUILayout.BeginHorizontal();
GUILayout.Space(20);
GUI.backgroundColor = Color.black;
using (new GUILayout.VerticalScope("Box"))
{
EditorGUILayout.LabelField("Penetrators", s_styleRichtText);
GUI.backgroundColor = _backgroundColor;
foreach (Prefab o in _prefabsPenetrator) PrefabListing(o);
}
GUI.backgroundColor = Color.black;
using (new GUILayout.VerticalScope("Box"))
{
EditorGUILayout.LabelField("Orifices", s_styleRichtText);
GUI.backgroundColor = _backgroundColor;
foreach (Prefab o in _prefabsOrifice) PrefabListing(o);
}
GUILayout.EndHorizontal();
if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
EditorGUIUtility.PingObject(AssetDatabase.LoadAssetAtPath(AssetDatabase.FindAssets("PenetratorSetup t:TextAsset").Select(g => AssetDatabase.GUIDToAssetPath(g)).Where(p => p.EndsWith(".txt")).FirstOrDefault(), typeof(TextAsset)));
}
void PrefabListing(Prefab prefab)
{
Rect r = EditorGUILayout.GetControlRect(GUILayout.Height(70));
Rect objectField = new Rect(r);
objectField.width -= 70;
Rect textureRect = new Rect(r);
textureRect.x += objectField.width;
textureRect.width = 70;
GUI.DrawTexture(textureRect, prefab.GetTexture());
if (Event.current.isMouse && Event.current.type == EventType.MouseDrag && r.Contains(Event.current.mousePosition))
{
DragAndDrop.PrepareStartDrag();
DragAndDrop.objectReferences = new UnityEngine.Object[] { prefab.GameObject };
DragAndDrop.StartDrag("Dragging TPS Prefab");
Event.current.Use();
}
EditorGUI.ObjectField(objectField, prefab.GameObject, typeof(GameObject), false);
}
void GUI_Setup()
{
GUILayout.BeginHorizontal();
GUILayout.Space(20);
GUILayout.BeginVertical();
GUILayout.Space(5);
EditorGUILayout.LabelField("<size=15>2.1 Select your avatar from the scene.</size>", s_styleRichtText);
EditorGUI.BeginChangeCheck();
EditorGUI.BeginChangeCheck();
_avatar = (Transform)EditorGUILayout.ObjectField("Avatar", _avatar, typeof(Transform), true);
if (EditorGUI.EndChangeCheck()) _animator = null;
if(_avatar == null)
{
#if VRC_SDK_VRCSDK3 && !UDON
Scene scene = EditorSceneManager.GetActiveScene();
if (scene != null) _avatar = scene.GetRootGameObjects().
Where(g => g.activeSelf && g.GetComponent<VRCAvatarDescriptor>()).Select(g => g.transform).
OrderByDescending(t =>
t.GetComponentsInChildren<Renderer>().Where(r => IsRendererPenetrator(r)).Count() +
t.GetComponentsInChildren<Light>().Where(l => GetOrificeRootFromLight(l.transform) != null).Count()).
FirstOrDefault();
#endif
}
AnimatorController prevAnim = _animator;
#if VRC_SDK_VRCSDK3 && !UDON
_animator = (AnimatorController)EditorGUILayout.ObjectField("Animator", _animator, typeof(AnimatorController), false);
bool changed = EditorGUI.EndChangeCheck();
if (_animator == null && _avatar != null && _avatar.GetComponent<VRCAvatarDescriptor>())
{
GUI_ResolveAssets();
}
if (prevAnim != _animator && _animator != null)
{
GUI_CheckAnimatorHasWDOn();
}
if (_animator != null && _avatar != null)
{
if (_doesAnimatorHaveWDOn)
{
EditorGUILayout.HelpBox("Your animator has at least one state with 'Write Defaults' turned on. This may cause problems with the TPS animator setup.", MessageType.Warning);
}
GUILayout.Space(10);
EditorGUILayout.LabelField("<size=15>2.2 Scan your avatar for TPS objects. Confirm all have been found.</size>", s_styleRichtText);
if (_penetrators == null)
ScanForTPS();
if (GUILayout.Button("Scan Avatar for TPS") || changed)
{
GUI_CheckAnimatorHasWDOn();
ScanForTPS();
}
}
#else
bool changed = EditorGUI.EndChangeCheck();
if (GUILayout.Button("Scan Avatar for TPS") || changed)
{
ScanForTPS();
}
#endif
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
bool showAddPenetratorHelpbox;
void GUI_Penetrators()
{
EditorGUILayout.HelpBox("Penetrators are identified by having TPS enabled on their material. Make sure the vertecies are baked. Use the tip field (optional) to update the direction.", MessageType.Info);
foreach (PenetratorConfig p in _penetrators)
{
GUI_Penetrator(p);
}
_penetrators.RemoveAll(p => p.Remove);
if (showAddPenetratorHelpbox)
{
EditorGUILayout.HelpBox("1.Add the penetrator onto your avatar 2.Enable TPS on a material of your penetrator 3. Scan again" +
"4. Create an empty gameobject at the tip of the penetrator and assign it to the tip field", MessageType.Info);
}
if (GUILayout.Button("Add Custom Penetrator")) showAddPenetratorHelpbox = !showAddPenetratorHelpbox;
}
void GUI_Orifices()
{
//EditorGUILayout.LabelField("<size=15>2.4 Orifices: Configure your orifice options.</size>", s_styleRichtText);
EditorGUILayout.HelpBox("Orifices are identified by their name & lights.", MessageType.Info);
foreach (OrificeConfig o in _orifices)
{
GUI_Orifice(o);
}
_orifices.RemoveAll(o => o.Remove);
if(_orifices.Any(o => o.AllowTransformEditing))
{
EditorGUILayout.HelpBox("Transform should be where your new orifice is and what direction it should point in. " +
"I recommend creating an empty GameObject under your renderer and moving it to the correct spot. (Blue arrow should be pointing outwards from the orifice)", MessageType.Info);
}
if (GUILayout.Button("Add Custom Orifice")) _orifices.Add(new OrificeConfig());
}
#if VRC_SDK_VRCSDK3 && !UDON
void GUI_ResolveAssets()
{
VRCAvatarDescriptor d = _avatar.GetComponent<VRCAvatarDescriptor>();
IEnumerable<CustomAnimLayer> fxlayers = d.baseAnimationLayers.Where(l => l.type == VRCAvatarDescriptor.AnimLayerType.FX && l.animatorController != null);
if (fxlayers.Count() > 0)
{
_animator = AssetDatabase.LoadAssetAtPath<AnimatorController>(AssetDatabase.GetAssetPath(fxlayers.First().animatorController));
ScanForTPS();
}
else if (GUILayout.Button("Create FX Layer"))
{
FindAvatarDirectory();
if (string.IsNullOrEmpty(_avatarDirectory) == false)
{
string path = _avatarDirectory + "/FX_" + _avatar.name;
_animator = AnimatorController.CreateAnimatorControllerAtPathWithClip(UniquePath(path, ".asset"), EmptyClip);
_animator.layers[0].stateMachine.states[0].state.writeDefaultValues = false;
CustomAnimLayer[] layers = d.baseAnimationLayers;
if (layers.Length < 5) Array.Resize<CustomAnimLayer>(ref layers, 5);
layers[4] = new CustomAnimLayer() { animatorController = _animator, type = AnimLayerType.FX };
d.baseAnimationLayers = layers;
d.customizeAnimationLayers = true;
}
}
}
#endif
void GUI_CheckAnimatorHasWDOn()
{
_doesAnimatorHaveWDOn = _animator.layers.Any(l => FindAllStates(l.stateMachine).Any(s => s.writeDefaultValues));
}
IEnumerable<AnimatorState> FindAllStates(AnimatorStateMachine m)
{
if (m == null) return new AnimatorState[0];
return m.stateMachines.SelectMany(sm => FindAllStates(sm.stateMachine)).Concat(m.states.Select(s => s.state));
}
void GUI_Penetrator(PenetratorConfig p)
{
GUI.backgroundColor = Color.black;
using (new GUILayout.VerticalScope("box"))
{
GUI.backgroundColor = _backgroundColor;
GUILayout.BeginHorizontal();
EditorGUILayout.ObjectField(p.Transform, typeof(Transform), true, GUILayout.Width(150));
EditorGUILayout.BeginVertical();
p.TransformTip = EditorGUILayout.ObjectField("Tip", p.TransformTip, typeof(Transform), true) as Transform;
if (!p.EditMask) if (GUILayout.Button("Edit Mask")) p.EditMask = true;
if(p.EditMask) p.Mask = EditorGUILayout.ObjectField("Mask", p.Mask, typeof(Texture2D), false) as Texture2D;
if (p.Renderer is MeshRenderer && p.EditMask &&GUI.Button(EditorGUILayout.GetControlRect(), "Apply Mask"))
{
foreach (Material m in p.Renderer?.sharedMaterials.Where(m => m != null))
m.SetTexture("_TPS_BakedMesh", p.Mask);
}
if (p.Renderer == null) EditorGUILayout.LabelField("No Renderer");
else if (!p.HasMesh) EditorGUILayout.LabelField("No Mesh");
else if (p.Renderer is MeshRenderer) EditorGUILayout.LabelField("Static");
else if (p.IsBaked && !p.EditMask) EditorGUILayout.LabelField("Is baked");
else if (GUI.Button(EditorGUILayout.GetControlRect(), "Bake Now"))
{
Texture2D tex = BakeToVertexColors.BakePositionsToTexture(p.Renderer, p.Mask);
foreach (Material m in p.Renderer?.sharedMaterials.Where(m => m != null))
m.SetTexture("_TPS_BakedMesh", tex);
p.SetBaked(true);
p.EditMask = false;
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
}
}
void GUI_Orifice(OrificeConfig o)
{
GUI.backgroundColor = Color.black;
using (new GUILayout.VerticalScope("box"))
{
GUI.backgroundColor = _backgroundColor;
GUILayout.BeginHorizontal();
if (o.AllowTransformEditing)
{
o.Transform = EditorGUILayout.ObjectField(o.Transform, typeof(Transform), true, GUILayout.Width(150)) as Transform;
}
else
{
EditorGUILayout.ObjectField(o.Transform, typeof(Transform), true, GUILayout.Width(150));
}
EditorGUILayout.BeginVertical();
EditorGUI.BeginChangeCheck();
o.OrificeType = (OrificeType)EditorGUILayout.EnumPopup("Type", o.OrificeType);
if (EditorGUI.EndChangeCheck())
{
o.ConfigureLights();
}
#if VRC_SDK_VRCSDK3 && !UDON
o.DoAnimatorSetup = EditorGUILayout.Toggle("Animator Setup (optional)", o.DoAnimatorSetup);
if (o.DoAnimatorSetup)
{
EditorGUI.BeginChangeCheck();
Renderer newR = EditorGUILayout.ObjectField("Renderer", o.Renderer, typeof(Renderer), true) as Renderer;
if (EditorGUI.EndChangeCheck()) o.SetRenderer(newR);
o.MaxDepth = EditorGUILayout.FloatField("Max Depth", o.MaxDepth);
if (o.Renderer != null && o.Renderer is SkinnedMeshRenderer)
{
using (new GUILayout.VerticalScope("Shapekeys", EditorStyles.helpBox))
{
GUILayout.Space(15);
EditorGUI.BeginChangeCheck();
o.BlendShapeIndexEnter = EditorGUILayout.Popup("Entering", o.BlendShapeIndexEnter, o.BlendshapeNames);
o.BlendShapeIndexIn = EditorGUILayout.Popup("Full Penetration", o.BlendShapeIndexIn, o.BlendshapeNames);
if (EditorGUI.EndChangeCheck()) o.ChangedSelectedShapekeys();
o.MaxOpeningWidth = EditorGUILayout.FloatField("Max Orfice Width", o.MaxOpeningWidth);
o.ScaleBlendshapesByWidth = EditorGUILayout.Toggle("Scale Blendshapes by Width", o.ScaleBlendshapesByWidth);
}
}
}
#endif
EditorGUILayout.EndVertical();
GUILayout.EndHorizontal();
if(o.AllowTransformEditing && GUILayout.Button("Remove", GUILayout.Width(150))) o.Remove = true;
}
}
void GUI_Button_Apply()
{
using (new EditorGUI.DisabledScope(false))
{
if (GUILayout.Button("Apply"))
{
if (!Directory.Exists(_avatarDirectory + "/TPS_"+_avatar.name)) AssetDatabase.CreateFolder(_avatarDirectory, "TPS_" + _avatar.name);
string dir = _avatarDirectory + "/TPS_" + _avatar.name;
AssetDatabase.StartAssetEditing();
s_debugIndex = 0;
try
{
RemoveTPSFromAnimator();
_penetrators = _penetrators.Where(p => p.Transform != null).ToList();
_orifices = _orifices.Where(o => o.Transform != null).ToList();
for (int i = 0; i < _penetrators.Count; i++)
{
SetupPenetrator(_avatar, _animator, _penetrators[i], _penetrators, i, dir);
}
for (int i = 0; i < _orifices.Count; i++)
{
_orifices[i].ConfigureLights();
SetupOrifice(_avatar, _animator, _orifices[i].Transform, _orifices[i].Renderer, _orifices[i].OrificeType, _orifices[i], i, dir);
}
}
catch (Exception e)
{
Debug.LogError(e);
}
finally
{
AssetDatabase.StopAssetEditing();
AssetDatabase.Refresh();
if (_animator != null)
{
EditorUtility.SetDirty(_animator);
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(_animator));
}
}
}
}
}
void GUI_Information()
{
EditorGUILayout.LabelField("<size=15>Blendshapes don't move</size>", s_styleRichtText);
EditorGUILayout.HelpBox("The blendshapes & Buffered depth are driven by Avatar Dynamics Contacts. " +
"To see its effect in the editor you need Lyumas Avatar Emulator: https://github.com/lyuma/Av3Emulator/releases. Click here to open the link.", MessageType.Info);
if (Event.current.type == EventType.MouseDown && GUILayoutUtility.GetLastRect().Contains(Event.current.mousePosition))
Application.OpenURL("https://github.com/lyuma/Av3Emulator/releases");
}
void GUI_Buttons_Remove()
{
#if VRC_SDK_VRCSDK3 && !UDON
if (GUILayout.Button("Remove TPS From Animator & Physics"))
{
RemoveTPSFromAnimator();
RemoveVRCSendersAndRecievers(_avatar);
}
#endif
if (GUILayout.Button("Remove TPS Objects")) _doClear = !_doClear;
if (_doClear)
{
GUILayout.Label("Remove all TPS Objects ? (not reversible)");
GUILayout.BeginHorizontal();
if (GUILayout.Button("Yes"))
{
Transform[] transforms = _penetrators.Select(p => p.Transform).Concat(_orifices.Select(o => o.Transform)).ToArray();
foreach (Transform t in transforms)
{
if (t != null)
{
try
{
Undo.DestroyObjectImmediate(t.gameObject);
}
catch (Exception e)
{
Debug.Log(e);
}
}
}
_doClear = false;
}
if (GUILayout.Button("No"))
{
_doClear = false;
}
GUILayout.EndHorizontal();
}
}
class Prefab
{
public GameObject GameObject;
public Editor Editor;
public Texture2D Texture;
public Prefab(GameObject o)
{
GameObject = o;
}
public Texture2D GetTexture()
{
if (Texture) return Texture;
if (Application.isPlaying) return null;
GameObject temp = GameObject.Instantiate(GameObject);
foreach (SkinnedMeshRenderer smr in temp.GetComponentsInChildren<SkinnedMeshRenderer>())
smr.updateWhenOffscreen = true;
Editor = UnityEditor.Editor.CreateEditor(temp);
Texture = Editor.RenderStaticPreview(AssetDatabase.GetAssetPath(GameObject), null, 200, 200);
EditorWindow.DestroyImmediate(Editor);
EditorWindow.DestroyImmediate(temp);
return Texture;
}
}
#endregion
#region Helpers
void ScanForTPS()
{
_penetrators = _avatar.GetComponentsInChildren<Renderer>(true).Where(r => IsRendererPenetrator(r)).Select(
r => (PrefabUtility.IsPartOfAnyPrefab(r.gameObject) ? PrefabUtility.GetNearestPrefabInstanceRoot(r.gameObject) : r.gameObject).transform)
.Select(t => new PenetratorConfig(t)).ToList();
//_orifices = _avatar.GetComponentsInChildren<Transform>().Where(t => t.name.StartsWith("[TPS][Orifice]")).Select(t => new OrificeConfig(t)).ToList();
_orifices = _avatar.GetComponentsInChildren<Renderer>(true).Where(r => r != null).Select(r => GetOrificeRootFromRenderer(r.transform)).Concat(
_avatar.GetComponentsInChildren<Light>(true).Where(l => l != null).Select(l => GetOrificeRootFromLight(l.transform))).Where(t => t != null).Distinct()
.Select(t => new OrificeConfig(t)).ToList();
FindAvatarDirectory();
}
void FindTPSPrefabs()
{
_prefabsPenetrator = AssetDatabase.FindAssets("[TPS][Penetrator] t:prefab").Select(g => new Prefab(AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g)))).ToArray();
_prefabsOrifice = AssetDatabase.FindAssets("[TPS][Orifice] t:prefab").Select(g => new Prefab(AssetDatabase.LoadAssetAtPath<GameObject>(AssetDatabase.GUIDToAssetPath(g)))).ToArray();
}
void RemoveTPSFromAnimator()
{
AnimatorControllerParameter[] parameters = _animator.parameters;
parameters = parameters.Where(p => p.name.StartsWith("TPS") == false).ToArray();
_animator.parameters = parameters;
AnimatorControllerLayer[] layers = _animator.layers;
layers = layers.Where(l => l.name.StartsWith("[TPS]") == false).ToArray();
_animator.layers = layers;
//Remove state machines from asset
UnityEngine.Object[] objects = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_animator));
foreach (UnityEngine.Object o in objects)
if (o != null && o.name.StartsWith("[TPS]"))
AssetDatabase.RemoveObjectFromAsset(o);
}
static bool IsRendererPenetrator(Renderer r)
{
if (r == null) return false;
return r.sharedMaterials.Any(m => m != null && m.HasProperty("_TPSPenetratorEnabled") && m.GetFloat("_TPSPenetratorEnabled") == 1);
}
Transform GetOrificeRootFromRenderer(Transform t)
{
if (t == null) return null;
if (t.gameObject.name.StartsWith("[TPS][Orifice]")) return t;
/*if (t.transform.GetComponentsInChildren<Light>(true).Any(l => l.range == ORF_NORM_RANGE_ID))
{
if (PrefabUtility.IsPartOfAnyPrefab(t.gameObject) && PrefabUtility.GetNearestPrefabInstanceRoot(t.gameObject) != _avatar.gameObject)
return PrefabUtility.GetNearestPrefabInstanceRoot(t.gameObject).transform;
return t;
}*/
Transform p = t;
while (p != null)
{
if (p.gameObject.name.StartsWith("[TPS][Orifice]")) return p;
p = p.parent;
}
return null;
}
Transform GetOrificeRootFromLight(Transform t)
{
if (t == null) return null;
float range = t.transform.GetComponent<Light>().range;
if (range == ORF_HOLE_RANGE_ID || range == ORF_RING_RANGE_ID) return t.parent;
return null;
}
static Mesh GetMesh(Renderer r)
{
if (r is SkinnedMeshRenderer)
{
SkinnedMeshRenderer smr = (r as SkinnedMeshRenderer);
/*Mesh bakedMesh = new Mesh();
Transform tr = r.transform;
Quaternion origRot = tr.localRotation;
Vector3 origScale = tr.localScale;
tr.localRotation = Quaternion.identity;
tr.localScale = Vector3.one;
smr.BakeMesh(bakedMesh);
tr.localRotation = origRot;
tr.localScale = origScale;
return bakedMesh;*/
return smr.sharedMesh;
}
if (r is MeshRenderer) return r.transform.GetComponent<MeshFilter>().sharedMesh;
return null;
}
static Transform SingletonChild(Transform parent, string childName)
{
//parent.Find does not find the objects in prefabs, so researching manually
for (int i = 0; i < parent.childCount; i++) if (parent.GetChild(i).name == childName) return parent.GetChild(i);
Transform t = new GameObject(childName).transform;
t.parent = parent;
t.localPosition = Vector3.zero;
t.localRotation = Quaternion.identity;
return t;
}
static bool AreVerteciesBaked(Renderer r)
{
MeshInfo meshInfo = GetAllMeshInfos(r)[0];
Texture tex = r.sharedMaterials.Where(m => m != null).Select(m => m.GetTexture("_TPS_BakedMesh")).FirstOrDefault();
if (tex == null) return false;
if (tex is Texture2D == false) return false;
if (tex.width < 8190) return false;
Texture2D tex2d = tex as Texture2D;
Vector3[] vertices = meshInfo.bakedVertices;
Color32[] colors = tex2d.GetPixels32();
for (int i = 0; i < vertices.Length; i++)
{
if (Mathf.Abs(vertices[i].x - BakeToVertexColors.DecodeFloatFromARGB8(colors[i * 6 + 0])) > 0.000001f) return false;
if (Mathf.Abs(vertices[i].y - BakeToVertexColors.DecodeFloatFromARGB8(colors[i * 6 + 1])) > 0.000001f) return false;
if (Mathf.Abs(vertices[i].z - BakeToVertexColors.DecodeFloatFromARGB8(colors[i * 6 + 2])) > 0.000001f) return false;
}
return true;
}
static void InstanciateMaterials(Transform avatar, Renderer r, string id, string directory, params Renderer[] instanciateIfMaterialReferencedByTheseRenderers)
{
if (PrefabUtility.IsPartOfAnyPrefab(r.gameObject) && PrefabUtility.GetNearestPrefabInstanceRoot(r.gameObject).transform != avatar)
{
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PrefabUtility.GetPrefabAssetPathOfNearestInstanceRoot(r.gameObject));
string localPath = AnimationUtility.CalculateTransformPath(r.transform, PrefabUtility.GetNearestPrefabInstanceRoot(r.gameObject).transform);
Renderer prefabRenderer = prefab.transform.Find(localPath)?.GetComponent<Renderer>();
if (prefabRenderer != null)
{
IEnumerable<Material> otherMaterials = instanciateIfMaterialReferencedByTheseRenderers.SelectMany(ren => ren.sharedMaterials);
Material[] prefabMaterials = prefabRenderer.sharedMaterials;
Material[] materials = r.sharedMaterials;
for (int i = 0; i < materials.Length; i++)
{
if (prefabMaterials[i] != materials[i] && otherMaterials.Contains(materials[i]) == false) continue;
Material copy = new Material(materials[i]);
AssetDatabase.CreateAsset(copy, directory + "/" + id + "_" + copy.name + ".mat");
materials[i] = copy;
}
r.sharedMaterials = materials;
}
}
}
private static void SetDefineSymbol(string symbol, bool active, bool refresh_if_changed)
{
try
{
string symbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(
BuildTargetGroup.Standalone);
if (!symbols.Contains(symbol) && active)
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(
BuildTargetGroup.Standalone, symbols + ";" + symbol);
if (refresh_if_changed)
AssetDatabase.Refresh();
}
else if (symbols.Contains(symbol) && !active)
{
PlayerSettings.SetScriptingDefineSymbolsForGroup(
BuildTargetGroup.Standalone, Regex.Replace(symbols, @";?" + @symbol, ""));
if (refresh_if_changed)
AssetDatabase.Refresh();
}
}
catch (Exception e)
{
e.ToString();
}
}
public static float RendererMaxDistanceIntoDirection(Renderer r, Vector3[] vertices, Vector3 localRendererDirection)
{
IEnumerable<float> zDistances = vertices.Select(v => Vector3.Dot(v, localRendererDirection));
return zDistances.Max() * Mathf.Abs(Vector3.Dot(r.transform.lossyScale, localRendererDirection));
}
public static Vector3 NearestPointOnLine(Vector3 linePnt, Vector3 lineDir, Vector3 pnt)
{
lineDir.Normalize();//this needs to be a unit vector
var v = pnt - linePnt;
var d = Vector3.Dot(v, lineDir);
return linePnt + lineDir * d;
}
static Vector3 RoundVector(Vector3 v, float steptsize)
{
return new Vector3(
(v.x + steptsize / 2) - (v.x + steptsize / 2) % steptsize,
(v.y + steptsize / 2) - (v.y + steptsize / 2) % steptsize,
(v.z + steptsize / 2) - (v.z + steptsize / 2) % steptsize);
}
#endregion
[InitializeOnLoad]
public class OnCompileHandler
{
static OnCompileHandler()
{
//Init Editor Variables with paths
SetDefineSymbol("TPS", true, true);
}
}
#region Penetrator
const string CONTACT_ORF_ROOT = "TPS_Orf_Root";
const string CONTACT_ORF_NORM = "TPS_Orf_Norm";
const string CONTACT_PEN_PENETRATING = "TPS_Pen_Penetrating";
const string CONTACT_PEN_WIDTH = "TPS_Pen_Width";
static int s_debugIndex = 0;
const float TPS_RECIEVER_DIST = 0.01f;
public static void SetupPenetrator(Transform avatar, AnimatorController animator, PenetratorConfig penetrator, List<PenetratorConfig> allPenetrators, int index, string directory, bool placeContacts = true, bool instanciateMaterials = true, bool configureMaterial = true, string pathSenderIsPenetrating = null, string pathSenderWidth = null)
{
#if VRC_SDK_VRCSDK3 && !UDON
//Remove senders, recievers
if (placeContacts) RemoveVRCSendersAndRecievers(penetrator.Transform);
//Get renderer & mesh
Renderer r = penetrator.Transform.GetComponentInChildren<Renderer>();
Transform rotationTransform = r.transform;
bool isSMR = r is SkinnedMeshRenderer;
// Find armature transform
if (isSMR)
{
rotationTransform = GetArmatureTransform(r as SkinnedMeshRenderer);
// Set root bone to armeture
(r as SkinnedMeshRenderer).rootBone = rotationTransform;
}
// get Forward vector
Vector3 forward = r.sharedMaterials.Where(m => m.HasProperty("_TPS_PenetratorForward")).Select(m => m.GetVector("_TPS_PenetratorForward")).FirstOrDefault();
Vector3 right = r.sharedMaterials.Where(m => m.HasProperty("_TPS_PenetratorRight")).Select(m => m.GetVector("_TPS_PenetratorRight")).FirstOrDefault();
Vector3 up = r.sharedMaterials.Where(m => m.HasProperty("_TPS_PenetratorUp")).Select(m => m.GetVector("_TPS_PenetratorUp")).FirstOrDefault();
if (penetrator.TransformTip)
{
Vector3 worldForward = (penetrator.TransformTip.position - penetrator.Transform.position).normalized;
Vector3 worldRight = penetrator.TransformTip.right;
if (Vector3.Dot(worldForward, Vector3.up) < 1) worldRight = Vector3.Cross(Vector3.up, worldForward).normalized;
forward = (rotationTransform.transform.worldToLocalMatrix * worldForward).normalized;
right = (rotationTransform.transform.worldToLocalMatrix * worldRight).normalized;
up = (Vector3.Cross(forward, right)).normalized;
}
forward = RoundVector(forward, 0.0001f);
right = RoundVector(right, 0.0001f);
up = RoundVector(up, 0.0001f);
// Instanciate material
if (instanciateMaterials) InstanciateMaterials(avatar, r, "Pen" + index, directory, allPenetrators.Where(p => p != penetrator && p.Renderer).Select(p => p.Renderer).ToArray());
Mesh mesh = GetMesh(r); ;
if (mesh == null)
{
Debug.LogError("[TPS][SetupPenetrator] Mesh is null.");
return;
}
//Calc length
float length = RendererMaxDistanceIntoDirection(r, mesh.vertices, forward);
float lengthBack = RendererMaxDistanceIntoDirection(r, mesh.vertices, -forward);
float width = mesh.vertices.Select(v => v - NearestPointOnLine(Vector3.zero, forward, v)).
Select(v => (v * Mathf.Abs(Vector3.Dot(r.transform.lossyScale, v.normalized))).magnitude).Average() * 2;
//Configure material
if (configureMaterial)
{
foreach (Material m in r.sharedMaterials)
{
m.EnableKeyword("TPS_Penetrator");
m.SetFloat("_TPSPenetratorEnabled", 1);
m.SetFloat("_TPS_PenetratorLength", length);
m.SetVector("_TPS_PenetratorScale", r.transform.lossyScale);
m.SetVector("_TPS_PenetratorForward", forward);
m.SetVector("_TPS_PenetratorRight", right);
m.SetVector("_TPS_PenetratorUp", up);
}
}
// Setup bounds
if (r is SkinnedMeshRenderer)
{
SkinnedMeshRenderer smr = r as SkinnedMeshRenderer;
//Fix bounding box (if bounding box is too small, penetrator will not get light data early enough
Bounds bounds = smr.localBounds;
bounds.Encapsulate(new Vector3(2 * length / r.transform.lossyScale.x, 0, 0));
bounds.Encapsulate(new Vector3(-2 * length / r.transform.lossyScale.x, 0, 0));
bounds.Encapsulate(new Vector3(0, 2 * length / r.transform.lossyScale.y, 0));
bounds.Encapsulate(new Vector3(0, -2 * length / r.transform.lossyScale.y, 0));
bounds.Encapsulate(new Vector3(0, 0, 2 * length / r.transform.lossyScale.z));
smr.localBounds = bounds;
smr.updateWhenOffscreen = false;
}
//parameter names
string paramFloatRootRoot = "TPS_Internal/Pen/" + index + "/RootRoot";
string paramFloatRootFrwd = "TPS_Internal/Pen/" + index + "/RootForw";
string paramFloatBackRoot = "TPS_Internal/Pen/" + index + "/BackRoot";
string paramFloatComp1 = "TPS_Internal/Pen/" + index + "/Comp1";
string paramFloatComp2 = "TPS_Internal/Pen/" + index + "/Comp2";
string paramBlendToCurrentDepth = "TPS_Internal/Pen/" + index + "/BlendToDepthVelocity";
string paramFloatBufferedDepth = "TPS_Pen_" + index + "_BufferedDepth";
string paramFloatBufferedStrength = "TPS_Pen_" + index + "_BufferedDepthStrength";
string paramIsPenetrating = "TPS_Pen_" + index + "_IsPenetrating";
//Add contacts
if (placeContacts)
{
pathSenderIsPenetrating = AnimationUtility.CalculateTransformPath(
PlaceVRCContactSenderOnChildObject(penetrator.Transform, Vector3.zero, length, CONTACT_PEN_PENETRATING), avatar);
pathSenderWidth = AnimationUtility.CalculateTransformPath(
PlaceVRCContactSenderOnChildObject(penetrator.Transform, Vector3.zero, Mathf.Max(0.01f, length - width), CONTACT_PEN_WIDTH), avatar);
// Orf -> Pen: Orientation
PlaceVRCContactReceiverProximityOnChildObject(animator, penetrator.Transform, Vector3.back * 0, paramFloatRootRoot, length, CONTACT_ORF_ROOT);
PlaceVRCContactReceiverProximityOnChildObject(animator, penetrator.Transform, Vector3.back * 0, paramFloatRootFrwd, length, CONTACT_ORF_NORM);
PlaceVRCContactReceiverProximityOnChildObject(animator, penetrator.Transform, Vector3.back * 0.01f, paramFloatBackRoot, length, CONTACT_ORF_ROOT);
// pen root pointed correctly && pen root in front of orf root && Collision
// TPS_Pen_RootRoot_ > TPS_Pen_RootForw_ && TPS_Pen_BackRoot_ > TPS_Pen_RootRoot_ && TPS_Pen_Collision_
}
CreateTwoParameterComparissionLayer(animator, "[TPS][Pen" + index + "] 1/3", paramFloatRootRoot, paramFloatRootFrwd, paramFloatComp1, "TPS/Pen/" + index + "/Comp1/", directory, "Pen_" + index + "_Comp1");
CreateTwoParameterComparissionLayer(animator, "[TPS][Pen" + index + "] 2/3", paramFloatBackRoot, paramFloatRootRoot, paramFloatComp2, "TPS/Pen/" + index + "/Comp2/", directory, "Pen_" + index + "_Comp2");
string rendererPath = AnimationUtility.CalculateTransformPath(r.transform, avatar);
AnimatorControllerLayer bufferLayer = SingletonLayer(animator, "[TPS][Pen" + index + "] 3/3", true);
AddParameter(animator, paramFloatBufferedDepth, AnimatorControllerParameterType.Float);
AddParameter(animator, paramFloatBufferedStrength, AnimatorControllerParameterType.Float);
AddParameter(animator, paramIsPenetrating, AnimatorControllerParameterType.Bool);
AddParameter(animator, paramBlendToCurrentDepth, AnimatorControllerParameterType.Float, 0.01f);
Func<string, bool, bool, bool, AnimationClip> makeBufferClip = (string name, bool penetrating, bool depth1, bool strength1) =>
{
return CreateClip("TPS/Pen/" + index + "/Buffer/" + name,
new CurveConfig(pathSenderIsPenetrating, "m_Enabled", typeof(VRCContactSender), penetrating ? OnCurve : OffCurve),
new CurveConfig(pathSenderWidth, "m_Enabled", typeof(VRCContactSender), penetrating ? OnCurve : OffCurve),
new CurveConfig("", paramFloatBufferedDepth, typeof(Animator), depth1 ? OnCurve : OffCurve),
new CurveConfig("", paramFloatBufferedStrength, typeof(Animator), strength1 ? OnCurve : OffCurve),
new CurveConfig(rendererPath, "material._TPS_BufferedDepth", typeof(Renderer), depth1 ? OnCurve : OffCurve),
new CurveConfig(rendererPath, "material._TPS_BufferedStrength", typeof(Renderer), strength1 ? OnCurve : OffCurve),
new CurveConfig(rendererPath, "m_UpdateWhenOffscreen", typeof(Renderer), OffCurve)); //Disable UpdateWhenOffscreen to keep custom boudning box for local player (vrc changes value on load. local -> on, remote ->off)
};
AnimationClip depth0Strength0True = makeBufferClip("True_00", true, false, false);
AnimationClip depth0Strength1True = makeBufferClip("True_01", true, false, true);
AnimationClip depth1Strength0True = makeBufferClip("True_10", true, true, false);
AnimationClip depth1Strength1True = makeBufferClip("True_11", true, true, true);
AnimationClip depth0Strength0False = makeBufferClip("False_00", false, false, false);
AnimationClip depth0Strength1False = makeBufferClip("False_01", false, false, true);
AnimationClip depth1Strength0False = makeBufferClip("False_10", false, true, false);
AnimationClip depth1Strength1False = makeBufferClip("False_11", false, true, true);
//when penetrating:
//Max Tree containing:
// currentDepthIncStrength => Depth : Depth , Strength : Blend Up
// bufferDepthIncStrength => Depth : Buffer, Strength : Blend Up
BlendTree currentDepthStrength0 = new BlendTree()
{
name = "Depth = Current, Strength = 0",
blendParameter = paramFloatRootRoot,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength0True, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength0True, threshold = 1, timeScale = 1 },
}
};
BlendTree currentDepthStrength1 = new BlendTree()
{
name = "Depth = Current, Strength = 1",
blendParameter = paramFloatRootRoot,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength1True, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength1True, threshold = 1, timeScale = 1 },
}
};
BlendTree currentDepthIncStrength = new BlendTree()
{
name = "Depth = Current, Strength = Strength + 1",
blendParameter = paramFloatBufferedStrength,
useAutomaticThresholds = false,
children = new ChildMotion[] {
new ChildMotion(){ motion = currentDepthStrength0, threshold = -0.01f, timeScale = 1 },
new ChildMotion(){ motion = currentDepthStrength1, threshold = 0.99f, timeScale = 1 },
}
};
BlendTree bufferDepthStrength0Penetrating = new BlendTree()
{
name = "Depth = Buffer, Strength = 0",
blendParameter = paramFloatBufferedDepth,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength0True, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength0True, threshold = 1, timeScale = 1 },
}
};
BlendTree bufferDepthStrength0Outside = new BlendTree()
{
name = "Depth = Buffer, Strength = 0",
blendParameter = paramFloatBufferedDepth,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength0False, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength0False, threshold = 1, timeScale = 1 },
}
};
BlendTree bufferDepthStrength1Penetrating = new BlendTree()
{
name = "Depth = Buffer, Strength = 1",
blendParameter = paramFloatBufferedDepth,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength1True, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength1True, threshold = 1, timeScale = 1 },
}
};
BlendTree bufferDepthStrength1Outside = new BlendTree()
{
name = "Depth = Buffer, Strength = 1",
blendParameter = paramFloatBufferedDepth,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = depth0Strength1False, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = depth1Strength1False, threshold = 1, timeScale = 1 },
}
};
BlendTree bufferDepthIncStrength = new BlendTree()
{
name = "Depth = Buffer, Strength = Strength + 1",
blendParameter = paramFloatBufferedStrength,
useAutomaticThresholds = false,
children = new ChildMotion[] {
new ChildMotion(){ motion = bufferDepthStrength0Penetrating, threshold = -0.01f, timeScale = 1 },
new ChildMotion(){ motion = bufferDepthStrength1Penetrating, threshold = 0.99f, timeScale = 1 },
}
};
BlendTree bufferDepthDecStrength = new BlendTree()
{
name = "Depth = Current, Strength = Strength + 1",
blendParameter = paramFloatBufferedStrength,
useAutomaticThresholds = false,
children = new ChildMotion[] {
new ChildMotion(){ motion = bufferDepthStrength0Outside, threshold = 0.001f, timeScale = 1 },
new ChildMotion(){ motion = bufferDepthStrength1Outside, threshold = 1.001f, timeScale = 1 },
}
};
BlendTree bufferSlowlyBlendToCurrentDepthIncStrength = new BlendTree()
{
name = "Depth = Blend to Current, Strength = Inc",
blendParameter = paramBlendToCurrentDepth,
useAutomaticThresholds = false,
children = new ChildMotion[] {
new ChildMotion(){ motion = bufferDepthIncStrength, threshold = 0, timeScale = 1 },
new ChildMotion(){ motion = currentDepthIncStrength, threshold = 1, timeScale = 1 },
}
};
//if current depth is bigger than buffer use current depth
//else use buffer, but slowly blend down to current depth
BlendTree maxDepthBuffer = new BlendTree()
{
name = "Max(Depth,Buffer)",
blendType = BlendTreeType.FreeformCartesian2D,
blendParameter = paramFloatRootRoot,
blendParameterY = paramFloatBufferedDepth,
useAutomaticThresholds = false,
children = new ChildMotion[] {
new ChildMotion(){ motion = currentDepthIncStrength, position = new Vector2(1.000f,0.000f), timeScale = 1 },
new ChildMotion(){ motion = currentDepthIncStrength, position = new Vector2(0.001f,0.000f), timeScale = 1 },
new ChildMotion(){ motion = currentDepthIncStrength, position = new Vector2(0.501f,0.499f), timeScale = 1 },
new ChildMotion(){ motion = currentDepthIncStrength, position = new Vector2(1.000f,0.999f), timeScale = 1 },
new ChildMotion(){ motion = bufferSlowlyBlendToCurrentDepthIncStrength , position = new Vector2(0.000f,1.000f), timeScale = 1 },
new ChildMotion(){ motion = bufferSlowlyBlendToCurrentDepthIncStrength , position = new Vector2(0.000f,0.001f), timeScale = 1 },
new ChildMotion(){ motion = bufferSlowlyBlendToCurrentDepthIncStrength , position = new Vector2(0.499f,0.501f), timeScale = 1 },
new ChildMotion(){ motion = bufferSlowlyBlendToCurrentDepthIncStrength , position = new Vector2(0.999f,1.000f), timeScale = 1 },
}
};
//AnimatorState stateNotPenetrating = CreateState("Not Penetrating", bufferLayer, depth0Strength0False);
AnimatorState statePenetration = CreateState("Penetration", bufferLayer, maxDepthBuffer);
AnimatorState stateNotPenetrating = CreateState("No Penetration", bufferLayer, bufferDepthDecStrength);
CreateTransition(stateNotPenetrating, statePenetration, new Condition(paramFloatComp1, CompareType.GREATER, 0), new Condition(paramFloatComp2, CompareType.GREATER, 0), new Condition(paramFloatRootRoot, CompareType.GREATER, 0));
//CreateTransition(decreaseOutside, stateNotPenetrating, new Condition(paramFloatComp1, CompareType.GREATER, 0), new Condition(paramFloatComp2, CompareType.GREATER, 0), new Condition(paramFloatRootRoot, CompareType.GREATER, 0));
CreateTransition(statePenetration, stateNotPenetrating, new Condition(paramFloatComp1, CompareType.LESS, 0.001f));
CreateTransition(statePenetration, stateNotPenetrating, new Condition(paramFloatComp2, CompareType.LESS, 0.001f));
CreateTransition(statePenetration, stateNotPenetrating, new Condition(paramFloatRootRoot, CompareType.LESS, 0.001f));
bufferLayer.stateMachine.defaultState = stateNotPenetrating;
AddParameterDriver(stateNotPenetrating, (paramIsPenetrating, ChangeType.Set, 0));
//AddParameterDriver(decreaseOutside, (paramIsPenetrating, ChangeType.Set, 0));
AddParameterDriver(statePenetration, (paramIsPenetrating, ChangeType.Set, 1));
AssetDatabase.CreateAsset(bufferDepthDecStrength, directory + "/Pen_" + index + "_DepthBlendTree.asset");
AssetDatabase.AddObjectToAsset(depth0Strength0False, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth0Strength1False, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth1Strength0False, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth1Strength1False, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth0Strength0True, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth0Strength1True, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth1Strength0True, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(depth1Strength1True, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(currentDepthStrength0, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(currentDepthStrength1, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferDepthStrength0Penetrating, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferDepthStrength0Outside, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferDepthStrength1Penetrating, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferDepthStrength1Outside, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferDepthIncStrength, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(currentDepthIncStrength, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(bufferSlowlyBlendToCurrentDepthIncStrength, bufferDepthDecStrength);
AssetDatabase.AddObjectToAsset(maxDepthBuffer, bufferDepthDecStrength);
AssetDatabase.ImportAsset(directory + "/Pen_" + index + "_DepthBlendTree.asset");
#endif
}
#endregion
#region Orifice
public static void SetupOrifice(Transform avatar, AnimatorController animator, Transform orifice, Renderer renderer, OrificeType type, OrificeConfig config, int index, string directory, bool placeContacts = true, bool instanciateMaterials = true)
{
#if VRC_SDK_VRCSDK3 && !UDON
if (placeContacts)
{
// Remove senders, recievers
RemoveVRCSendersAndRecievers(orifice);
// Orf -> Pen: Position, Normal for shader
// Orf -> Pen: Penetrating
PlaceVRCContactSenderOnChildObject(orifice, Vector3.forward * 0.00f, 0.01f, CONTACT_ORF_ROOT);
PlaceVRCContactSenderOnChildObject(orifice, Vector3.forward * 0.01f, 0.01f, CONTACT_ORF_NORM);
}
if (renderer == null || config.DoAnimatorSetup == false) return;
//Get renderer & mesh
string rendererPath = AnimationUtility.CalculateTransformPath(renderer.transform, avatar);
//Instanciate material
if (instanciateMaterials) InstanciateMaterials(avatar, renderer, "Orf" + index, directory);
//Calc depth
//Parameter names
string paramDepthIn = "TPS_Internal/Orf/" + index + "/Depth_In";
string paramWidth1In = "TPS_Internal/Orf/" + index + "/Width1_In";
string paramWidth2In = "TPS_Internal/Orf/" + index + "/Width2_In";
string paramDepth = "TPS_Orf_" + index + "_Depth";
string paramWidth = "TPS_Orf_" + index + "_Width";
string paramIsPenetrating = "TPS_Orf_" + index + "_IsPenetrated";
// Pen -> Orf: Penetrating
PlaceVRCContactReceiverProximityOnChildObject(animator, orifice, Vector3.back * config.MaxDepth, paramDepthIn, config.MaxDepth, CONTACT_PEN_PENETRATING);
// Pen -> Orf: Width
float widthRecieverRadius = config.MaxOpeningWidth + 0.2f;
PlaceVRCContactReceiverProximityOnChildObject(animator, orifice, Vector3.back * widthRecieverRadius * 0.5f, paramWidth1In, widthRecieverRadius, CONTACT_PEN_PENETRATING);
PlaceVRCContactReceiverProximityOnChildObject(animator, orifice, Vector3.back * widthRecieverRadius * 0.5f, paramWidth2In, widthRecieverRadius, CONTACT_PEN_WIDTH);
//Layer width calculation
AnimatorControllerLayer widthLayer = SingletonLayer(animator, "[TPS][Orf" + index + "] 1/2", true);
AddParameter(animator, paramWidth, AnimatorControllerParameterType.Float);
AnimationClip widthZero = CreateClip("TPS/Orf/" + index + "/Width/Zero", new CurveConfig("", paramWidth, typeof(Animator), OffCurve));
AnimationClip widthPos = CreateClip("TPS/Orf/" + index + "/Width/Pos", new CurveConfig("", paramWidth, typeof(Animator), OnCurve));
AnimationClip widthNeg = CreateClip("TPS/Orf/" + index + "/Width/Neg", new CurveConfig("", paramWidth, typeof(Animator), FloatCurve(-1,1)));
AnimationClip widthZeroToOne = CreateClip("TPS/Orf/" + index + "/Width/ZeroToOne", new CurveConfig("", paramWidth, typeof(Animator), AnimationCurve.Linear(0,0,1,1)));
BlendTree subtracvtioTree = new BlendTree()
{
blendParameter = paramWidth1In,
blendParameterY = paramWidth2In,
useAutomaticThresholds = false,
blendType = BlendTreeType.SimpleDirectional2D,
children = new ChildMotion[]{
new ChildMotion(){ motion = widthZero, timeScale = 1, position = new Vector2(0 , 0) },
new ChildMotion(){ motion = widthZero, timeScale = 1, position = new Vector2(1 , 1) },
new ChildMotion(){ motion = widthPos, timeScale = 1, position = new Vector2(1 , 0) },
new ChildMotion(){ motion = widthNeg, timeScale = 1, position = new Vector2(0 , 1) },
}
};
SaveBlendTree(subtracvtioTree, directory, "Orf_" + index + "_width", false, widthZeroToOne);
AnimatorState hasntBeenColliding = CreateState("No Pen", widthLayer, widthZero, true);
AnimatorState waitForNoCollisions = CreateState("Buffer", widthLayer, widthZeroToOne);
waitForNoCollisions.timeParameterActive = true;
waitForNoCollisions.timeParameter = paramWidth;
AnimatorState calcWidth = CreateState("Calc", widthLayer, subtracvtioTree);
CreateTransition(hasntBeenColliding, calcWidth, new Condition(paramDepthIn, CompareType.GREATER, 0));
CreateTransition(waitForNoCollisions, hasntBeenColliding, new Condition(paramDepthIn, CompareType.LESS, 0.01f));
CreateTransition(calcWidth, hasntBeenColliding, new Condition(paramDepthIn, CompareType.LESS, 0.01f));
CreateTransition(calcWidth, waitForNoCollisions, new Condition(paramWidth, CompareType.GREATER, 0), new Condition(paramWidth1In, CompareType.GREATER, 0), new Condition(paramWidth2In, CompareType.GREATER, 0));
AddParameter(animator, paramIsPenetrating, AnimatorControllerParameterType.Bool);
AddParameterDriver(hasntBeenColliding, (paramIsPenetrating, ChangeType.Set, 0));
AddParameterDriver(calcWidth, (paramIsPenetrating, ChangeType.Set, 1));
//DebugLayer(paramWidth1In, renderer, "_TPSWidth1", directory, "DebugWidth1" + index);
//DebugLayer(paramWidth2In, renderer, "_TPSWidth2", directory, "DebugWidth2" + index);
//DebugLayer(paramWidth, renderer, "_TPSWidthDebug", directory, "DebugWidth" + index);
//Orifice Depth layer. Length TPS_Orf_RootTip_, condition check
AnimatorControllerLayer depthLayer = SingletonLayer(animator, "[TPS][Orf" + index + "] 2/2", true);
AddParameter(animator, paramDepth, AnimatorControllerParameterType.Float);
Func<string, float, float, float, AnimationClip> PenAnim = (string name,float depth, float blend1, float blend2) =>
{
return CreateClip(name, new CurveConfig("", paramDepth, typeof(Animator), CustomCurve((1, depth))),
new CurveConfig(rendererPath, "blendShape." + config.GetBlendshapeNameEnter(), typeof(SkinnedMeshRenderer), CustomCurve((1, blend1))),
new CurveConfig(rendererPath, "blendShape." + config.GetBlendshapeNameIn(), typeof(SkinnedMeshRenderer), CustomCurve((1, blend2))));
};
AnimationClip startNoWidth = PenAnim("TPS/Orf/" + index + "/Blend/00", 0, 0, 0);
AnimationClip startFullWidth = PenAnim("TPS/Orf/" + index + "/Blend/01", 0, 100, 0);
AnimationClip endNoWidth = PenAnim("TPS/Orf/" + index + "/Blend/10", 1, 0, 0);
AnimationClip endFullWidth = PenAnim("TPS/Orf/" + index + "/Blend/11", 1, 0, 100);
float maxWidthThreshold = 1 - 0.2f / (0.2f + config.MaxOpeningWidth);
BlendTree in000 = new BlendTree()
{
name = "No Depth",
blendParameter = paramWidth,
useAutomaticThresholds = false,
children = new ChildMotion[]{
new ChildMotion() { motion = startNoWidth, timeScale = 1, threshold = 0 },
new ChildMotion() { motion = startNoWidth, timeScale = 1, threshold = maxWidthThreshold }
}
};
BlendTree in005 = new BlendTree()
{
name = "Sligh Depth",
blendParameter = paramWidth,
useAutomaticThresholds = false,
children = new ChildMotion[]{
new ChildMotion() { motion = config.ScaleBlendshapesByWidth ? startNoWidth : startFullWidth, timeScale = 1, threshold = 0 },
new ChildMotion() { motion = startFullWidth, timeScale = 1, threshold = maxWidthThreshold }
}
};
BlendTree in100 = new BlendTree()
{
name = "Full Depth",
blendParameter = paramWidth,
useAutomaticThresholds = false,
children = new ChildMotion[]{
new ChildMotion() { motion = config.ScaleBlendshapesByWidth ? endNoWidth : endFullWidth, timeScale = 1, threshold = 0 },
new ChildMotion() { motion = endFullWidth, timeScale = 1, threshold = maxWidthThreshold }
}
};
BlendTree penetrationTree = new BlendTree()
{
blendParameter = paramDepthIn,
useAutomaticThresholds = false,
children = new ChildMotion[]{
new ChildMotion() { motion = in000, timeScale = 1, threshold = 0 },
new ChildMotion() { motion = in005, timeScale = 1, threshold = 0.05f },
new ChildMotion() { motion = in100, timeScale = 1, threshold = 1 }
}
};
SaveBlendTree(penetrationTree, directory, "Orf_" + index + "_0", true);
AnimatorState penetration = CreateState("Penetrated", depthLayer, penetrationTree);
AnimatorState noPenetration = CreateState("No Penetration", depthLayer, startNoWidth);
depthLayer.stateMachine.defaultState = noPenetration;
CreateTransition(noPenetration, penetration, new Condition(paramIsPenetrating, CompareType.EQUAL, true));
CreateTransition(penetration, noPenetration, new Condition(paramIsPenetrating, CompareType.EQUAL, false));
//DebugLayer(paramDepthIn, renderer, "_TPSWidthDebugDepth1", directory, "DebugDpeth1" +index);
//DebugLayer(paramDepth, renderer, "_TPSWidthDebugDepth2", directory, "DebugDepth2" +index);
#endif
}
#endregion
#region Animator Functions
static void CreateTwoParameterComparissionLayer(AnimatorController animator, string layername, string paramName1, string paramName2, string output, string clipNamePrefix, string directory, string fileName)
{
AnimatorControllerLayer layer = SingletonLayer(animator, layername, true);
AddParameter(animator, output, AnimatorControllerParameterType.Float);
string at = output;
Type t = typeof(Animator);
BlendTree tree = new BlendTree();
tree.blendType = BlendTreeType.FreeformCartesian2D;
tree.useAutomaticThresholds = false;
AnimationClip zero = CreateClip(clipNamePrefix + "Zero", new CurveConfig("", at, t, OffCurve));
tree.AddChild(zero, new Vector2(0, 0));
tree.AddChild(CreateClip(clipNamePrefix + "Neg", new CurveConfig("", at, t, FloatCurve(-1, 1))), new Vector2(1, 0));
tree.AddChild(CreateClip(clipNamePrefix + "Pos", new CurveConfig("", at, t, FloatCurve(1, 1))), new Vector2(0, 1));
tree.AddChild(zero, new Vector2(1, 1));
tree.blendParameter = paramName1;
tree.blendParameterY = paramName2;
SaveBlendTree(tree, directory, fileName);
CreateState("BlendTree", layer, tree, true);
}
AnimatorState CreateTwoParameterAbsoluteDifferenceLayer(AnimatorControllerLayer layer, string paramName1, string paramName2, string output, string directory, string fileName, params CurveConfig[] additionalTargets)
{
AddParameter(_animator, output, AnimatorControllerParameterType.Float);
string at = output;
Type t = typeof(Animator);
BlendTree tree = new BlendTree();
tree.blendType = BlendTreeType.FreeformCartesian2D;
tree.useAutomaticThresholds = false;
AnimationClip clip0 = CreateClip("zero", additionalTargets.Select(c => new CurveConfig(c.Path, c.Attribute, c.Type, OffCurve)).Append(new CurveConfig("", at, t, OffCurve)).ToArray());
AnimationClip clip1 = CreateClip("one", additionalTargets.Select(c => new CurveConfig(c.Path, c.Attribute, c.Type, c.Curve)).Append(new CurveConfig("", at, t, FloatCurve(1, 1))).ToArray());
tree.AddChild(clip0, new Vector2(0, 0));
tree.AddChild(clip1, new Vector2(1, 0));
tree.AddChild(clip1, new Vector2(0, 1));
tree.AddChild(clip0, new Vector2(1, 1));
tree.blendParameter = paramName1;
tree.blendParameterY = paramName2;
SaveBlendTree(tree, directory, fileName);
return CreateState("BlendTree", layer, tree, true);
}
void DebugLayer(string param, Renderer renderer, string materialProp, string directory, string name)
{
AnimatorControllerLayer layer = new AnimatorControllerLayer()
{
name = "[TPS] Debug-" + name,
stateMachine = new AnimatorStateMachine() { name = "[TPS] Debug" },
defaultWeight = 1
};
_animator.AddLayer(layer);
AssetDatabase.AddObjectToAsset(layer.stateMachine,
AssetDatabase.GetAssetPath(_animator));
string path = AnimationUtility.CalculateTransformPath(renderer.transform, _avatar);
BlendTree tree = new BlendTree()
{
blendParameter = param,
useAutomaticThresholds = false,
children = new ChildMotion[]
{
new ChildMotion(){ motion = CreateClip("DebugNeg", new CurveConfig(path, "material."+materialProp, typeof(Renderer), FloatCurve(-1,1))), threshold = -1, timeScale = 1 },
new ChildMotion(){ motion = CreateClip("DebugPos" , new CurveConfig(path, "material."+materialProp, typeof(Renderer), OnCurve )), threshold = 1, timeScale = 1 },
}
};
AnimatorState s = new AnimatorState() { name = "Debug", motion = tree, writeDefaultValues = false };
layer.stateMachine.AddState(s, new Vector3(300, 100));
layer.stateMachine.defaultState = s;
SaveBlendTree(tree, directory, "Debug" + (s_debugIndex++));
}
public static void SaveBlendTree(BlendTree tree, string directory, string name, bool deepTrees = false, params AnimationClip[] clips)
{
string path = directory + "/" + name + ".asset";
AssetDatabase.CreateAsset(tree, path);
List<AnimationClip> allClips = new List<AnimationClip>();
if (clips != null && clips.Length > 0)
{
allClips.AddRange(clips);
}
allClips.AddRange(tree.children.Select(c => c.motion).Where(m => m is AnimationClip && m != null).Select(m => m as AnimationClip));
if (deepTrees)
{
List<BlendTree> allTrees = new List<BlendTree>();
GatherAllSubtrees(tree, allTrees);
foreach (BlendTree t in allTrees.Distinct())
{
AssetDatabase.AddObjectToAsset(t, path);
allClips.AddRange(t.children.Select(c => c.motion).Where(m => m is AnimationClip && m != null).Select(m => m as AnimationClip));
}
}
foreach (AnimationClip c in allClips.Distinct())
AssetDatabase.AddObjectToAsset(c, path);
AssetDatabase.ImportAsset(path);
}
static void GatherAllSubtrees(BlendTree tree, List<BlendTree> trees)
{
foreach (BlendTree t in tree.children.Select(c => c.motion).Where(c => c is BlendTree && c != null))
{
trees.Add(t);
GatherAllSubtrees(t, trees);
}
}
//https://github.com/akshayb6/trilateration-in-3d/blob/master/trilateration.py
Vector3 Trilaterate3D(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4, float r1, float r2, float r3, float r4)
{
Vector3 e_x = (p2 - p1).normalized;
float i = Vector3.Dot(e_x, p3 - p1);
Vector3 e_y = (p3 - p1 - (i * e_x)).normalized;
Vector3 e_z = Vector3.Cross(e_x, e_y);
float d = (p2 - p1).magnitude;
float j = Vector3.Dot(e_y, (p3 - p1));
float x = (Mathf.Pow(r1, 2) - Mathf.Pow(r2, 2) + Mathf.Pow(d, 2)) / (2 * d);
float y = ((Mathf.Pow(r1, 2) - Mathf.Pow(r3, 2) + Mathf.Pow(i, 2) + Mathf.Pow(j, 2)) / (2 * j)) - ((i / j) * (x));
float z1 = Mathf.Sqrt(Mathf.Pow(r1, 2) - Mathf.Pow(x, 2) - Mathf.Pow(y, 2));
float z2 = Mathf.Sqrt(Mathf.Pow(r1, 2) - Mathf.Pow(x, 2) - Mathf.Pow(y, 2)) * (-1);
Vector3 ans1 = p1 + (x * e_x) + (y * e_y) + (z1 * e_z);
Vector3 ans2 = p1 + (x * e_x) + (y * e_y) + (z2 * e_z);
float dist1 = (p4 - ans1).magnitude;
float dist2 = (p4 - ans2).magnitude;
if (Mathf.Abs(r4 - dist1) < Mathf.Abs(r4 - dist2))
return ans1;
else
return ans2;
}
#if VRC_SDK_VRCSDK3 && !UDON
static void RemoveVRCSendersAndRecievers(Transform transform)
{
IEnumerable<VRCContactReceiver> reciever = transform.GetComponentsInChildren<VRCContactReceiver>(true).Where(r => r.collisionTags.Any(t => t.StartsWith("TPS")) || r.parameter.StartsWith("TPS"));
IEnumerable<VRCContactSender> sender = transform.GetComponentsInChildren<VRCContactSender>(true).Where(r => r.collisionTags.Any(t => t.StartsWith("TPS")));
foreach (VRCContactReceiver r in reciever)
{
if (r.gameObject.GetComponents<Component>().Length == 2 && !PrefabUtility.IsPartOfAnyPrefab(r.gameObject)) DestroyImmediate(r.gameObject);
else DestroyImmediate(r);
}
foreach (VRCContactSender s in sender)
{
if (s.gameObject.GetComponents<Component>().Length == 2 && !PrefabUtility.IsPartOfAnyPrefab(s.gameObject)) DestroyImmediate(s.gameObject);
else DestroyImmediate(s);
}
}
static void PlaceVRCContactReceiverProximity(AnimatorController animator, Transform transform, Vector3 position, string paramName, float radius, params string[] tags)
{
VRCContactReceiver reciever = transform.gameObject.AddComponent<VRCContactReceiver>();
reciever.position = new Vector3(position.x / transform.lossyScale.x, position.y / transform.lossyScale.y, position.z / transform.lossyScale.z);
reciever.parameter = paramName;
reciever.radius = radius / transform.lossyScale.x;
reciever.receiverType = VRC.Dynamics.ContactReceiver.ReceiverType.Proximity;
reciever.collisionTags = new List<string>(tags);
AddParameter(animator, paramName, AnimatorControllerParameterType.Float);
}
static Transform PlaceVRCContactReceiverProximityOnChildObject(AnimatorController animator, Transform parent, Vector3 localPosition, string paramName, float radius, params string[] tags)
{
string path = "R_" + tags[0] + "=>" + paramName;
Transform transform = SingletonChild(parent, path);
transform.position = parent.position + transform.rotation * localPosition;
PlaceVRCContactReceiverProximity(animator, transform, Vector3.zero, paramName, radius, tags);
return transform;
}
static VRCContactReceiver PlaceVRCContactReceiverBool(AnimatorController animator, Transform transform, Vector3 position, string paramName, float radius, params string[] tags)
{
VRCContactReceiver reciever = transform.gameObject.AddComponent<VRCContactReceiver>();
reciever.position = new Vector3(position.x / transform.lossyScale.x, position.y / transform.lossyScale.y, position.z / transform.lossyScale.z);
reciever.parameter = paramName;
reciever.radius = radius / transform.lossyScale.x;
reciever.receiverType = VRC.Dynamics.ContactReceiver.ReceiverType.Constant;
reciever.collisionTags = new List<string>(tags);
AddParameter(animator, paramName, AnimatorControllerParameterType.Float);
return reciever;
}
static VRCContactSender PlaceVRCContactSender(Transform transform, Vector3 position, float radius, params string[] tags)
{
VRCContactSender sender = transform.gameObject.AddComponent<VRCContactSender>();
sender.position = new Vector3(position.x / transform.lossyScale.x, position.y / transform.lossyScale.y, position.z / transform.lossyScale.z);
sender.radius = radius / transform.lossyScale.x;
sender.collisionTags = new List<string>(tags);
return sender;
}
static Transform PlaceVRCContactSenderOnChildObject(Transform parent, Vector3 localPosition, float radius, params string[] tags)
{
string path = "S_" + tags[0];
Transform transform = SingletonChild(parent, path);
transform.position = parent.position + transform.rotation * localPosition;
PlaceVRCContactSender(transform, Vector3.zero, radius, tags);
return transform;
}
#endif
}
public static class ExtensionMethods
{
public static IList<T> Swap<T>(this IList<T> list, int indexA, int indexB)
{
T tmp = list[indexA];
list[indexA] = list[indexB];
list[indexB] = tmp;
return list;
}
}
public enum CompareType { EQUAL, NOT_EQUAL, LESS, GREATER }
public class ThryAnimatorFunctions
{
public static AnimatorControllerLayer SingletonLayer(AnimatorController animator, string layerName, bool clearLayer = true, AnimatorControllerLayer needsToBeAboveLayer = null)
{
AnimatorControllerLayer layer = null;
int layerIndex = 0;
for (int i = animator.layers.Length - 1; i >= 0; i--)
{
if (animator.layers[i].name == layerName)
{
if (clearLayer)
{
ClearLayer(animator.layers[i]);
}
layer = animator.layers[i];
layerIndex = i;
break;
}
}
if (layer == null)
{
layer = new AnimatorControllerLayer();
layer.name = layerName;
layer.defaultWeight = 1;
layer.stateMachine = new AnimatorStateMachine();
layer.stateMachine.name = layer.name;
layer.stateMachine.hideFlags = HideFlags.HideInHierarchy;
if (AssetDatabase.GetAssetPath(animator) != "")
{
AssetDatabase.AddObjectToAsset(layer.stateMachine,
AssetDatabase.GetAssetPath(animator));
}
layerIndex = animator.layers.Length;
animator.AddLayer(layer);
}
if (needsToBeAboveLayer != null)
{
AnimatorControllerLayer[] layers = animator.layers;
for (int i = 0; i < layers.Length; i++)
{
if (layers[i].name == layer.name)
{
break;
}
if (layers[i].name == needsToBeAboveLayer.name)
{
needsToBeAboveLayer = layers[i];
layers[i] = layer;
for (int j = layerIndex; j > i; j--)
layers[j] = layers[j - 1];
layers[i + 1] = needsToBeAboveLayer;
break;
}
}
animator.layers = layers;
}
return layer;
}
public static void ClearLayer(AnimatorControllerLayer layer)
{
layer.stateMachine.states = new ChildAnimatorState[0];
layer.stateMachine.stateMachines = new ChildAnimatorStateMachine[0];
layer.stateMachine.anyStateTransitions = new AnimatorStateTransition[0];
layer.stateMachine.entryTransitions = new AnimatorTransition[0];
layer.stateMachine.behaviours = new StateMachineBehaviour[0];
}
private static AnimationClip LoadEmptyClipOrCreateClip(string name, string directory, float length)
{
string[] guids = AssetDatabase.FindAssets(name + " t:animationclip");
if (guids.Length > 0) return AssetDatabase.LoadAssetAtPath<AnimationClip>(AssetDatabase.GUIDToAssetPath(guids[0]));
return CreateClip("Assets", "Empty_Clip", new CurveConfig("NanObject", "m_IsActive", typeof(GameObject), FloatCurve(1, length)));
}
private static AnimationClip _empty;
public static AnimationClip EmptyClip
{
get
{
if (_empty == null) _empty = LoadEmptyClipOrCreateClip("Empty_Clip", "Assets", 1f / 60);
return _empty;
}
}
private static AnimationClip _empty1Sec;
public static AnimationClip EmptyClip1Sec
{
get
{
if (_empty1Sec == null) _empty1Sec = LoadEmptyClipOrCreateClip("Empty_1_sec", "Assets", 1f / 60);
return _empty1Sec;
}
}
private static AnimationClip _empty2Sec;
public static AnimationClip EmptyClip2Sec
{
get
{
if (_empty2Sec == null) _empty2Sec = LoadEmptyClipOrCreateClip("Empty_2_sec", "Assets", 1f / 60);
return _empty2Sec;
}
}
private static AnimationClip _empty5Sec;
public static AnimationClip EmptyClip5Sec
{
get
{
if (_empty5Sec == null) _empty5Sec = LoadEmptyClipOrCreateClip("Empty_5_sec", "Assets", 1f / 60);
return _empty5Sec;
}
}
#if VRC_SDK_VRCSDK3 && !UDON
public static VRCAvatarParameterDriver AddParameterDriver(AnimatorState state, params (string, VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType, float)[] drive)
{
if (drive.Count() == 0) return null;
VRCAvatarParameterDriver driver = state.behaviours.FirstOrDefault(b => b.GetType() == typeof(VRCAvatarParameterDriver)) as VRCAvatarParameterDriver;
if (driver == null)
driver = state.AddStateMachineBehaviour(typeof(VRCAvatarParameterDriver)) as VRCAvatarParameterDriver;
foreach ((string, VRC.SDKBase.VRC_AvatarParameterDriver.ChangeType, float) d in drive)
{
VRC.SDKBase.VRC_AvatarParameterDriver.Parameter driverParameter = new VRC.SDKBase.VRC_AvatarParameterDriver.Parameter();
driverParameter.name = d.Item1;
driverParameter.value = d.Item3;
driverParameter.type = d.Item2;
driver.parameters.Add(driverParameter);
}
return driver;
}
#endif
public static AnimatorState CreateState(string name, AnimatorControllerLayer layer, Motion motion, bool setAsDefaultState = false)
{
AnimatorState state = layer.stateMachine.AddState(name);
state.motion = motion;
state.writeDefaultValues = false;
if (setAsDefaultState) layer.stateMachine.defaultState = state;
return state;
}
public static AnimatorState CreateState(string name, AnimatorStateMachine stateMachine, Motion motion, bool setAsDefaultState = false)
{
AnimatorState state = stateMachine.AddState(name);
state.motion = motion;
state.writeDefaultValues = false;
if (setAsDefaultState) stateMachine.defaultState = state;
return state;
}
public static AnimatorStateTransition CreateTransition(AnimatorState from, AnimatorState to, float duration = 0.01f, bool hasExitTime = true, float exitTime = 0.1f)
{
AnimatorStateTransition transition = from.AddTransition(to);
transition.hasExitTime = hasExitTime;
transition.duration = duration;
transition.exitTime = exitTime;
return transition;
}
public static AnimatorStateTransition CreateTransition(AnimatorState from, AnimatorStateMachine to, float duration = 0.01f, bool hasExitTime = true, float exitTime = 0.1f)
{
AnimatorStateTransition transition = from.AddTransition(to);
transition.hasExitTime = hasExitTime;
transition.duration = duration;
transition.exitTime = exitTime;
return transition;
}
public static AnimatorStateTransition CreateAnyStateTransition(AnimatorControllerLayer l, AnimatorState to, float duration = 0.01f, bool hasExitTime = false, float exitTime = 0.1f)
{
AnimatorStateTransition transition = l.stateMachine.AddAnyStateTransition(to);
transition.hasExitTime = hasExitTime;
transition.duration = duration;
transition.exitTime = exitTime;
transition.canTransitionToSelf = false;
return transition;
}
public class Condition
{
public string ParamName;
public CompareType CompareType;
public object Value;
public Condition(string n, CompareType t, object v)
{
this.ParamName = n;
this.CompareType = t;
this.Value = v;
}
}
public static AnimatorStateTransition CreateTransition(AnimatorState from, AnimatorState to, params Condition[] conditions)
{
AnimatorStateTransition transition = CreateTransition(from, to, 0.00f, false, 0.0f);
AddTransitionConditions(transition, conditions);
return transition;
}
public static AnimatorStateTransition CreateTransition(AnimatorState from, AnimatorStateMachine to, params Condition[] conditions)
{
AnimatorStateTransition transition = CreateTransition(from, to, 0.01f, false, 0.0f);
AddTransitionConditions(transition, conditions);
return transition;
}
public static AnimatorTransition CreateTransition(AnimatorStateMachine from, AnimatorState to, AnimatorStateMachine parent, params Condition[] conditions)
{
AnimatorTransition newT = new AnimatorTransition();
newT.destinationState = to;
AddTransitionConditions(newT, conditions);
AnimatorTransition[] transitions = parent.GetStateMachineTransitions(from);
transitions = transitions.Append(newT).ToArray();
parent.SetStateMachineTransitions(from, transitions);
return newT;
}
public static AnimatorTransition CreateTransition(AnimatorStateMachine from, AnimatorStateMachine to, AnimatorStateMachine parent, params Condition[] conditions)
{
AnimatorTransition newT = new AnimatorTransition();
newT.destinationStateMachine = to;
AddTransitionConditions(newT, conditions);
AnimatorTransition[] transitions = parent.GetStateMachineTransitions(from);
transitions = transitions.Append(newT).ToArray();
parent.SetStateMachineTransitions(from, transitions);
return newT;
}
public static AnimatorStateTransition CreateTransition(AnimatorState from, AnimatorState to, float duration = 0.01f, bool hasExitTime = false, float exitTime = 0.1f, params Condition[] conditions)
{
AnimatorStateTransition transition = CreateTransition(from, to, duration, hasExitTime, exitTime);
AddTransitionConditions(transition, conditions);
return transition;
}
public static void AddTransitionConditions(AnimatorStateTransition transition, params Condition[] conditions)
{
foreach (Condition c in conditions)
{
if (c.Value.GetType() == typeof(float))
{
if (c.CompareType == CompareType.LESS) transition.AddCondition(AnimatorConditionMode.Less, (float)c.Value, c.ParamName);
if (c.CompareType == CompareType.GREATER) transition.AddCondition(AnimatorConditionMode.Greater, (float)c.Value, c.ParamName);
}
else if (c.Value.GetType() == typeof(int))
{
if (c.CompareType == CompareType.LESS) transition.AddCondition(AnimatorConditionMode.Less, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.GREATER) transition.AddCondition(AnimatorConditionMode.Greater, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.EQUAL) transition.AddCondition(AnimatorConditionMode.Equals, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.NOT_EQUAL) transition.AddCondition(AnimatorConditionMode.NotEqual, (int)c.Value, c.ParamName);
}
else if (c.Value.GetType() == typeof(bool))
{
if ((bool)c.Value) transition.AddCondition(AnimatorConditionMode.If, 0, c.ParamName);
else transition.AddCondition(AnimatorConditionMode.IfNot, 0, c.ParamName);
}
}
}
public static void AddTransitionConditions(AnimatorTransition transition, params Condition[] conditions)
{
foreach (Condition c in conditions)
{
if (c.Value.GetType() == typeof(float))
{
if (c.CompareType == CompareType.LESS) transition.AddCondition(AnimatorConditionMode.Less, (float)c.Value, c.ParamName);
if (c.CompareType == CompareType.GREATER) transition.AddCondition(AnimatorConditionMode.Greater, (float)c.Value, c.ParamName);
}
else if (c.Value.GetType() == typeof(int))
{
if (c.CompareType == CompareType.LESS) transition.AddCondition(AnimatorConditionMode.Less, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.GREATER) transition.AddCondition(AnimatorConditionMode.Greater, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.EQUAL) transition.AddCondition(AnimatorConditionMode.Equals, (int)c.Value, c.ParamName);
if (c.CompareType == CompareType.NOT_EQUAL) transition.AddCondition(AnimatorConditionMode.NotEqual, (int)c.Value, c.ParamName);
}
else if (c.Value.GetType() == typeof(bool))
{
if ((bool)c.Value) transition.AddCondition(AnimatorConditionMode.If, 0, c.ParamName);
else transition.AddCondition(AnimatorConditionMode.IfNot, 0, c.ParamName);
}
}
}
public static void AddParameter(AnimatorController animator, string param, AnimatorControllerParameterType type, object defaultValue = null)
{
if (animator.parameters.Where(p => p.name == param).Count() == 0)
{
animator.AddParameter(param, type);
}
if (defaultValue != null)
{
AnimatorControllerParameter[] parameters = animator.parameters;
if (defaultValue.GetType() == typeof(bool)) parameters.First(p => p.name == param).defaultBool = (bool)defaultValue;
if (defaultValue.GetType() == typeof(int)) parameters.First(p => p.name == param).defaultInt = (int)defaultValue;
if (defaultValue.GetType() == typeof(float)) parameters.First(p => p.name == param).defaultFloat = (float)defaultValue;
animator.parameters = parameters;
}
}
public static BlendTree CreateFloatCopyBlendTree(string directory, string name, string fromParamter, string toParameter)
{
AnimationClip neg = CreateClip(name + "_neg", ("", typeof(Animator), toParameter, NegativeOneCurveOneFrame));
AnimationClip pos = CreateClip(name + "_pos", ("", typeof(Animator), toParameter, PositiveOneCurveOneFrame));
BlendTree tree = new BlendTree();
tree.useAutomaticThresholds = false;
tree.AddChild(neg, -1);
tree.AddChild(pos, 1);
tree.blendParameter = fromParamter;
string path = directory + "/" + name + ".asset";
AssetDatabase.CreateAsset(tree, path);
AssetDatabase.AddObjectToAsset(neg, path);
AssetDatabase.AddObjectToAsset(pos, path);
return tree;
}
public static Dictionary<(GameObject, GameObject), string> savedPaths = new Dictionary<(GameObject, GameObject), string>();
public static string GetPath(GameObject sensor, GameObject avatar)
{
if (savedPaths.ContainsKey((sensor, avatar))) return savedPaths[(sensor, avatar)];
Transform o = sensor.transform.parent;
List<Transform> path = new List<Transform>();
while (o != avatar.transform && o != null)
{
path.Add(o);
o = o.parent;
}
path.Reverse();
System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (Transform t in path)
{
sb.Append(t.name + "/");
}
sb.Append(sensor.name);
string finalpath = sb.ToString();
savedPaths.Add((sensor, avatar), finalpath);
return finalpath;
}
public static AnimationClip CreateAnimation()
{
AnimationClip clip = new AnimationClip();
return clip;
}
public static AnimationCurve OnCurve => new AnimationCurve(new Keyframe[] { new Keyframe(0, 1), new Keyframe(1, 1) });
public static AnimationCurve OnCurveOneFrame => new AnimationCurve(new Keyframe[] { new Keyframe(0, 1), new Keyframe(1f / 60, 1) });
public static AnimationCurve OffCurve => new AnimationCurve(new Keyframe[] { new Keyframe(0, 0), new Keyframe(1, 0) });
public static AnimationCurve OffCurveOneFrame => new AnimationCurve(new Keyframe[] { new Keyframe(0, 0), new Keyframe(1f / 60, 0) });
public static AnimationCurve PositiveOneCurveOneFrame => new AnimationCurve(new Keyframe[] { new Keyframe(0, 1), new Keyframe(1f / 60, 1) });
public static AnimationCurve NegativeOneCurveOneFrame => new AnimationCurve(new Keyframe[] { new Keyframe(0, -1), new Keyframe(1f / 60, -1) });
public static AnimationCurve IntCurve(int value, float time) { return new AnimationCurve(new Keyframe[] { new Keyframe(0, value), new Keyframe(time, value) }); }
public static AnimationCurve FloatCurve(float value, float time) { return new AnimationCurve(new Keyframe[] { new Keyframe(0, value), new Keyframe(time, value) }); }
public static AnimationCurve CustomCurve(params (float, float)[] keys) { return new AnimationCurve(keys.Select(tv => new Keyframe(tv.Item1, tv.Item2)).ToArray()); }
public struct CurveConfig
{
public string Path;
public string Attribute;
public Type Type;
public AnimationCurve Curve;
public CurveConfig(string path, string at, Type type, AnimationCurve curve)
{
this.Path = path;
this.Attribute = at;
this.Type = type;
this.Curve = curve;
}
}
public static AnimationClip CreateClip(string directory, string name, params (string, Type, string, AnimationCurve)[] curves)
{
AnimationClip clip = new AnimationClip();
foreach ((string, Type, string, AnimationCurve) curve in curves)
{
clip.SetCurve(curve.Item1, curve.Item2, curve.Item3, curve.Item4);
}
AssetDatabase.CreateAsset(clip, directory + "/" + name + ".anim");
return clip;
}
public static AnimationClip CreateClip(string directory, string name, params CurveConfig[] curves)
{
AnimationClip clip = new AnimationClip();
foreach (CurveConfig c in curves)
{
clip.SetCurve(c.Path, c.Type, c.Attribute, c.Curve);
}
AssetDatabase.CreateAsset(clip, directory + "/" + name + ".anim");
return clip;
}
public static AnimationClip CreateClip(string name, params (string, Type, string, AnimationCurve)[] curves)
{
AnimationClip clip = new AnimationClip();
clip.name = name;
foreach ((string, Type, string, AnimationCurve) curve in curves)
{
clip.SetCurve(curve.Item1, curve.Item2, curve.Item3, curve.Item4);
}
return clip;
}
public static AnimationClip CreateClip(string name, params CurveConfig[] curves)
{
AnimationClip clip = new AnimationClip();
clip.name = name;
foreach (CurveConfig c in curves)
{
clip.SetCurve(c.Path, c.Type, c.Attribute, c.Curve);
}
return clip;
}
public struct ThryCurveData
{
public string path;
public string propertyName;
public AnimationCurve curve;
}
public static ThryCurveData[] GetAllCurves(AnimationClip clip)
{
if (clip == null) return new ThryCurveData[0];
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(clip);
ThryCurveData[] curves = new ThryCurveData[bindings.Length];
for (int i = 0; i < curves.Length; i++)
{
ThryCurveData data = new ThryCurveData();
data.path = bindings[i].path;
data.propertyName = bindings[i].propertyName;
data.curve = AnimationUtility.GetEditorCurve(clip, bindings[i]);
curves[i] = data;
}
return curves;
}
#endregion
}
#region Vertex Color Baker
public class BakeToVertexColors
{
//Strings
const string log_prefix = "<color=blue>Poi:</color> "; //color is hex or name
static readonly string suffixSeparator = "_";
const string bakedSuffix_normals = "baked_normals";
const string bakedSuffix_mesh = "baked_mesh";
const string bakesFolderName = "Baked";
const string defaultUnityAssetBakesFolder = "Default Unity Resources";
//Properties
static GameObject Selection
{
get => _selection;
set => _selection = value;
}
/// <summary>
/// Adds a suffix to the end of the string then returns it
/// </summary>
/// <param name="str"></param>
/// <param name="suffixes"></param>
/// <returns></returns>
static string AddSuffix(string str, params string[] suffixes)
{
bool ignoreSeparatorOnce = string.IsNullOrWhiteSpace(str);
StringBuilder sb = new StringBuilder(str);
foreach (var suff in suffixes)
{
if (ignoreSeparatorOnce)
{
sb.Append(suff);
ignoreSeparatorOnce = false;
continue;
}
sb.Append(suffixSeparator + suff);
}
return sb.ToString();
}
/// <summary>
/// Replaces all forward slashes \ with back slashes /
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
static string NormalizePathSlashes(string path)
{
if (!string.IsNullOrEmpty(path))
path = path.Replace('\\', '/');
return path;
}
/// <summary>
/// Changes a path in Assets to an absolute windows path
/// </summary>
/// <param name="localPath"></param>
/// <returns></returns>
static string LocalAssetsPathToAbsolutePath(string localPath)
{
localPath = NormalizePathSlashes(localPath);
const string assets = "Assets/";
if (localPath.StartsWith(assets))
{
localPath = localPath.Remove(0, assets.Length);
localPath = $"{Application.dataPath}/{localPath}";
}
return localPath;
}
/// <summary>
/// Ensures directory exists inside the assets folder
/// </summary>
/// <param name="assetPath"></param>
static void EnsurePathExistsInAssets(string assetPath)
{
Directory.CreateDirectory(LocalAssetsPathToAbsolutePath(assetPath));
}
static Texture2D SaveTextureAsset(Mesh mesh, Texture2D tex, string newName)
{
string assetPath = AssetDatabase.GetAssetPath(mesh);
if (string.IsNullOrWhiteSpace(assetPath))
{
Debug.LogWarning(log_prefix + "Invalid asset path for " + mesh.name);
return null;
}
//Figure out folder name
string bakesDir = $"{Path.GetDirectoryName(assetPath)}";
//Handle default assets
if (bakesDir.StartsWith("Library"))
bakesDir = $"Assets\\{defaultUnityAssetBakesFolder}";
if (!bakesDir.EndsWith(bakesFolderName))
bakesDir += $"\\{bakesFolderName}";
if (!assetPath.Contains('.'))
assetPath += '\\';
EnsurePathExistsInAssets(bakesDir);
//Generate path
string pathNoExt = Path.Combine(bakesDir, newName);
string newPath = AssetDatabase.GenerateUniqueAssetPath($"{pathNoExt}.png");
byte[] encoding = tex.EncodeToPNG();
File.Create(newPath).Close();
using (var fs = new FileStream(newPath, FileMode.OpenOrCreate, FileAccess.Write))
{
fs.Write(encoding, 0, encoding.Length);
}
AssetDatabase.ImportAsset(newPath);
TextureImporter importer = TextureImporter.GetAtPath(newPath) as TextureImporter;
importer.filterMode = FilterMode.Point;
importer.wrapMode = TextureWrapMode.Clamp;
importer.mipmapEnabled = false;
importer.streamingMipmaps = true;
importer.sRGBTexture = false;
importer.crunchedCompression = false;
importer.maxTextureSize = 8192;
importer.textureCompression = TextureImporterCompression.Uncompressed;
importer.npotScale = TextureImporterNPOTScale.None;
importer.isReadable = true;
AssetDatabase.ImportAsset(newPath);
tex = AssetDatabase.LoadAssetAtPath<Texture2D>(newPath);
if (tex == null)
{
Debug.Log(log_prefix + "Failed to load saved mesh");
return null;
}
return tex;
}
public static Transform GetArmatureTransform(SkinnedMeshRenderer smr)
{
if (smr.bones.Length == 0) return smr.transform;
IEnumerable<Transform> bones = smr.bones;
Transform armature = bones.First();
while (armature != null && bones.Contains(armature)) armature = armature.parent;
if (armature == null) armature = smr.transform;
return armature;
}
public static MeshInfo[] GetAllMeshInfos(params Renderer[] renderers)
{
var infos = renderers?.Select(ren =>
{
MeshInfo info = new MeshInfo();
if (ren is SkinnedMeshRenderer smr)
{
Mesh bakedMesh = new Mesh();
Queue<(Quaternion, Vector3)> ogTransformSettings = new Queue<(Quaternion, Vector3)>();
Transform tr = smr.transform;
while(tr != null)
{
ogTransformSettings.Enqueue((tr.localRotation, tr.localScale));
tr.localRotation = Quaternion.identity;
tr.localScale = Vector3.one;
tr = tr.parent;
}
Transform aTransform = GetArmatureTransform(ren as SkinnedMeshRenderer);
if(aTransform != smr.transform)
{
ogTransformSettings.Enqueue((aTransform.localRotation, aTransform.localScale));
aTransform.localRotation = Quaternion.identity;
aTransform.localScale = Vector3.one;
}
smr.BakeMesh(bakedMesh);
tr = smr.transform;
while (tr != null)
{
(Quaternion, Vector3) set = ogTransformSettings.Dequeue();
tr.localRotation = set.Item1;
tr.localScale = set.Item2;
tr = tr.parent;
}
if (aTransform != smr.transform)
{
(Quaternion, Vector3) set = ogTransformSettings.Dequeue();
aTransform.localRotation = set.Item1;
aTransform.localScale = set.Item2;
}
info.sharedMesh = smr.sharedMesh;
info.bakedVertices = bakedMesh?.vertices;
info.bakedNormals = bakedMesh?.normals;
info.ownerRenderer = smr;
if (!info.sharedMesh)
Debug.LogWarning(log_prefix + $"Skinned Mesh Renderer at <b>{info.ownerRenderer.gameObject.name}</b> doesn't have a valid mesh");
}
else if (ren is MeshRenderer mr)
{
info.sharedMesh = mr.GetComponent<MeshFilter>()?.sharedMesh;
info.bakedVertices = info.sharedMesh?.vertices;
info.bakedNormals = info.sharedMesh?.normals;
info.ownerRenderer = mr;
if (!info.sharedMesh)
Debug.LogWarning(log_prefix + $"Mesh renderer at <b>{info.ownerRenderer.gameObject.name}</b> doesn't have a mesh filter with a valid mesh");
}
return info;
}).ToArray();
return infos;
}
static Color32 Uint8tof32(uint r, uint g, uint b, uint a)
{
Color32 split = new Color32(
(byte)r,
(byte)g,
(byte)b,
(byte)a
);
return split;
}
static Color32 EncodeFloatToARGB8(float f)
{
uint u = BitConverter.ToUInt32(BitConverter.GetBytes(f), 0);
return Uint8tof32(u & 255, (u >> 8) & 255, (u >> 16) & 255, (u >> 24) & 255);
}
public static float DecodeFloatFromARGB8(Color32 c)
{
uint u = c.r + ((uint)c.g << 8) + ((uint)c.b << 16) + ((uint)c.a << 24);
return BitConverter.ToSingle(BitConverter.GetBytes(u), 0);
}
public static Texture2D GetReadableTexture(Texture texture)
{
RenderTexture temp = RenderTexture.GetTemporary(texture.width, texture.height, 0, RenderTextureFormat.Default, RenderTextureReadWrite.Linear);
Graphics.Blit(texture, temp);
RenderTexture previous = RenderTexture.active;
RenderTexture.active = temp;
Texture2D ret = new Texture2D(texture.width, texture.height);
ret.ReadPixels(new Rect(0, 0, temp.width, temp.height), 0, 0);
ret.Apply();
RenderTexture.active = previous;
RenderTexture.ReleaseTemporary(temp);
return ret;
}
public static Texture2D BakePositionsToTexture(Renderer renderer, Texture2D mask)
{
return BakePositionsToTexture(GetAllMeshInfos(renderer)[0], mask);
}
static Texture2D BakePositionsToTexture(MeshInfo meshInfo, Texture2D mask)
{
if (!meshInfo.sharedMesh)
return null;
Texture2D tex = null;
if (mask != null) mask = GetReadableTexture(mask);
try
{
Vector3[] verts = meshInfo.bakedVertices; //accessing mesh.vertices on every iteration is very slow
Vector3[] norms = meshInfo.bakedNormals; //accessing mesh.vertices on every iteration is very slow
Vector2[] uvs = meshInfo.sharedMesh.uv;
tex = new Texture2D(8190, ((verts.Length * 2 * 3 - 1) / 8190) + 1, TextureFormat.RGBA32, false);
Color32[] colors = new Color32[tex.width * tex.height];
for (int i = 0; i < verts.Length; i++)
{
colors[i * 6 + 0] = EncodeFloatToARGB8(verts[i].x);
colors[i * 6 + 1] = EncodeFloatToARGB8(verts[i].y);
colors[i * 6 + 2] = EncodeFloatToARGB8(verts[i].z);
if (mask != null)
{
Color m = mask.GetPixelBilinear(uvs[i].x, uvs[i].y);
if (m.maxColorComponent > 0 && m.a > 0) continue;
}
colors[i * 6 + 3] = EncodeFloatToARGB8(norms[i].x);
colors[i * 6 + 4] = EncodeFloatToARGB8(norms[i].y);
colors[i * 6 + 5] = EncodeFloatToARGB8(norms[i].z);
}
tex.SetPixels32(colors);
tex.Apply();
//Create new mesh asset and add it to queue
string name = AddSuffix(meshInfo.ownerRenderer.gameObject.name, bakedSuffix_mesh);
tex= SaveTextureAsset(meshInfo.sharedMesh, tex, name);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
return tex;
}
public struct MeshInfo
{
public Renderer ownerRenderer;
public Mesh sharedMesh;
public Vector3[] bakedVertices;
public Vector3[] bakedNormals;
}
struct VertexInfo
{
public Vector3 vertex;
public int originalIndex;
public Vector3 normal;
public Vector3 averagedNormal;
}
static GameObject _selection;
private string _subTitle;
}
#endregion
}