mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-14 14:29:52 -05:00
Merge pull request #2509 from ousttrue/feature/cloth_experiment
[cloth] ClothWarpLib 開発版 を追加
This commit is contained in:
commit
a2cef9784c
|
|
@ -87,7 +87,10 @@ namespace UniVRM10
|
|||
{
|
||||
foreach (var collider in Colliders)
|
||||
{
|
||||
collider.DrawGizmos();
|
||||
if (collider != null)
|
||||
{
|
||||
collider.DrawGizmos();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
Assets/VRM10_Samples/ClothSample.meta
Normal file
8
Assets/VRM10_Samples/ClothSample.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6871c022e581b3742bbc892f38900607
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/ClothViewer.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/ClothViewer.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: db40660c554df69458e2f1385c6b5b89
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
73
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothAIUEO.cs
Normal file
73
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothAIUEO.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public class ClothAIUEO : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public Vrm10Instance Controller;
|
||||
private void Reset()
|
||||
{
|
||||
Controller = GetComponent<Vrm10Instance>();
|
||||
}
|
||||
|
||||
Coroutine m_coroutine;
|
||||
|
||||
[SerializeField]
|
||||
float m_wait = 0.5f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Controller == null)
|
||||
{
|
||||
Controller = GetComponent<Vrm10Instance>();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RoutineNest(ExpressionPreset preset, float velocity, float wait)
|
||||
{
|
||||
for (var value = 0.0f; value <= 1.0f; value += velocity)
|
||||
{
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
|
||||
yield return null;
|
||||
}
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 1.0f);
|
||||
yield return new WaitForSeconds(wait);
|
||||
for (var value = 1.0f; value >= 0; value -= velocity)
|
||||
{
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
|
||||
yield return null;
|
||||
}
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 0);
|
||||
yield return new WaitForSeconds(wait * 2);
|
||||
}
|
||||
|
||||
IEnumerator Routine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
var velocity = 0.1f;
|
||||
|
||||
yield return RoutineNest(ExpressionPreset.aa, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.ih, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.ou, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.ee, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.oh, velocity, m_wait);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_coroutine = StartCoroutine(Routine());
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
StopCoroutine(m_coroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 915733a6f44f9554cae9a093cca75906
|
||||
timeCreated: 1517463794
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
/// <summary>
|
||||
/// 喜怒哀楽驚を循環させる
|
||||
/// </summary>
|
||||
public class ClothAutoExpression : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public Vrm10Instance Controller;
|
||||
private void Reset()
|
||||
{
|
||||
Controller = GetComponent<Vrm10Instance>();
|
||||
}
|
||||
|
||||
Coroutine m_coroutine;
|
||||
|
||||
[SerializeField]
|
||||
float m_wait = 0.5f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Controller == null)
|
||||
{
|
||||
Controller = GetComponent<Vrm10Instance>();
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator RoutineNest(ExpressionPreset preset, float velocity, float wait)
|
||||
{
|
||||
for (var value = 0.0f; value <= 1.0f; value += velocity)
|
||||
{
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
|
||||
yield return null;
|
||||
}
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 1.0f);
|
||||
yield return new WaitForSeconds(wait);
|
||||
for (var value = 1.0f; value >= 0; value -= velocity)
|
||||
{
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), value);
|
||||
yield return null;
|
||||
}
|
||||
Controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(preset), 0);
|
||||
yield return new WaitForSeconds(wait * 2);
|
||||
}
|
||||
|
||||
IEnumerator Routine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return new WaitForSeconds(1.0f);
|
||||
|
||||
var velocity = 0.01f;
|
||||
|
||||
yield return RoutineNest(ExpressionPreset.happy, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.angry, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.sad, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.relaxed, velocity, m_wait);
|
||||
yield return RoutineNest(ExpressionPreset.surprised, velocity, m_wait);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_coroutine = StartCoroutine(Routine());
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
StopCoroutine(m_coroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 974382c74d34d85488192569177a07a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
116
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothBlinker.cs
Normal file
116
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothBlinker.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
/// <summary>
|
||||
/// VRMBlendShapeProxy によるランダムに瞬きするサンプル。
|
||||
/// VRMBlendShapeProxy のある GameObject にアタッチする。
|
||||
/// </summary>
|
||||
public class ClothBlinker : MonoBehaviour
|
||||
{
|
||||
Vrm10Instance m_controller;
|
||||
|
||||
[FormerlySerializedAs("m_interVal")]
|
||||
[SerializeField]
|
||||
public float Interval = 5.0f;
|
||||
|
||||
[FormerlySerializedAs("m_closingTime")]
|
||||
[SerializeField]
|
||||
public float ClosingTime = 0.06f;
|
||||
|
||||
[FormerlySerializedAs("m_openingSeconds")]
|
||||
[SerializeField]
|
||||
public float OpeningSeconds = 0.03f;
|
||||
|
||||
[FormerlySerializedAs("m_closeSeconds")]
|
||||
[SerializeField]
|
||||
public float CloseSeconds = 0.1f;
|
||||
|
||||
Coroutine m_coroutine;
|
||||
|
||||
float m_nextRequest;
|
||||
bool m_request;
|
||||
public bool Request
|
||||
{
|
||||
get { return m_request; }
|
||||
set
|
||||
{
|
||||
if (Time.time < m_nextRequest)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_request = value;
|
||||
m_nextRequest = Time.time + 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator BlinkRoutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var waitTime = Time.time + Random.value * Interval;
|
||||
while (waitTime > Time.time)
|
||||
{
|
||||
if (Request)
|
||||
{
|
||||
m_request = false;
|
||||
break;
|
||||
}
|
||||
yield return null;
|
||||
}
|
||||
|
||||
// close
|
||||
var value = 0.0f;
|
||||
var closeSpeed = 1.0f / CloseSeconds;
|
||||
while (true)
|
||||
{
|
||||
value += Time.deltaTime * closeSpeed;
|
||||
if (value >= 1.0f)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), value);
|
||||
yield return null;
|
||||
}
|
||||
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), 1.0f);
|
||||
|
||||
// wait...
|
||||
yield return new WaitForSeconds(ClosingTime);
|
||||
|
||||
// open
|
||||
value = 1.0f;
|
||||
var openSpeed = 1.0f / OpeningSeconds;
|
||||
while (true)
|
||||
{
|
||||
value -= Time.deltaTime * openSpeed;
|
||||
if (value < 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), value);
|
||||
yield return null;
|
||||
}
|
||||
m_controller.Runtime.Expression.SetWeight(ExpressionKey.CreateFromPreset(ExpressionPreset.blink), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
m_controller = GetComponent<Vrm10Instance>();
|
||||
m_coroutine = StartCoroutine(BlinkRoutine());
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (m_coroutine != null)
|
||||
{
|
||||
StopCoroutine(m_coroutine);
|
||||
m_coroutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: be508240e6f6f57418cca09778dc2546
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
#if UNITY_STANDALONE_WIN
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public static class ClothFileDialogForWindows
|
||||
{
|
||||
#if UNITY_STANDALONE_WIN
|
||||
#region GetOpenFileName
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
public class OpenFileName
|
||||
{
|
||||
public int structSize = 0;
|
||||
public IntPtr dlgOwner = IntPtr.Zero;
|
||||
public IntPtr instance = IntPtr.Zero;
|
||||
public String filter = null;
|
||||
public String customFilter = null;
|
||||
public int maxCustFilter = 0;
|
||||
public int filterIndex = 0;
|
||||
public String file = null;
|
||||
public int maxFile = 0;
|
||||
public String fileTitle = null;
|
||||
public int maxFileTitle = 0;
|
||||
public String initialDir = null;
|
||||
public String title = null;
|
||||
public int flags = 0;
|
||||
public short fileOffset = 0;
|
||||
public short fileExtension = 0;
|
||||
public String defExt = null;
|
||||
public IntPtr custData = IntPtr.Zero;
|
||||
public IntPtr hook = IntPtr.Zero;
|
||||
public String templateName = null;
|
||||
public IntPtr reservedPtr = IntPtr.Zero;
|
||||
public int reservedInt = 0;
|
||||
public int flagsEx = 0;
|
||||
}
|
||||
|
||||
[DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
|
||||
public static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
|
||||
/*
|
||||
public static bool GetOpenFileName1([In, Out] OpenFileName ofn)
|
||||
{
|
||||
return GetOpenFileName(ofn);
|
||||
}
|
||||
*/
|
||||
|
||||
[DllImport("Comdlg32.dll", SetLastError = true, CharSet = CharSet.Auto)]
|
||||
private static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
|
||||
|
||||
static string Filter(params string[] filters)
|
||||
{
|
||||
return string.Join("\0", filters) + "\0";
|
||||
}
|
||||
public static string FileDialog(string title, params string[] extensions)
|
||||
{
|
||||
OpenFileName ofn = new OpenFileName();
|
||||
ofn.structSize = Marshal.SizeOf(ofn);
|
||||
|
||||
var filters = new List<string>();
|
||||
filters.Add("All Files"); filters.Add("*.*");
|
||||
foreach (var ext in extensions)
|
||||
{
|
||||
filters.Add(ext); filters.Add("*" + ext);
|
||||
}
|
||||
ofn.filter = Filter(filters.ToArray());
|
||||
ofn.filterIndex = 2;
|
||||
ofn.file = new string(new char[256]);
|
||||
ofn.maxFile = ofn.file.Length;
|
||||
ofn.fileTitle = new string(new char[64]);
|
||||
ofn.maxFileTitle = ofn.fileTitle.Length;
|
||||
ofn.initialDir = UnityEngine.Application.dataPath;
|
||||
ofn.title = title;
|
||||
//ofn.defExt = "PNG";
|
||||
ofn.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000200 | 0x00000008;//OFN_EXPLORER|OFN_FILEMUSTEXIST|OFN_PATHMUSTEXIST| OFN_ALLOWMULTISELECT|OFN_NOCHANGEDIR
|
||||
if (!GetOpenFileName(ofn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ofn.file;
|
||||
}
|
||||
public static string SaveDialog(string title, string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
OpenFileName ofn = new OpenFileName();
|
||||
ofn.structSize = Marshal.SizeOf(ofn);
|
||||
ofn.filter = Filter("All Files", "*.*", extension, "*" + extension);
|
||||
ofn.filterIndex = 2;
|
||||
var chars = new char[256];
|
||||
var it = Path.GetFileName(path).GetEnumerator();
|
||||
for (int i = 0; i < chars.Length && it.MoveNext(); ++i)
|
||||
{
|
||||
chars[i] = it.Current;
|
||||
}
|
||||
ofn.file = new string(chars);
|
||||
ofn.maxFile = ofn.file.Length;
|
||||
ofn.fileTitle = new string(new char[64]);
|
||||
ofn.maxFileTitle = ofn.fileTitle.Length;
|
||||
ofn.initialDir = Path.GetDirectoryName(path);
|
||||
ofn.title = title;
|
||||
//ofn.defExt = "PNG";
|
||||
ofn.flags = 0x00000002 | 0x00000004; // OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
|
||||
if (!GetSaveFileName(ofn))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ofn.file;
|
||||
}
|
||||
#endregion
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: aad95bf34c27466469ba37298cd4028c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
201
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothGuess.cs
Normal file
201
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothGuess.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniVRM10.ClothWarp.Components;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public static class ClothGuess
|
||||
{
|
||||
public enum StrandConnectionType
|
||||
{
|
||||
Cloth,
|
||||
ClothLoop,
|
||||
Strand,
|
||||
}
|
||||
|
||||
public static void Guess(Animator animator)
|
||||
{
|
||||
// skirt
|
||||
{
|
||||
if (TryAddGroup(animator, HumanBodyBones.Hips,
|
||||
new[] { "skirt", "スカート", "スカート" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroupChildChild(animator, HumanBodyBones.Hips,
|
||||
new[] { "skirt", "スカート", "スカート" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroup(animator, HumanBodyBones.Head,
|
||||
new[] { "髪", "hair" }, out var g))
|
||||
{
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroup(animator, HumanBodyBones.Hips,
|
||||
new[] { "裾" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftUpperArm,
|
||||
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroupChildChild(animator, HumanBodyBones.LeftLowerArm,
|
||||
new[] { "袖" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroupChildChild(animator, HumanBodyBones.RightUpperArm,
|
||||
new[] { "袖" }, new[] { "ひじ袖" }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
c.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroupChildChild(animator, HumanBodyBones.RightLowerArm,
|
||||
new[] { "袖" }, new string[] { }, out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
{
|
||||
if (TryAddGroup(animator, HumanBodyBones.Chest, new[] { "マント" },
|
||||
out var g))
|
||||
{
|
||||
var c = g[0].gameObject.AddComponent<ClothGrid>();
|
||||
c.Warps = g;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="mask"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="animator"></param>
|
||||
/// <param name="humanBone"></param>
|
||||
/// <param name="targets"></param>
|
||||
/// <param name="excludes"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="sort"></param>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
static bool TryAddGroupChildChild(
|
||||
Animator animator, HumanBodyBones humanBone,
|
||||
string[] targets, string[] excludes,
|
||||
out List<ClothWarpRoot> group)
|
||||
{
|
||||
var bone = animator.GetBoneTransform(humanBone);
|
||||
if (bone == null)
|
||||
{
|
||||
Debug.LogWarning($"{humanBone} not found");
|
||||
group = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ClothWarpRoot> transforms = new();
|
||||
foreach (Transform child in bone)
|
||||
{
|
||||
foreach (Transform childchild in child)
|
||||
{
|
||||
if (excludes.Any(x => childchild.name.ToLower().Contains(x.ToLower())))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (childchild.name.ToLower().Contains(target.ToLower()))
|
||||
{
|
||||
var warp = childchild.gameObject.AddComponent<ClothWarpRoot>();
|
||||
// Name = name,
|
||||
// CollisionMask = mask,
|
||||
warp.BaseSettings.radius = 0.02f;
|
||||
// Connection = type
|
||||
transforms.Add(warp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transforms.Count == 0)
|
||||
{
|
||||
// Debug.LogWarning($"{string.Join(',', targets)} not found");
|
||||
group = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
group = transforms;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool TryAddGroup(Animator animator, HumanBodyBones humanBone, string[] targets,
|
||||
out List<ClothWarpRoot> group)
|
||||
{
|
||||
var bone = animator.GetBoneTransform(humanBone);
|
||||
if (bone == null)
|
||||
{
|
||||
Debug.LogWarning($"{humanBone} not found");
|
||||
group = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
List<ClothWarpRoot> transforms = new();
|
||||
foreach (Transform child in bone)
|
||||
{
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (child.name.ToLower().Contains(target.ToLower()))
|
||||
{
|
||||
var warp = child.gameObject.AddComponent<ClothWarpRoot>();
|
||||
if (warp != null)
|
||||
{
|
||||
// CollisionMask = mask,
|
||||
warp.BaseSettings.radius = 0.02f;
|
||||
// Connection = type
|
||||
transforms.Add(warp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transforms.Count == 0)
|
||||
{
|
||||
// Debug.LogWarning($"{string.Join(',', targets)} not found");
|
||||
group = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
group = transforms;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8ad944ee2631d2b4eb6a33e6bbcd349f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
93
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothLoaded.cs
Normal file
93
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothLoaded.cs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
using System;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
class Loaded : IDisposable
|
||||
{
|
||||
RuntimeGltfInstance m_instance;
|
||||
public RuntimeGltfInstance Instance => m_instance;
|
||||
Vrm10Instance m_controller;
|
||||
public Vrm10RuntimeControlRig ControlRig => m_controller.Runtime.ControlRig;
|
||||
public Vrm10Runtime Runtime => m_controller.Runtime;
|
||||
|
||||
ClothAIUEO m_lipSync;
|
||||
bool m_enableLipSyncValue;
|
||||
public bool EnableLipSyncValue
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_enableLipSyncValue == value) return;
|
||||
m_enableLipSyncValue = value;
|
||||
if (m_lipSync != null)
|
||||
{
|
||||
m_lipSync.enabled = m_enableLipSyncValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClothAutoExpression m_autoExpression;
|
||||
bool m_enableAutoExpressionValue;
|
||||
public bool EnableAutoExpressionValue
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_enableAutoExpressionValue == value) return;
|
||||
m_enableAutoExpressionValue = value;
|
||||
if (m_autoExpression != null)
|
||||
{
|
||||
m_autoExpression.enabled = m_enableAutoExpressionValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ClothBlinker m_blink;
|
||||
bool m_enableBlinkValue;
|
||||
public bool EnableBlinkValue
|
||||
{
|
||||
set
|
||||
{
|
||||
if (m_blink == value) return;
|
||||
m_enableBlinkValue = value;
|
||||
if (m_blink != null)
|
||||
{
|
||||
m_blink.enabled = m_enableBlinkValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Loaded(RuntimeGltfInstance instance, Transform lookAtTarget)
|
||||
{
|
||||
m_instance = instance;
|
||||
|
||||
m_controller = instance.GetComponent<Vrm10Instance>();
|
||||
if (m_controller != null)
|
||||
{
|
||||
// VRM
|
||||
m_controller.UpdateType = Vrm10Instance.UpdateTypes.LateUpdate; // after HumanPoseTransfer's setPose
|
||||
{
|
||||
m_lipSync = instance.gameObject.AddComponent<ClothAIUEO>();
|
||||
m_blink = instance.gameObject.AddComponent<ClothBlinker>();
|
||||
m_autoExpression = instance.gameObject.AddComponent<ClothAutoExpression>();
|
||||
|
||||
m_controller.LookAtTargetType = VRM10ObjectLookAt.LookAtTargetTypes.SpecifiedTransform;
|
||||
m_controller.LookAtTarget = lookAtTarget;
|
||||
}
|
||||
}
|
||||
|
||||
var animation = instance.GetComponent<Animation>();
|
||||
if (animation && animation.clip != null)
|
||||
{
|
||||
// GLTF animation
|
||||
animation.Play(animation.clip.name);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// destroy GameObject
|
||||
GameObject.Destroy(m_instance.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b68db84044b3ee84aba85929dad95494
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace VRM.VRM10RokuroCamera
|
||||
{
|
||||
public class ClothRokuroCamera : MonoBehaviour
|
||||
{
|
||||
[Range(0.1f, 5.0f)]
|
||||
public float RotateSpeed = 0.7f;
|
||||
|
||||
[Range(0.1f, 5.0f)]
|
||||
public float GrabSpeed = 0.7f;
|
||||
|
||||
[Range(0.1f, 5.0f)]
|
||||
public float DollySpeed = 1.0f;
|
||||
|
||||
struct PosRot
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
}
|
||||
|
||||
class _Rokuro
|
||||
{
|
||||
public float Yaw = 180.0f;
|
||||
public float Pitch;
|
||||
public float ShiftX;
|
||||
public float ShiftY;
|
||||
public float Distance = 2.0f;
|
||||
|
||||
public void Rotate(float x, float y)
|
||||
{
|
||||
Yaw += x;
|
||||
Pitch -= y;
|
||||
Pitch = Mathf.Clamp(Pitch, -90, 90);
|
||||
}
|
||||
|
||||
public void Grab(float x, float y)
|
||||
{
|
||||
ShiftX += x * Distance;
|
||||
ShiftY += y * Distance;
|
||||
}
|
||||
|
||||
public void Dolly(float delta)
|
||||
{
|
||||
if (delta > 0)
|
||||
{
|
||||
Distance *= 0.9f;
|
||||
}
|
||||
else if (delta < 0)
|
||||
{
|
||||
Distance *= 1.1f;
|
||||
}
|
||||
}
|
||||
|
||||
public PosRot Calc()
|
||||
{
|
||||
var r = Quaternion.Euler(Pitch, Yaw, 0);
|
||||
return new PosRot
|
||||
{
|
||||
Position = r * new Vector3(-ShiftX, -ShiftY, -Distance),
|
||||
Rotation = r,
|
||||
};
|
||||
}
|
||||
}
|
||||
private _Rokuro _currentCamera = new _Rokuro();
|
||||
|
||||
private List<Coroutine> _activeCoroutines = new List<Coroutine>();
|
||||
private void OnEnable()
|
||||
{
|
||||
// right mouse drag
|
||||
_activeCoroutines.Add(StartCoroutine(MouseDragOperationCoroutine(1, diff =>
|
||||
{
|
||||
_currentCamera.Rotate(diff.x * RotateSpeed, diff.y * RotateSpeed);
|
||||
})));
|
||||
// middle mouse drag
|
||||
_activeCoroutines.Add(StartCoroutine(MouseDragOperationCoroutine(2, diff =>
|
||||
{
|
||||
_currentCamera.Grab(
|
||||
diff.x * GrabSpeed / Screen.height,
|
||||
diff.y * GrabSpeed / Screen.height
|
||||
);
|
||||
})));
|
||||
// mouse wheel
|
||||
_activeCoroutines.Add(StartCoroutine(MouseScrollOperationCoroutine(diff =>
|
||||
{
|
||||
_currentCamera.Dolly(diff.y * DollySpeed);
|
||||
})));
|
||||
}
|
||||
private void OnDisable()
|
||||
{
|
||||
foreach (var coroutine in _activeCoroutines)
|
||||
{
|
||||
StopCoroutine(coroutine);
|
||||
}
|
||||
_activeCoroutines.Clear();
|
||||
}
|
||||
private void Update()
|
||||
{
|
||||
var posRot = _currentCamera.Calc();
|
||||
|
||||
transform.localRotation = posRot.Rotation;
|
||||
transform.localPosition = posRot.Position;
|
||||
}
|
||||
private IEnumerator MouseDragOperationCoroutine(int buttonIndex, Action<Vector2> dragOperation)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
while (!Input.GetMouseButtonDown(buttonIndex))
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
var prevPos = Input.mousePosition;
|
||||
while (Input.GetMouseButton(buttonIndex))
|
||||
{
|
||||
var currPos = Input.mousePosition;
|
||||
var diff = currPos - prevPos;
|
||||
dragOperation(diff);
|
||||
|
||||
prevPos = currPos;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
private IEnumerator MouseScrollOperationCoroutine(Action<Vector2> scrollOperation)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
scrollOperation(Input.mouseScrollDelta);
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 93dcf1b2cc4d917489620707e78e9f27
|
||||
timeCreated: 1523878901
|
||||
licenseType: Pro
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public class ClothTargetMover : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
float m_radius = 5.0f;
|
||||
|
||||
[SerializeField]
|
||||
float m_angularVelocity = 40.0f;
|
||||
|
||||
[SerializeField]
|
||||
float m_y = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
float m_height = 3.0f;
|
||||
|
||||
public IEnumerator Start()
|
||||
{
|
||||
var angle = 0.0f;
|
||||
|
||||
while (true)
|
||||
{
|
||||
angle += m_angularVelocity * Time.deltaTime * Mathf.Deg2Rad;
|
||||
|
||||
var x = Mathf.Cos(angle) * m_radius;
|
||||
var z = Mathf.Sin(angle) * m_radius;
|
||||
var y = m_y + m_height * Mathf.Cos(angle / 3);
|
||||
|
||||
transform.localPosition = new Vector3(x, y, z);
|
||||
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0bc0c019f7af45745aa41b25018fd61f
|
||||
timeCreated: 1524045545
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "ClothViewer",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:b7aa47b240b57de44a4b2021c143c9bf",
|
||||
"GUID:8d76e605759c3f64a957d63ef96ada7c",
|
||||
"GUID:1cd941934d098654fa21a13f28346412",
|
||||
"GUID:e47c917724578cc43b5506c17a27e9a0",
|
||||
"GUID:308b348fb80d89d42a9620951b0f60db",
|
||||
"GUID:3e5d614bc16b50d41bd94c8d7444ca46"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 623c6cdde4acc6641978d011f67c5b87
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8498
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothViewer.unity
Normal file
8498
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothViewer.unity
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bedad7250033c0b49ac1e7db4d7ee63e
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
483
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothViewerUI.cs
Normal file
483
Assets/VRM10_Samples/ClothSample/ClothViewer/ClothViewerUI.cs
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using UniVRM10.ClothWarp.Components;
|
||||
using UniGLTF;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public class ClothViewerUI : MonoBehaviour
|
||||
{
|
||||
[SerializeField] Text m_version = default;
|
||||
|
||||
[Header("Model")]
|
||||
[SerializeField] Toggle m_useAsync = default;
|
||||
[SerializeField] Button m_openModel = default;
|
||||
[SerializeField] Toggle m_showBoxMan = default;
|
||||
|
||||
[Header("Cloth")]
|
||||
[SerializeField] Toggle m_useJob = default;
|
||||
[SerializeField] Button m_reconstructSprngBone = default;
|
||||
[SerializeField] Button m_resetSpringBone = default;
|
||||
[SerializeField] Toggle m_pauseSpringBone = default;
|
||||
|
||||
[Header("Motion")]
|
||||
[SerializeField] Button m_openMotion = default;
|
||||
[SerializeField] Button m_pastePose = default;
|
||||
[SerializeField] Toggle ToggleMotionTPose = default;
|
||||
[SerializeField] Toggle ToggleMotionBVH = default;
|
||||
[SerializeField] ToggleGroup ToggleMotion = default;
|
||||
public bool IsTPose
|
||||
{
|
||||
get => ToggleMotion.ActiveToggles().FirstOrDefault() == ToggleMotionTPose;
|
||||
set
|
||||
{
|
||||
ToggleMotionTPose.isOn = value;
|
||||
ToggleMotionBVH.isOn = !value;
|
||||
}
|
||||
}
|
||||
|
||||
[Header("Expression")]
|
||||
[SerializeField] Toggle m_enableLipSync = default;
|
||||
[SerializeField] Toggle m_enableAutoBlink = default;
|
||||
[SerializeField] Toggle m_enableAutoExpression = default;
|
||||
|
||||
[SerializeField] GameObject m_target = default;
|
||||
[SerializeField] TextAsset m_motion;
|
||||
[SerializeField] TextFields m_texts = default;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
var map = new ObjectMap(gameObject);
|
||||
|
||||
m_version = map.Get<Text>("VrmVersion");
|
||||
|
||||
m_useAsync = map.Get<Toggle>("UseAsync");
|
||||
m_openModel = map.Get<Button>("OpenModel");
|
||||
m_showBoxMan = map.Get<Toggle>("ShowBoxMan");
|
||||
|
||||
m_useJob = map.Get<Toggle>("UseJob");
|
||||
m_reconstructSprngBone = map.Get<Button>("ReconstcutSpringBone");
|
||||
m_resetSpringBone = map.Get<Button>("ResetSpringBone");
|
||||
m_pauseSpringBone = map.Get<Toggle>("PauseSpringBone");
|
||||
|
||||
m_openMotion = map.Get<Button>("OpenMotion");
|
||||
m_pastePose = map.Get<Button>("PastePose");
|
||||
ToggleMotionTPose = map.Get<Toggle>("TPose");
|
||||
ToggleMotionBVH = map.Get<Toggle>("BVH");
|
||||
ToggleMotion = map.Get<ToggleGroup>("_Motion_");
|
||||
|
||||
m_enableLipSync = map.Get<Toggle>("EnableLipSync");
|
||||
m_enableAutoBlink = map.Get<Toggle>("EnableAutoBlink");
|
||||
m_enableAutoExpression = map.Get<Toggle>("EnableAutoExpression");
|
||||
m_texts.Reset(map);
|
||||
m_target = GameObject.FindObjectOfType<ClothTargetMover>().gameObject;
|
||||
}
|
||||
|
||||
|
||||
// Runtime
|
||||
GameObject m_root = default;
|
||||
IVrm10Animation m_src = default;
|
||||
public IVrm10Animation Motion
|
||||
{
|
||||
get { return m_src; }
|
||||
set
|
||||
{
|
||||
if (m_src != null)
|
||||
{
|
||||
m_src.Dispose();
|
||||
}
|
||||
m_src = value;
|
||||
|
||||
TPose = new Vrm10TPose(m_src.ControlRig.Item1.GetRawHipsPosition());
|
||||
}
|
||||
}
|
||||
|
||||
public IVrm10Animation TPose;
|
||||
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
|
||||
Loaded m_loaded;
|
||||
ClothWarp.HumanoidPose m_init;
|
||||
|
||||
static class ArgumentChecker
|
||||
{
|
||||
static string[] Supported = {
|
||||
".gltf",
|
||||
".glb",
|
||||
".vrm",
|
||||
".zip",
|
||||
};
|
||||
|
||||
static string UnityHubPath => System.Environment.GetEnvironmentVariable("ProgramFiles") + "\\Unity\\Hub";
|
||||
|
||||
public static bool IsLoadable(string path)
|
||||
{
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
// not exists
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Application.isEditor)
|
||||
{
|
||||
// skip editor argument
|
||||
// {UnityHub_Resources}\PackageManager\ProjectTemplates\com.unity.template.3d-5.0.4.tgz
|
||||
if (path.StartsWith(UnityHubPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(path).ToLower();
|
||||
if (!Supported.Contains(ext))
|
||||
{
|
||||
// unknown extension
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool TryGetFirstLoadable(out string cmd)
|
||||
{
|
||||
foreach (var arg in System.Environment.GetCommandLineArgs())
|
||||
{
|
||||
if (ArgumentChecker.IsLoadable(arg))
|
||||
{
|
||||
cmd = arg;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cmd = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public int Iteration = 32;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
m_version.text = string.Format("VRMViewer {0}.{1}",
|
||||
VRM10SpecVersion.MAJOR, VRM10SpecVersion.MINOR);
|
||||
|
||||
m_openModel.onClick.AddListener(OnOpenModelClicked);
|
||||
m_openMotion.onClick.AddListener(OnOpenMotionClicked);
|
||||
m_pastePose.onClick.AddListener(OnPastePoseClicked);
|
||||
m_reconstructSprngBone.onClick.AddListener(OnReconstruct);
|
||||
m_resetSpringBone.onClick.AddListener(OnReset);
|
||||
|
||||
// load initial bvh
|
||||
if (m_motion != null)
|
||||
{
|
||||
Motion = BvhMotion.LoadBvhFromText(m_motion.text);
|
||||
}
|
||||
|
||||
if (ArgumentChecker.TryGetFirstLoadable(out var cmd))
|
||||
{
|
||||
LoadModel(cmd);
|
||||
}
|
||||
|
||||
m_texts.Start();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.Tab))
|
||||
{
|
||||
if (m_root != null) m_root.SetActive(!m_root.activeSelf);
|
||||
}
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
if (_cancellationTokenSource != null)
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
}
|
||||
|
||||
if (Motion != null)
|
||||
{
|
||||
Motion.ShowBoxMan(m_showBoxMan.isOn);
|
||||
}
|
||||
|
||||
if (m_loaded != null)
|
||||
{
|
||||
m_loaded.EnableLipSyncValue = m_enableLipSync.isOn;
|
||||
m_loaded.EnableBlinkValue = m_enableAutoBlink.isOn;
|
||||
m_loaded.EnableAutoExpressionValue = m_enableAutoExpression.isOn;
|
||||
|
||||
if (IsTPose)
|
||||
{
|
||||
m_loaded.Runtime.VrmAnimation = TPose;
|
||||
}
|
||||
else if (Motion != null)
|
||||
{
|
||||
// Automatically retarget in Vrm10Runtime.Process
|
||||
m_loaded.Runtime.VrmAnimation = Motion;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnOpenModelClicked()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var path = UnityEditor.EditorUtility.OpenFilePanel("Open VRM", "", "vrm");
|
||||
#elif UNITY_STANDALONE_WIN
|
||||
var path = ClothFileDialogForWindows.FileDialog("open VRM", "vrm");
|
||||
#else
|
||||
var path = Application.dataPath + "/default.vrm";
|
||||
#endif
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(path).ToLower();
|
||||
if (ext != ".vrm")
|
||||
{
|
||||
Debug.LogWarning($"{path} is not vrm");
|
||||
return;
|
||||
}
|
||||
|
||||
LoadModel(path);
|
||||
}
|
||||
|
||||
async void OnOpenMotionClicked()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
var path = UnityEditor.EditorUtility.OpenFilePanel("Open Motion", "", "bvh");
|
||||
#elif UNITY_STANDALONE_WIN
|
||||
var path = ClothFileDialogForWindows.FileDialog("open Motion", "bvh", "gltf", "glb", "vrma");
|
||||
#else
|
||||
var path = Application.dataPath + "/default.bvh";
|
||||
#endif
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var ext = Path.GetExtension(path).ToLower();
|
||||
if (ext == ".bvh")
|
||||
{
|
||||
Motion = BvhMotion.LoadBvhFromPath(path);
|
||||
return;
|
||||
}
|
||||
|
||||
// gltf, glb etc...
|
||||
using GltfData data = new AutoGltfFileParser(path).Parse();
|
||||
using var loader = new VrmAnimationImporter(data);
|
||||
var instance = await loader.LoadAsync(new ImmediateCaller());
|
||||
Motion = instance.GetComponent<Vrm10AnimationInstance>();
|
||||
instance.GetComponent<Animation>().Play();
|
||||
}
|
||||
|
||||
async void OnPastePoseClicked()
|
||||
{
|
||||
var text = GUIUtility.systemCopyBuffer;
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Motion = await Vrm10PoseLoader.LoadVrmAnimationPose(text);
|
||||
}
|
||||
catch (UniJSON.ParserException)
|
||||
{
|
||||
Debug.LogWarning("UniJSON.ParserException");
|
||||
}
|
||||
catch (UniJSON.DeserializationException)
|
||||
{
|
||||
Debug.LogWarning("UniJSON.DeserializationException");
|
||||
}
|
||||
}
|
||||
|
||||
void OnReconstruct()
|
||||
{
|
||||
if (m_loaded == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_loaded.Runtime.SpringBone.ReconstructSpringBone();
|
||||
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
|
||||
// system.ResetParticle();
|
||||
}
|
||||
|
||||
void OnReset()
|
||||
{
|
||||
if (m_loaded == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_loaded.Runtime.SpringBone.RestoreInitialTransform();
|
||||
// ResetStrandPose();
|
||||
}
|
||||
|
||||
// Action<float> MakeSetPose()
|
||||
// {
|
||||
// var start = m_init;
|
||||
// var animator = m_loaded.Instance.GetComponent<Animator>();
|
||||
// var end = new ClothWarp.HumanoidPose(animator);
|
||||
// return (float t) =>
|
||||
// {
|
||||
// ClothWarp.HumanoidPose.ApplyLerp(animator, start, end, t);
|
||||
// };
|
||||
// }
|
||||
|
||||
// void ResetStrandPose()
|
||||
// {
|
||||
// ResetStrandPose(MakeSetPose(), 32, 1.0f / 30, 60);
|
||||
// }
|
||||
|
||||
// void ResetStrandPose(Action<float> setPose, int iteration, float timeDelta, int finish)
|
||||
// {
|
||||
// var system = m_loaded.Instance.GetComponent<ClothWarp.RotateParticleSystem>();
|
||||
|
||||
// // init
|
||||
// setPose(0);
|
||||
// system.ResetParticle();
|
||||
|
||||
// // lerp
|
||||
// var t = 0.0f;
|
||||
// var d = 1.0f / iteration;
|
||||
// for (int i = 0; i < iteration; ++i, t += d)
|
||||
// {
|
||||
// setPose(t);
|
||||
// system.Process(timeDelta);
|
||||
// }
|
||||
|
||||
// // finish
|
||||
// setPose(1.0f);
|
||||
// for (int i = 0; i < finish; ++i)
|
||||
// {
|
||||
// system.Process(timeDelta);
|
||||
// }
|
||||
// }
|
||||
|
||||
static IMaterialDescriptorGenerator GetVrmMaterialDescriptorGenerator(bool useUrp)
|
||||
{
|
||||
if (useUrp)
|
||||
{
|
||||
return new UrpVrm10MaterialDescriptorGenerator();
|
||||
}
|
||||
else
|
||||
{
|
||||
return new BuiltInVrm10MaterialDescriptorGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
void OnInit(Vrm10Instance vrm)
|
||||
{
|
||||
var animator = vrm.GetComponent<Animator>();
|
||||
|
||||
try
|
||||
{
|
||||
if (vrm.SpringBone.Springs.Count == 0)
|
||||
{
|
||||
ClothGuess.Guess(animator);
|
||||
if (vrm.SpringBone.ColliderGroups.Count == 0)
|
||||
{
|
||||
HumanoidCollider.AddColliders(animator);
|
||||
var warps = animator.GetComponentsInChildren<ClothWarpRoot>();
|
||||
var colliderGroups = animator.GetComponentsInChildren<VRM10SpringBoneColliderGroup>();
|
||||
foreach (var warp in warps)
|
||||
{
|
||||
warp.ColliderGroups = colliderGroups.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClothWarpRuntimeProvider.FromVrm10(vrm,
|
||||
go => go.AddComponent<ClothWarpRoot>(),
|
||||
o => GameObject.DestroyImmediate(o));
|
||||
}
|
||||
|
||||
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
|
||||
{
|
||||
var cloth = hips.GetComponent<ClothGrid>();
|
||||
if (cloth == null)
|
||||
{
|
||||
cloth = hips.gameObject.AddComponent<ClothGrid>();
|
||||
cloth.Reset();
|
||||
cloth.LoopIsClosed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
async void LoadModel(string path)
|
||||
{
|
||||
// cleanup
|
||||
m_loaded?.Dispose();
|
||||
m_loaded = null;
|
||||
_cancellationTokenSource?.Dispose();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
var cancellationToken = _cancellationTokenSource.Token;
|
||||
|
||||
try
|
||||
{
|
||||
Debug.LogFormat("{0}", path);
|
||||
var vrm10Instance = await Vrm10.LoadPathAsync(path,
|
||||
canLoadVrm0X: true,
|
||||
showMeshes: false,
|
||||
awaitCaller: m_useAsync.isOn
|
||||
? new RuntimeOnlyAwaitCaller()
|
||||
: new ImmediateCaller(),
|
||||
materialGenerator: GetVrmMaterialDescriptorGenerator(true),
|
||||
vrmMetaInformationCallback: m_texts.UpdateMeta,
|
||||
springboneRuntime: m_useJob.isOn
|
||||
? new UniVRM10.ClothWarp.Jobs.ClothWarpJobRuntime(OnInit)
|
||||
: new UniVRM10.ClothWarp.ClothWarpRuntime(OnInit)
|
||||
);
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
UnityObjectDestroyer.DestroyRuntimeOrEditor(vrm10Instance.gameObject);
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
if (vrm10Instance == null)
|
||||
{
|
||||
Debug.LogWarning("LoadPathAsync is null");
|
||||
return;
|
||||
}
|
||||
|
||||
var instance = vrm10Instance.GetComponent<RuntimeGltfInstance>();
|
||||
instance.ShowMeshes();
|
||||
instance.EnableUpdateWhenOffscreen();
|
||||
m_loaded = new Loaded(instance, m_target.transform);
|
||||
m_init = new ClothWarp.HumanoidPose(vrm10Instance.GetComponent<Animator>());
|
||||
m_showBoxMan.isOn = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is OperationCanceledException)
|
||||
{
|
||||
Debug.LogWarning($"Canceled to Load: {path}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Failed to Load: {path}");
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 46079dccaf3b7ac4ebbba728f1971ed0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public static class HumanoidCollider
|
||||
{
|
||||
static (string group, HumanBodyBones head, HumanBodyBones tail, float radius)[] Capsules = new[]
|
||||
{
|
||||
("Leg", HumanBodyBones.LeftUpperLeg, HumanBodyBones.LeftLowerLeg, 0.06f),
|
||||
("Leg", HumanBodyBones.LeftLowerLeg, HumanBodyBones.LeftFoot, 0.05f),
|
||||
("Leg", HumanBodyBones.RightUpperLeg, HumanBodyBones.RightLowerLeg, 0.06f),
|
||||
("Leg", HumanBodyBones.RightLowerLeg, HumanBodyBones.RightFoot, 0.05f),
|
||||
|
||||
("Arm", HumanBodyBones.LeftUpperArm, HumanBodyBones.LeftLowerArm, 0.03f),
|
||||
("Arm", HumanBodyBones.LeftLowerArm, HumanBodyBones.LeftHand, 0.03f),
|
||||
("Arm", HumanBodyBones.LeftHand, HumanBodyBones.LeftMiddleProximal, 0.02f),
|
||||
("Arm", HumanBodyBones.RightUpperArm, HumanBodyBones.RightLowerArm, 0.03f),
|
||||
("Arm", HumanBodyBones.RightLowerArm, HumanBodyBones.RightHand, 0.03f),
|
||||
("Arm", HumanBodyBones.RightHand, HumanBodyBones.RightMiddleProximal, 0.02f),
|
||||
};
|
||||
|
||||
public static void AddColliders(Animator animator)
|
||||
{
|
||||
Dictionary<string, VRM10SpringBoneColliderGroup> map = new();
|
||||
|
||||
foreach (var (group, _head, _tail, radius) in Capsules)
|
||||
{
|
||||
if (!map.ContainsKey(group))
|
||||
{
|
||||
var g = animator.gameObject.AddComponent<VRM10SpringBoneColliderGroup>();
|
||||
map.Add(group, g);
|
||||
}
|
||||
|
||||
|
||||
var head = animator.GetBoneTransform(_head);
|
||||
var vrmCollider = head.gameObject.AddComponent<VRM10SpringBoneCollider>();
|
||||
if (vrmCollider != null)
|
||||
{
|
||||
vrmCollider.Radius = radius;
|
||||
vrmCollider.ColliderType = VRM10SpringBoneColliderTypes.Capsule;
|
||||
var tail = animator.GetBoneTransform(_tail);
|
||||
vrmCollider.Tail = head.worldToLocalMatrix.MultiplyPoint(tail.position);
|
||||
|
||||
map[group].Colliders.Add(vrmCollider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static T GetOrAddComponent<T>(GameObject o) where T : Component
|
||||
{
|
||||
var t = o.GetComponent<T>();
|
||||
if (t != null)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
return o.AddComponent<T>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e6fd7b5b1e402644088796a17d7c2df9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1431334beb4184340955317d5a35a9fe
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UniHumanoid;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public class BvhMotion : IVrm10Animation
|
||||
{
|
||||
UniHumanoid.BvhImporterContext m_context;
|
||||
public Transform Root => m_context?.Root.transform;
|
||||
public SkinnedMeshRenderer m_boxMan;
|
||||
public SkinnedMeshRenderer BoxMan => m_boxMan;
|
||||
(INormalizedPoseProvider, ITPoseProvider) m_controlRig;
|
||||
(INormalizedPoseProvider, ITPoseProvider) IVrm10Animation.ControlRig => m_controlRig;
|
||||
IDictionary<ExpressionKey, Func<float>> _ExpressionMap = new Dictionary<ExpressionKey, Func<float>>();
|
||||
public IReadOnlyDictionary<ExpressionKey, Func<float>> ExpressionMap => (IReadOnlyDictionary<ExpressionKey, Func<float>>)_ExpressionMap;
|
||||
|
||||
public LookAtInput? LookAt { get; set; }
|
||||
|
||||
public BvhMotion(UniHumanoid.BvhImporterContext context)
|
||||
{
|
||||
m_context = context;
|
||||
var provider = new AnimatorPoseProvider(m_context.Root.transform, m_context.Root.GetComponent<Animator>());
|
||||
m_controlRig = (provider, provider);
|
||||
|
||||
// create SkinnedMesh for bone visualize
|
||||
var animator = m_context.Root.GetComponent<Animator>();
|
||||
m_boxMan = SkeletonMeshUtility.CreateRenderer(animator);
|
||||
var shaderName = UnityEngine.Rendering.GraphicsSettings.renderPipelineAsset == null
|
||||
? "Standard"
|
||||
: "Universal Render Pipeline/Lit"
|
||||
;
|
||||
var material = new Material(Shader.Find(shaderName));
|
||||
BoxMan.sharedMaterial = material;
|
||||
var mesh = BoxMan.sharedMesh;
|
||||
mesh.name = "box-man";
|
||||
}
|
||||
|
||||
public static BvhMotion LoadBvhFromText(string source, string path = "tmp.bvh")
|
||||
{
|
||||
var context = new UniHumanoid.BvhImporterContext();
|
||||
context.Parse(path, source);
|
||||
context.Load();
|
||||
return new BvhMotion(context);
|
||||
}
|
||||
public static BvhMotion LoadBvhFromPath(string path)
|
||||
{
|
||||
return LoadBvhFromText(File.ReadAllText(path), path);
|
||||
}
|
||||
|
||||
public void ShowBoxMan(bool enable)
|
||||
{
|
||||
m_boxMan.enabled = enable;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GameObject.Destroy(m_context.Root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d57facd372741a44a4216655733cdf5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 487929d3039a63544a0825523ac6a8ab
|
||||
timeCreated: 1546851178
|
||||
licenseType: Pro
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
141
Assets/VRM10_Samples/ClothSample/ClothViewer/TextFields.cs
Normal file
141
Assets/VRM10_Samples/ClothSample/ClothViewer/TextFields.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
|
||||
namespace UniVRM10.Cloth.Viewer
|
||||
{
|
||||
public class ObjectMap
|
||||
{
|
||||
Dictionary<string, GameObject> _map = new();
|
||||
public IReadOnlyDictionary<string, GameObject> Objects => _map;
|
||||
|
||||
public ObjectMap(GameObject root)
|
||||
{
|
||||
foreach (var x in root.GetComponentsInChildren<Transform>())
|
||||
{
|
||||
_map[x.name] = x.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
public T Get<T>(string name) where T : Component
|
||||
{
|
||||
return _map[name].GetComponent<T>();
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TextFields
|
||||
{
|
||||
[SerializeField]
|
||||
Text m_textModelTitle = default;
|
||||
[SerializeField]
|
||||
Text m_textModelVersion = default;
|
||||
[SerializeField]
|
||||
Text m_textModelAuthor = default;
|
||||
[SerializeField]
|
||||
Text m_textModelCopyright = default;
|
||||
[SerializeField]
|
||||
Text m_textModelContact = default;
|
||||
[SerializeField]
|
||||
Text m_textModelReference = default;
|
||||
[SerializeField]
|
||||
RawImage m_thumbnail = default;
|
||||
|
||||
[SerializeField, Header("CharacterPermission")]
|
||||
Text m_textPermissionAllowed = default;
|
||||
[SerializeField]
|
||||
Text m_textPermissionViolent = default;
|
||||
[SerializeField]
|
||||
Text m_textPermissionSexual = default;
|
||||
[SerializeField]
|
||||
Text m_textPermissionCommercial = default;
|
||||
[SerializeField]
|
||||
Text m_textPermissionOther = default;
|
||||
|
||||
[SerializeField, Header("DistributionLicense")]
|
||||
Text m_textDistributionLicense = default;
|
||||
[SerializeField]
|
||||
Text m_textDistributionOther = default;
|
||||
|
||||
public void Reset(ObjectMap map)
|
||||
{
|
||||
m_textModelTitle = map.Get<Text>("Title (1)");
|
||||
m_textModelVersion = map.Get<Text>("Version (1)");
|
||||
m_textModelAuthor = map.Get<Text>("Author (1)");
|
||||
m_textModelCopyright = map.Get<Text>("Copyright (1)");
|
||||
m_textModelContact = map.Get<Text>("Contact (1)");
|
||||
m_textModelReference = map.Get<Text>("Reference (1)");
|
||||
m_textPermissionAllowed = map.Get<Text>("AllowedUser (1)");
|
||||
m_textPermissionViolent = map.Get<Text>("Violent (1)");
|
||||
m_textPermissionSexual = map.Get<Text>("Sexual (1)");
|
||||
m_textPermissionCommercial = map.Get<Text>("Commercial (1)");
|
||||
m_textPermissionOther = map.Get<Text>("Other (1)");
|
||||
m_textDistributionLicense = map.Get<Text>("LicenseType (1)");
|
||||
m_textDistributionOther = map.Get<Text>("OtherLicense (1)");
|
||||
m_thumbnail = map.Get<RawImage>("RawImage");
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
m_textModelTitle.text = "";
|
||||
m_textModelVersion.text = "";
|
||||
m_textModelAuthor.text = "";
|
||||
m_textModelCopyright.text = "";
|
||||
m_textModelContact.text = "";
|
||||
m_textModelReference.text = "";
|
||||
|
||||
m_textPermissionAllowed.text = "";
|
||||
m_textPermissionViolent.text = "";
|
||||
m_textPermissionSexual.text = "";
|
||||
m_textPermissionCommercial.text = "";
|
||||
m_textPermissionOther.text = "";
|
||||
|
||||
m_textDistributionLicense.text = "";
|
||||
m_textDistributionOther.text = "";
|
||||
}
|
||||
|
||||
public void UpdateMeta(Texture2D thumbnail, UniGLTF.Extensions.VRMC_vrm.Meta meta, Migration.Vrm0Meta meta0)
|
||||
{
|
||||
m_thumbnail.texture = thumbnail;
|
||||
|
||||
if (meta != null)
|
||||
{
|
||||
m_textModelTitle.text = meta.Name;
|
||||
m_textModelVersion.text = meta.Version;
|
||||
m_textModelAuthor.text = meta.Authors[0];
|
||||
m_textModelCopyright.text = meta.CopyrightInformation;
|
||||
m_textModelContact.text = meta.ContactInformation;
|
||||
if (meta.References != null && meta.References.Count > 0)
|
||||
{
|
||||
m_textModelReference.text = meta.References[0];
|
||||
}
|
||||
m_textPermissionAllowed.text = meta.AvatarPermission.ToString();
|
||||
m_textPermissionViolent.text = meta.AllowExcessivelyViolentUsage.ToString();
|
||||
m_textPermissionSexual.text = meta.AllowExcessivelySexualUsage.ToString();
|
||||
m_textPermissionCommercial.text = meta.CommercialUsage.ToString();
|
||||
// m_textPermissionOther.text = meta.OtherPermissionUrl;
|
||||
|
||||
// m_textDistributionLicense.text = meta.ModificationLicense.ToString();
|
||||
m_textDistributionOther.text = meta.OtherLicenseUrl;
|
||||
}
|
||||
|
||||
if (meta0 != null)
|
||||
{
|
||||
m_textModelTitle.text = meta0.title;
|
||||
m_textModelVersion.text = meta0.version;
|
||||
m_textModelAuthor.text = meta0.author;
|
||||
m_textModelContact.text = meta0.contactInformation;
|
||||
m_textModelReference.text = meta0.reference;
|
||||
m_textPermissionAllowed.text = meta0.allowedUser.ToString();
|
||||
m_textPermissionViolent.text = meta0.violentUsage.ToString();
|
||||
m_textPermissionSexual.text = meta0.sexualUsage.ToString();
|
||||
m_textPermissionCommercial.text = meta0.commercialUsage.ToString();
|
||||
m_textPermissionOther.text = meta0.otherPermissionUrl;
|
||||
// m_textDistributionLicense.text = meta0.ModificationLicense.ToString();
|
||||
m_textDistributionOther.text = meta0.otherLicenseUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 58a0b5125aeceaa41b333a57e049c99e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/ClothWarp.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/ClothWarp.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a00a3e7f5bad4ae4cbbf0e2acc739fa8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/ClothWarp/Editor.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/ClothWarp/Editor.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 78593d828a6c64142bffa9e5b8e514dc
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "ClothWarp.Editor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:308b348fb80d89d42a9620951b0f60db",
|
||||
"GUID:e47c917724578cc43b5506c17a27e9a0",
|
||||
"GUID:3e5d614bc16b50d41bd94c8d7444ca46"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a3dafcb1fb23ff148bbc17714d01144c
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Components
|
||||
{
|
||||
[CustomEditor(typeof(ClothWarpRuntimeProvider))]
|
||||
public class RotateParticleRuntimeProviderEditor : Editor
|
||||
{
|
||||
const string FROM_VRM10_MENU = "Replace VRM10 Springs to ClothWarp Warps";
|
||||
|
||||
[MenuItem(FROM_VRM10_MENU, true)]
|
||||
public static bool IsFromVrm10()
|
||||
{
|
||||
var go = Selection.activeGameObject;
|
||||
if (go == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return go.GetComponent<Vrm10Instance>() != null;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
var provider = target as ClothWarpRuntimeProvider;
|
||||
if (provider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var instance = provider.GetComponent<Vrm10Instance>();
|
||||
using (new EditorGUI.DisabledScope(instance == null))
|
||||
{
|
||||
if (GUILayout.Button("Replace VRM10 Springs to ClothWarp Warps"))
|
||||
{
|
||||
Undo.IncrementCurrentGroup();
|
||||
Undo.SetCurrentGroupName(FROM_VRM10_MENU);
|
||||
var undo = Undo.GetCurrentGroup();
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(instance, "RegisterCompleteObjectUndo");
|
||||
ClothWarpRuntimeProvider.FromVrm10(instance, Undo.AddComponent<ClothWarpRoot>, Undo.DestroyObjectImmediate);
|
||||
Undo.RegisterFullObjectHierarchyUndo(instance.gameObject, "RegisterFullObjectHierarchyUndo");
|
||||
|
||||
Undo.RegisterCompleteObjectUndo(provider, "RegisterCompleteObjectUndo");
|
||||
provider.Reset();
|
||||
|
||||
Undo.CollapseUndoOperations(undo);
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(instance == null || !Application.isPlaying))
|
||||
{
|
||||
if (GUILayout.Button("RestoreInitialTransform"))
|
||||
{
|
||||
instance.Runtime.SpringBone.RestoreInitialTransform();
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Reset"))
|
||||
{
|
||||
provider.Reset();
|
||||
}
|
||||
|
||||
base.OnInspectorGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 80a9e5abcd0197a46b50bcd2a1c62561
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UniVRM10;
|
||||
|
||||
namespace UniVRM10.ClothWarp.Components
|
||||
{
|
||||
[CustomEditor(typeof(ClothWarpRoot))]
|
||||
class WarpRootEditor : Editor
|
||||
{
|
||||
private ClothWarpRoot m_target;
|
||||
private Vrm10Instance m_vrm;
|
||||
private MultiColumnTreeView m_treeview;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (target == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_target = (ClothWarpRoot)target;
|
||||
m_vrm = m_target.GetComponentInParent<Vrm10Instance>();
|
||||
}
|
||||
|
||||
// public override void OnInspectorGUI()
|
||||
// {
|
||||
// var n = EditorUtility.GetDirtyCount(m_target.GetInstanceID());
|
||||
// base.OnInspectorGUI();
|
||||
// if (n != EditorUtility.GetDirtyCount(m_target.GetInstanceID()))
|
||||
// {
|
||||
// if (m_vrm != null)
|
||||
// {
|
||||
// if (Application.isPlaying)
|
||||
// {
|
||||
// m_vrm.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.BaseSettings);
|
||||
// foreach (var p in m_target.Particles)
|
||||
// {
|
||||
// m_vrm.Runtime.SpringBone.SetJointLevel(p.Transform, p.GetSettings(m_target.BaseSettings));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
void BindColumn<T>(string title, int width, Func<T> makeVisualELmeent, Func<int, bool> enableFunc, string subpath) where T : BindableElement
|
||||
{
|
||||
m_treeview.columns.Add(new Column
|
||||
{
|
||||
title = title,
|
||||
width = width,
|
||||
makeCell = makeVisualELmeent,
|
||||
bindCell = (v, i) =>
|
||||
{
|
||||
if (v is T prop)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
sb.Append("m_particles.Array.data[");
|
||||
sb.Append(i);
|
||||
sb.Append("].");
|
||||
sb.Append(subpath);
|
||||
var s = sb.ToString();
|
||||
// Debug.Log(s);
|
||||
prop.BindProperty(serializedObject.FindProperty(s));
|
||||
prop.SetEnabled(enableFunc(i));
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public override VisualElement CreateInspectorGUI()
|
||||
{
|
||||
var root = new VisualElement();
|
||||
|
||||
root.TrackSerializedObjectValue(serializedObject, OnValueChanged);
|
||||
|
||||
root.Bind(serializedObject);
|
||||
|
||||
{
|
||||
var s = new PropertyField { bindingPath = "m_Script" };
|
||||
s.SetEnabled(false);
|
||||
root.Add(s);
|
||||
}
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.BaseSettings) });
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.Center) });
|
||||
|
||||
// root.Add(new PropertyField { bindingPath = "m_particles" });
|
||||
{
|
||||
Func<int, bool> isCustom = (i) =>
|
||||
{
|
||||
return m_target.Particles[i].Mode == ClothWarpRoot.ParticleMode.Custom;
|
||||
};
|
||||
|
||||
m_treeview = new MultiColumnTreeView();
|
||||
BindColumn("Transform", 120, () => new ObjectField(), (_) => false, "Transform");
|
||||
BindColumn("Mode", 40, () => new EnumField(), (_) => true, "Mode");
|
||||
BindColumn("stiffnessForce", 40, () => new FloatField(), isCustom, "Settings.stiffnessForce");
|
||||
BindColumn("gravityPower", 40, () => new FloatField(), isCustom, "Settings.gravityPower");
|
||||
BindColumn("gravityDir", 120, () => new Vector3Field(), isCustom, "Settings.gravityDir");
|
||||
BindColumn("dragForce", 40, () => new FloatField(), isCustom, "Settings.dragForce");
|
||||
BindColumn("radius", 40, () => new FloatField(), isCustom, "Settings.radius");
|
||||
|
||||
m_treeview.autoExpand = true;
|
||||
m_treeview.SetRootItems(m_target.m_rootitems);
|
||||
root.Add(m_treeview);
|
||||
}
|
||||
|
||||
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.ColliderGroups) });
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
private void OnValueChanged(SerializedObject so)
|
||||
{
|
||||
Debug.Log("Name changed: " + so.targetObject.name);
|
||||
// var nameProperty = so.FindProperty("m_Name");
|
||||
|
||||
// if (nameProperty.stringValue.Contains(" "))
|
||||
// _textField.style.backgroundColor = Color.red;
|
||||
// else
|
||||
// _textField.style.backgroundColor = StyleKeyword.Null;
|
||||
m_treeview.RefreshItems();
|
||||
// m_treeview.SetRootItems(m_target.m_rootitems);
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public void OnSceneGUI()
|
||||
{
|
||||
HandleUtility.Repaint();
|
||||
if (m_treeview == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var item = m_treeview.selectedItem;
|
||||
if (item == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (item is ClothWarpRoot.Particle p)
|
||||
{
|
||||
p = m_target.GetParticleFromTransform(p.Transform);
|
||||
var t = p.Transform;
|
||||
Handles.color = Color.green;
|
||||
Handles.SphereHandleCap(t.GetInstanceID(), t.position, t.rotation, p.Settings.radius * 2, EventType.Repaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea017d386e4fdee47b8a896571e2a0de
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
129
Assets/VRM10_Samples/ClothSample/ClothWarp/README.md
Normal file
129
Assets/VRM10_Samples/ClothSample/ClothWarp/README.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# ClothWarp(仮)
|
||||
|
||||
これは UniVRM の cloth の開発版です。
|
||||
|
||||
## 概要
|
||||
|
||||
縦糸(Warp)を横に連結して四角格子(Grid)を作ります。
|
||||
|
||||
> 縦糸は従来の `SpringBone` とだいたい同じものです。
|
||||
|
||||
各四角格子にはバネによる横方向の拘束(フックの法則)と当たり判定(2枚の三角形) を持たせます。
|
||||
|
||||
これにより縦糸の間を `Collider`(球/カプセル) がすりぬけることを防止することができます。
|
||||
|
||||
## Components
|
||||
|
||||
設定置き場。布を構成する縦糸(Warp)と、縦糸を横に連結した四角格子(Grid)の2段階とする
|
||||
|
||||
### UniVRM10.ClothWarp.Components.ClothWarpRoot
|
||||
|
||||
各 particle にはVRM-0.X のように子孫を自動登録し Base 設定を適用する。
|
||||
Base 設定を変更する場合は変える方を列挙設定できる。
|
||||
Custom と Disable を選択できる。
|
||||
|
||||
- ゆれものの根元にアタッチする
|
||||
- [ ] 子孫に HumanoidBone がある場合にアタッチ不可
|
||||
- [ ] 枝分かれ
|
||||
- [ ] WarpRoot らからデフォルト以外の Warp を選び出す
|
||||
- [ ] Center
|
||||
- [ ] Scale
|
||||
|
||||
```
|
||||
ひとつめの joint にアタッチする
|
||||
| 子孫の Transform は自動的に登録される(デフォルト設定を適用)
|
||||
| | 個別に無効化・カスタム設定をできるようにする
|
||||
WarpRoot O=o=o=o=o
|
||||
\
|
||||
o 枝分かれも自動的に登録
|
||||
```
|
||||
|
||||
| | MonoBehaviour | move | rotate |
|
||||
| --------------- | ------------- | ------------------------- | ---------- |
|
||||
| root | attach | 動かない | 回転する |
|
||||
| particles[x] | | verlet 積分で慣性移動する | 回転する |
|
||||
| particles[末端] | | verlet 積分で慣性移動する | 回転しない |
|
||||
|
||||
#### Settings
|
||||
|
||||
- [warp]verlet_center: 速度を算出する座標の原点(curernt-pvev は
|
||||
- [warp]stiffness(剛性): 初期姿勢に戻る力
|
||||
- [warp]force(Vec3): 下向きにすれば gravity
|
||||
- [warp]dragforce(抗力): 減速率(1-dragforce を velocity に乗算する)
|
||||
(current-current_center)-(prev-prev_center) に変わる)
|
||||
- [particle]mode: base, custom, disabled
|
||||
- [particle]stiffness(剛性): 初期姿勢に戻る力
|
||||
- [particle]force(Vec3): 下向きにすれば gravity
|
||||
- [particle]dragforce(抗力): 減速率(1-dragforce を velocity に乗算する)
|
||||
|
||||
#### 枝分かれの例外処理
|
||||
|
||||
- 枝分かれした particle は一番兄の particle の local 移動を複製する(独自の速度、衝突はしない)
|
||||
|
||||
### UniVRM10.ClothWarp.Components.ClothGrid
|
||||
|
||||
- 隣り合う Warp に横方向の拘束が追加される
|
||||
- particle sphere の衝突のかわりに四角格子の三角形が衝突する
|
||||
|
||||
- [ ] デフォルト(WarpRoot から children[0] を選択する)以外の縦糸選択
|
||||
- [ ] 三角形の厚み(法線方向の offset)
|
||||
|
||||
```
|
||||
WarpRoot0 o=o=o=o
|
||||
| | |
|
||||
WarpRoot1 o=o=o <- 短い方の長さに合わせる
|
||||
| | |
|
||||
WarpRoot2 o=o=o
|
||||
|
|
||||
v
|
||||
CloseLoop=true ならば WarpRoot0 と連結して輪を閉じる
|
||||
```
|
||||
|
||||
#### Settings
|
||||
|
||||
- CloseLoop: 最後の Warp と 最初の Warp を接続して輪を作る。Warp が3本以上必要。
|
||||
|
||||
## Colliders
|
||||
|
||||
- UniVRM10.VRM10SpringBoneCollider
|
||||
- UniVRM10.VRM10SpringBoneCollider.Group
|
||||
|
||||
を使う。
|
||||
|
||||
## Logic
|
||||
|
||||
### input phase
|
||||
|
||||
- current_position, prev_position が持ち越される
|
||||
- {FromTransform}{位置を修正}{回転を得る} Update root position. each rotation.
|
||||
|
||||
### dynamics phase
|
||||
|
||||
- (if cloth){add force} weft constarint(横方向 ばね拘束)
|
||||
- {位置を求める} 速度、力を解決
|
||||
- {位置を修正} parent constraint(親子間の距離を一定に保つ。再帰)
|
||||
|
||||
### collision phase
|
||||
|
||||
- (if not cloth){位置を修正} particle(Sphere) x collider(Sphere or Capsule)
|
||||
- (if cloth){位置を修正} cloth(Triangle) x collider(SPhere or Capsule)
|
||||
|
||||
### output phase
|
||||
|
||||
- {回転を修正} calc rotation from position(再帰) 👈 ここまで回転は出てこないことに注意
|
||||
- {ToTransform} apply result rotation 👈 回転しかしない(伸びない)
|
||||
|
||||
## TODO
|
||||
|
||||
### Debug
|
||||
|
||||
- [ ] Debug 用の詳細な Gizmo
|
||||
|
||||
### 工夫
|
||||
|
||||
- [ ] Cloth の片面衝突
|
||||
- [ ] 衝突時の velocity 下げ
|
||||
|
||||
### Optimize
|
||||
|
||||
- [ ] 衝突グループ(現状総当たり)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3c501aa9d3818bf419f0bcb53c59e91d
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/ClothWarp/Runtime.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/ClothWarp/Runtime.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2ba7e498c4d5eef44aba4cf125686089
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniVRM10.ClothWarp.Components;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
class ClothRectList
|
||||
{
|
||||
readonly List<Transform> _particles;
|
||||
|
||||
public List<(SpringConstraint, ClothRect)> List = new();
|
||||
public readonly bool[] ClothUsedParticles;
|
||||
|
||||
public ClothRectList(List<Transform> particles, Vrm10Instance vrm)
|
||||
{
|
||||
_particles = particles;
|
||||
ClothUsedParticles = new bool[_particles.Count];
|
||||
|
||||
var cloths = vrm.GetComponentsInChildren<ClothGrid>();
|
||||
foreach (var cloth in cloths)
|
||||
{
|
||||
AddCloth(cloth, vrm);
|
||||
}
|
||||
}
|
||||
|
||||
void AddCloth(ClothGrid cloth, Vrm10Instance vrm)
|
||||
{
|
||||
for (int i = 1; i < cloth.Warps.Count; ++i)
|
||||
{
|
||||
var s0 = cloth.Warps[i - 1];
|
||||
var s1 = cloth.Warps[i];
|
||||
for (int j = 0; j < s0.Particles.Count && j < s1.Particles.Count; ++j)
|
||||
{
|
||||
// d x x c
|
||||
// | |
|
||||
// a x-x b
|
||||
var a = s0.Particles[j].Transform;
|
||||
var b = s1.Particles[j].Transform;
|
||||
var c = j == 0 ? s1.transform : s1.Particles[j - 1].Transform;
|
||||
var d = j == 0 ? s0.transform : s0.Particles[j - 1].Transform;
|
||||
ClothUsedParticles[_particles.IndexOf(a)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(b)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(c)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(d)] = true;
|
||||
if (i % 2 == 1)
|
||||
{
|
||||
// 互い違いに
|
||||
// abcd to badc
|
||||
(a, b) = (b, a);
|
||||
(c, d) = (d, c);
|
||||
}
|
||||
List.Add((
|
||||
new SpringConstraint(
|
||||
_particles.IndexOf(a),
|
||||
_particles.IndexOf(b),
|
||||
Vector3.Distance(
|
||||
vrm.DefaultTransformStates[a].Position,
|
||||
vrm.DefaultTransformStates[b].Position)),
|
||||
new ClothRect(
|
||||
_particles.IndexOf(a),
|
||||
_particles.IndexOf(b),
|
||||
_particles.IndexOf(c),
|
||||
_particles.IndexOf(d))));
|
||||
}
|
||||
}
|
||||
|
||||
if (cloth.Warps.Count >= 3 && cloth.LoopIsClosed)
|
||||
{
|
||||
// close loop
|
||||
var i = cloth.Warps.Count;
|
||||
var s0 = cloth.Warps.Last();
|
||||
var s1 = cloth.Warps.First();
|
||||
for (int j = 0; j < s0.Particles.Count && j < s1.Particles.Count; ++j)
|
||||
{
|
||||
var a = s0.Particles[j].Transform;
|
||||
var b = s1.Particles[j].Transform;
|
||||
var c = j == 0 ? s1.transform : s1.Particles[j - 1].Transform;
|
||||
var d = j == 0 ? s0.transform : s0.Particles[j - 1].Transform;
|
||||
ClothUsedParticles[_particles.IndexOf(a)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(b)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(c)] = true;
|
||||
ClothUsedParticles[_particles.IndexOf(d)] = true;
|
||||
if (i % 2 == 1)
|
||||
{
|
||||
// 互い違いに
|
||||
// abcd to badc
|
||||
(a, b) = (b, a);
|
||||
(c, d) = (d, c);
|
||||
}
|
||||
List.Add((
|
||||
new SpringConstraint(
|
||||
_particles.IndexOf(a),
|
||||
_particles.IndexOf(b),
|
||||
Vector3.Distance(
|
||||
vrm.DefaultTransformStates[a].Position,
|
||||
vrm.DefaultTransformStates[b].Position)
|
||||
),
|
||||
new ClothRect(
|
||||
_particles.IndexOf(a),
|
||||
_particles.IndexOf(b),
|
||||
_particles.IndexOf(c),
|
||||
_particles.IndexOf(d)
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e5215383e4117f748adc44f843ff8568
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "ClothWarp",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
|
||||
"GUID:e47c917724578cc43b5506c17a27e9a0",
|
||||
"GUID:8d76e605759c3f64a957d63ef96ada7c",
|
||||
"GUID:1cd941934d098654fa21a13f28346412"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 308b348fb80d89d42a9620951b0f60db
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
[Serializable]
|
||||
public class ClothWarpNode
|
||||
{
|
||||
public ParticleInitState Init;
|
||||
public ParticleRuntimeState State;
|
||||
|
||||
public readonly List<ClothWarpNode> Children = new();
|
||||
public readonly ClothWarpNode Parent;
|
||||
|
||||
// 現フレームの力積算
|
||||
public Vector3 Force = Vector3.zero;
|
||||
|
||||
// 直前で接触があった
|
||||
public bool HasCollide = false;
|
||||
|
||||
public ClothWarpNode(int index, ClothWarpNode parent, SimulationEnv env, Transform transform, float radius, float mass)
|
||||
{
|
||||
Init = new ParticleInitState(index, transform, radius, mass);
|
||||
State = new ParticleRuntimeState(env, transform);
|
||||
Parent = parent;
|
||||
}
|
||||
|
||||
|
||||
public void BeginFrame(SimulationEnv env, FrameTime time, in Vector3 rest)
|
||||
{
|
||||
// integrate forces
|
||||
Force = Vector3.zero;
|
||||
|
||||
// 曲げ
|
||||
if (HasCollide)
|
||||
{
|
||||
// 震え防止。ちょっとマイルドになるような気もする?
|
||||
}
|
||||
else
|
||||
{
|
||||
// Stiffness: 1 で即時に元に戻る
|
||||
Force += (rest - State.Current) * env.Stiffness / time.SqDt;
|
||||
}
|
||||
|
||||
// 外力(sqDtで割るとピーキーすぎるのでこれでいいのでは?)
|
||||
Force += env.External / time.DeltaTime;
|
||||
}
|
||||
|
||||
public Vector3 Verlet(SimulationEnv env, FrameTime time)
|
||||
{
|
||||
var velocity = (State.Current - State.Prev);
|
||||
if (HasCollide)
|
||||
{
|
||||
// 震え防止。ちょっとマイルドになるような気もする?
|
||||
velocity = Vector3.zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
// DragForce: 1 で即時停止
|
||||
velocity *= (1 - env.DragForce);
|
||||
}
|
||||
|
||||
HasCollide = false;
|
||||
return State.Current + velocity + Force * time.SqDt;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get ParentParent.rotatio * Parent.Init.LocalRotation
|
||||
/// </summary>
|
||||
/// <param name="transforms"></param>
|
||||
/// <returns></returns>
|
||||
public Quaternion RestRotation(IReadOnlyList<Transform> transforms)
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
var parent = transforms[Parent.Init.Index];
|
||||
if (Parent.Parent == null)
|
||||
{
|
||||
var pt = parent.parent;
|
||||
if (pt == null)
|
||||
{
|
||||
return Parent.Init.LocalRotation;
|
||||
}
|
||||
|
||||
return pt.rotation * Parent.Init.LocalRotation;
|
||||
}
|
||||
|
||||
var parentparent = transforms[Parent.Parent.Init.Index];
|
||||
var restRotation = parentparent.rotation * Parent.Init.LocalRotation;
|
||||
return restRotation;
|
||||
}
|
||||
|
||||
public void OnDrawGizmos(Transform transform)
|
||||
{
|
||||
if (Init.Radius == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Gizmos.color = Init.Mass == 0 ? Color.red : Color.gray;
|
||||
if (transform.parent != null && Init.Mass > 0)
|
||||
{
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
Gizmos.DrawLine(transform.parent.position, transform.position);
|
||||
}
|
||||
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, Init.Radius);
|
||||
|
||||
var r = Init.Radius * 2;
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(Vector3.left * r, Vector3.right * r);
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(Vector3.down * r, Vector3.up * r);
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLine(Vector3.back * r, Vector3.forward * r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 125347015f2366e4b8957431407ddf9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,432 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UniVRM10.ClothWarp.Components;
|
||||
using SphereTriangle;
|
||||
using UniGLTF;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
public class ClothWarpRuntime : IVrm10SpringBoneRuntime
|
||||
{
|
||||
Vrm10Instance _vrm;
|
||||
Action<Vrm10Instance> _onInit;
|
||||
bool _initialized = false;
|
||||
bool _building = false;
|
||||
|
||||
public SimulationEnv Env = new()
|
||||
{
|
||||
DragForce = 0.6f,
|
||||
Stiffness = 0.07f,
|
||||
};
|
||||
|
||||
public List<VRM10SpringBoneColliderGroup> _colliderGroups = new();
|
||||
|
||||
public float _clothFactor = 0.5f;
|
||||
|
||||
// runtime
|
||||
public List<Strand> _strands = new List<Strand>();
|
||||
public ParticleList _list = new();
|
||||
|
||||
ClothRectList _clothRects;
|
||||
public List<ClothRectCollision> _clothRectCollisions = new();
|
||||
|
||||
public PositionList _newPos;
|
||||
Vector3[] _restPositions;
|
||||
|
||||
static Color[] Colors = new Color[]
|
||||
{
|
||||
Color.yellow,
|
||||
Color.green,
|
||||
Color.magenta,
|
||||
};
|
||||
|
||||
Color GetGizmoColor(VRM10SpringBoneColliderGroup g)
|
||||
{
|
||||
for (int i = 0; i < _colliderGroups.Count; ++i)
|
||||
{
|
||||
if (_colliderGroups[i] == g)
|
||||
{
|
||||
return Colors[i];
|
||||
}
|
||||
}
|
||||
|
||||
return Color.gray;
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
|
||||
{
|
||||
_building = true;
|
||||
_vrm = vrm;
|
||||
|
||||
if (_onInit != null)
|
||||
{
|
||||
_onInit(vrm);
|
||||
_onInit = null;
|
||||
}
|
||||
|
||||
_initialized = false;
|
||||
var strandMap = new Dictionary<Components.ClothWarpRoot, Strand>();
|
||||
var warps = vrm.GetComponentsInChildren<Components.ClothWarpRoot>();
|
||||
foreach (var warp in warps)
|
||||
{
|
||||
var strands = new List<Strand>();
|
||||
var strand = _list.MakeParticleStrand(Env, warp);
|
||||
strands.Add(strand);
|
||||
_strands.AddRange(strands);
|
||||
strandMap.Add(warp, strand);
|
||||
|
||||
foreach (var g in warp.ColliderGroups)
|
||||
{
|
||||
foreach (var c in g.Colliders)
|
||||
{
|
||||
if (c != null)
|
||||
{
|
||||
AddColliderIfNotExists(g.name, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_clothRects = new ClothRectList(_list._particleTransforms, vrm);
|
||||
|
||||
_newPos = new(_list._particles.Count);
|
||||
_list.EndInitialize(_newPos.Init);
|
||||
_restPositions = new Vector3[_list._particles.Count];
|
||||
_newPos.EndInitialize();
|
||||
|
||||
_clothRectCollisions = new();
|
||||
for (int i = 0; i < _clothRects.List.Count; ++i)
|
||||
{
|
||||
var (s, r) = _clothRects.List[i];
|
||||
_clothRectCollisions.Add(new());
|
||||
var c = _clothRectCollisions.Last();
|
||||
c.InitializeColliderSide(_newPos, _colliderGroups, r);
|
||||
}
|
||||
|
||||
// await awaitCaller.NextFrame();
|
||||
|
||||
_initialized = true;
|
||||
_building = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// すべての Particle を Init 状態にする。
|
||||
/// Verlet の Prev を現在地に更新する(速度0)。
|
||||
/// </summary>
|
||||
public void RestoreInitialTransform()
|
||||
{
|
||||
foreach (var strand in _strands)
|
||||
{
|
||||
strand.Reset(_list._particleTransforms);
|
||||
}
|
||||
// foreach (var p in _system._list._particleTransforms)
|
||||
// {
|
||||
// p.transform.localRotation = _vrm.DefaultTransformStates[p.transform].LocalRotation;
|
||||
// }
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Process(Time.deltaTime);
|
||||
}
|
||||
|
||||
void Process(float deltaTime)
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using var profile = new ProfileSample("ClothWarp");
|
||||
|
||||
_newPos.BoundsCache.Clear();
|
||||
|
||||
using (new ProfileSample("UpdateRoot"))
|
||||
{
|
||||
//
|
||||
// input
|
||||
//
|
||||
// 各strandのrootの移動と回転を外部から入力する。
|
||||
// それらを元に各 joint の方向を元に戻した場合の戻り位置を計算する
|
||||
foreach (var strand in _strands)
|
||||
{
|
||||
strand.UpdateRoot(_list._particleTransforms, _newPos, _restPositions);
|
||||
}
|
||||
}
|
||||
|
||||
using (new ProfileSample("Verlet"))
|
||||
{
|
||||
//
|
||||
// particle simulation
|
||||
//
|
||||
// verlet 積分
|
||||
var time = new FrameTime(deltaTime);
|
||||
_list.BeginFrame(Env, time, _restPositions);
|
||||
foreach (var (spring, collision) in _clothRects.List)
|
||||
{
|
||||
// cloth constraint
|
||||
spring.Resolve(time, _clothFactor, _list._particles);
|
||||
}
|
||||
_list.Verlet(Env, time, _newPos.Init);
|
||||
// 長さで拘束
|
||||
foreach (var strand in _strands)
|
||||
{
|
||||
strand.ForceLength(_list._particleTransforms, _newPos);
|
||||
}
|
||||
}
|
||||
|
||||
// collision
|
||||
using (new ProfileSample("Collision"))
|
||||
{
|
||||
for (int i = 0; i < _colliderGroups.Count; ++i)
|
||||
{
|
||||
var g = _colliderGroups[i];
|
||||
|
||||
for (int j = 0; j < _clothRects.List.Count; ++j)
|
||||
{
|
||||
var (spring, rect) = _clothRects.List[j];
|
||||
var collision = _clothRectCollisions[j];
|
||||
// using var prof = new ProfileSample("Collision: Cloth");
|
||||
// 頂点 abcd は同じ CollisionMask
|
||||
// TODO:
|
||||
// if (_list._particles[rect._a].Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1)))
|
||||
{
|
||||
// cloth
|
||||
collision.Collide(_newPos, g.Colliders, rect);
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < _list._particles.Count; ++j)
|
||||
{
|
||||
// using var prof = new ProfileSample("Collision: Strand");
|
||||
if (_clothRects.ClothUsedParticles[j])
|
||||
{
|
||||
// 布で処理された
|
||||
continue;
|
||||
}
|
||||
|
||||
var particle = _list._particles[j];
|
||||
if (particle.Init.Mass == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 紐の当たり判定
|
||||
// TODO:
|
||||
// if (particle.Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1)))
|
||||
{
|
||||
var p = _newPos.Get(j);
|
||||
foreach (var c in g.Colliders)
|
||||
{
|
||||
// strand
|
||||
if (c != null && TryCollide(c, p, particle.Init.Radius, out var resolved))
|
||||
{
|
||||
_newPos.CollisionMove(particle.Init.Index, resolved, c.Radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (new ProfileSample("Apply"))
|
||||
{
|
||||
for (int i = 0; i < _newPos.CollisionCount.Length; ++i)
|
||||
{
|
||||
if (_newPos.CollisionCount[i] > 0)
|
||||
{
|
||||
_list._particles[i].HasCollide = true;
|
||||
}
|
||||
}
|
||||
var result = _newPos.Resolve();
|
||||
//
|
||||
// apply result
|
||||
//
|
||||
// apply positions and
|
||||
// calc rotation from positions recursive
|
||||
foreach (var strand in _strands)
|
||||
{
|
||||
strand.Apply(_list._particleTransforms, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VRM10SpringBoneColliderGroup GetOrAddColliderGroup(string groupName, GameObject go)
|
||||
{
|
||||
foreach (var g in _colliderGroups)
|
||||
{
|
||||
if (g.Name == groupName)
|
||||
{
|
||||
return g;
|
||||
}
|
||||
}
|
||||
|
||||
var group = go.GetOrAddComponent<VRM10SpringBoneColliderGroup>();
|
||||
_colliderGroups.Add(group);
|
||||
return group;
|
||||
}
|
||||
|
||||
void AddColliderIfNotExists(string groupName,
|
||||
VRM10SpringBoneCollider c)
|
||||
{
|
||||
var group = GetOrAddColliderGroup(groupName, c.gameObject);
|
||||
|
||||
foreach (var collider in group.Colliders)
|
||||
{
|
||||
if (collider == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (collider == c)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// c.GizmoColor = GetGizmoColor(group);
|
||||
group.Colliders.Add(c);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide sphere a and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="ra"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="ba"></param>
|
||||
/// <param name="resolved"></param>
|
||||
/// <returns></returns>
|
||||
static bool TryCollideSphereAndSphere(
|
||||
in Vector3 from, float ra,
|
||||
in Vector3 to, float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var d = Vector3.Distance(from, to);
|
||||
if (d > (ra + rb))
|
||||
{
|
||||
resolved = default;
|
||||
return false;
|
||||
}
|
||||
Vector3 normal = (to - from).normalized;
|
||||
resolved = new(from, from + normal * (d - rb));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide capsule and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="capsuleHead"></param>
|
||||
/// <param name="capsuleTail"></param>
|
||||
/// <param name="capsuleRadius"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="rb"></param>
|
||||
static bool TryCollideCapsuleAndSphere(
|
||||
in Vector3 capsuleHead,
|
||||
in Vector3 capsuleTail,
|
||||
float capsuleRadius,
|
||||
in Vector3 b,
|
||||
float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var P = (capsuleTail - capsuleHead).normalized;
|
||||
var Q = b - capsuleHead;
|
||||
var dot = Vector3.Dot(P, Q);
|
||||
if (dot <= 0)
|
||||
{
|
||||
// head側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
var t = dot / P.magnitude;
|
||||
if (t >= 1.0f)
|
||||
{
|
||||
// tail側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
// head-tail上の m_transform.position との最近点
|
||||
var p = capsuleHead + P * t;
|
||||
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collision for strand
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="resolved"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryCollide(VRM10SpringBoneCollider c, in Vector3 p, float radius, out LineSegment resolved)
|
||||
{
|
||||
var headWorldPosition = c.transform.TransformPoint(c.Offset);
|
||||
if (c.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
|
||||
{
|
||||
var tailWorldPosition = c.transform.TransformPoint(c.TailOrNormal);
|
||||
return TryCollideCapsuleAndSphere(headWorldPosition, tailWorldPosition, c.Radius, p, radius, out resolved);
|
||||
}
|
||||
else
|
||||
{
|
||||
return TryCollideSphereAndSphere(headWorldPosition, c.Radius, p, radius, out resolved);
|
||||
}
|
||||
}
|
||||
|
||||
void IVrm10SpringBoneRuntime.DrawGizmos()
|
||||
{
|
||||
_list.DrawGizmos();
|
||||
|
||||
for (int i = 0; i < _clothRectCollisions.Count; ++i)
|
||||
{
|
||||
// var (spring, rect) = _clothRects[i];
|
||||
var collision = _clothRectCollisions[i];
|
||||
// collision.DrawGizmos();
|
||||
}
|
||||
|
||||
if (_newPos != null)
|
||||
{
|
||||
_newPos.DrawGizmos();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings)
|
||||
{
|
||||
}
|
||||
|
||||
public ClothWarpRuntime(Action<Vrm10Instance> onInit = null)
|
||||
{
|
||||
_onInit = onInit;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool ReconstructSpringBone()
|
||||
{
|
||||
if (_vrm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_building)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var task = InitializeAsync(_vrm, new ImmediateCaller());
|
||||
task.Wait();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: edc50c3e3f828de469f8ae6af569c174
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 7a8d83c5cee1b1349a818b15bfe48c35
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Components
|
||||
{
|
||||
[AddComponentMenu("ClothWarp/ClothGrid")]
|
||||
[DisallowMultipleComponent]
|
||||
public class ClothGrid : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
public bool LoopIsClosed = false;
|
||||
|
||||
[SerializeField]
|
||||
public List<ClothWarpRoot> Warps = new();
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
for (int i = 0; i < transform.childCount; ++i)
|
||||
{
|
||||
var child = transform.GetChild(i);
|
||||
if (child.TryGetComponent<ClothWarpRoot>(out var warp))
|
||||
{
|
||||
Warps.Add(warp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDrawGizmosSelected()
|
||||
{
|
||||
if (Warps.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Gizmos.color = Color.red;
|
||||
Gizmos.color = new Color(1, 0.5f, 0);
|
||||
for (int i = 0; i < Warps.Count; ++i)
|
||||
{
|
||||
if (i + 1 == Warps.Count)
|
||||
{
|
||||
if (LoopIsClosed)
|
||||
{
|
||||
DrawWeft(Warps[i], Warps[0]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawWeft(Warps[i], Warps[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawWeft(ClothWarpRoot w0, ClothWarpRoot w1)
|
||||
{
|
||||
if (w0 == null || w1 == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Gizmos.DrawLine(w0.transform.position, w1.transform.position);
|
||||
|
||||
for (int i = 0; i < w0.Particles.Count && i < w1.Particles.Count; ++i)
|
||||
{
|
||||
var p0 = w0.Particles[i];
|
||||
var p1 = w1.Particles[i];
|
||||
if (p0.Transform != null && p1.Transform != null)
|
||||
{
|
||||
Gizmos.DrawLine(p0.Transform.position, p1.Transform.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eb08bedf8ccf007488d5907f401f289d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UniVRM10;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Components
|
||||
{
|
||||
[AddComponentMenu("ClothWarp/ClothWarpRoot")]
|
||||
[DisallowMultipleComponent]
|
||||
/// <summary>
|
||||
/// Warp の root にアタッチする。
|
||||
/// 子孫の Transform がすべて登録される。
|
||||
/// </summary>
|
||||
public class ClothWarpRoot : MonoBehaviour
|
||||
{
|
||||
public static BlittableJointMutable DefaultSetting()
|
||||
{
|
||||
return new BlittableJointMutable
|
||||
{
|
||||
stiffnessForce = 1.0f,
|
||||
gravityPower = 0,
|
||||
gravityDir = new Vector3(0, -1.0f, 0),
|
||||
dragForce = 0.4f,
|
||||
radius = 0.02f,
|
||||
};
|
||||
}
|
||||
|
||||
public enum ParticleMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Use BaseSettings
|
||||
/// </summary>
|
||||
Base,
|
||||
/// <summary>
|
||||
/// Use specific settings
|
||||
/// </summary>
|
||||
Custom,
|
||||
/// <summary>
|
||||
/// no animation
|
||||
/// </summary>
|
||||
Disabled,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VRM10SpringBoneJoint に相当する
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct Particle
|
||||
{
|
||||
public Transform Transform;
|
||||
public ParticleMode Mode;
|
||||
public BlittableJointMutable Settings;
|
||||
|
||||
public Particle(Transform t, ParticleMode mode, BlittableJointMutable settings)
|
||||
{
|
||||
Transform = t;
|
||||
Mode = mode;
|
||||
Settings = settings;
|
||||
}
|
||||
|
||||
public Particle(Transform t, BlittableJointMutable settings)
|
||||
: this(t, ParticleMode.Custom, settings)
|
||||
{
|
||||
}
|
||||
|
||||
public Particle(Transform t)
|
||||
: this(t, ParticleMode.Base, DefaultSetting())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
public BlittableJointMutable BaseSettings = DefaultSetting();
|
||||
|
||||
/// <summary>
|
||||
/// null のときは world root ではなく model root で処理
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public Transform Center;
|
||||
|
||||
/// <summary>
|
||||
/// 枝分かれ不可
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
private List<Particle> m_particles = new();
|
||||
public IReadOnlyList<Particle> Particles => m_particles;
|
||||
|
||||
[SerializeField]
|
||||
public List<VRM10SpringBoneColliderGroup> ColliderGroups = new();
|
||||
|
||||
// uitool kit 向け
|
||||
public List<TreeViewItemData<Particle>> m_rootitems;
|
||||
|
||||
// 逆引き
|
||||
Dictionary<Transform, int> m_map = new();
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
m_particles = GetComponentsInChildren<Transform>().Skip(1).Select(x => new Particle(x)).ToList();
|
||||
m_map.Clear();
|
||||
for (int i = 0; i < m_particles.Count; ++i)
|
||||
{
|
||||
var p = m_particles[i];
|
||||
if (p.Mode == ParticleMode.Base)
|
||||
{
|
||||
p.Settings = BaseSettings;
|
||||
m_particles[i] = p;
|
||||
}
|
||||
m_map.Add(p.Transform, i);
|
||||
}
|
||||
|
||||
m_rootitems = MakeTree(-1);
|
||||
}
|
||||
|
||||
List<TreeViewItemData<Particle>> MakeTree(int id)
|
||||
{
|
||||
List<TreeViewItemData<Particle>> items = new();
|
||||
foreach (Transform child_transform in id == -1 ? transform : m_particles[id].Transform)
|
||||
{
|
||||
var child_id = m_map[child_transform];
|
||||
var item = (child_transform.childCount > 0)
|
||||
? new TreeViewItemData<Particle>(child_id, m_particles[child_id], MakeTree(child_id))
|
||||
: new TreeViewItemData<Particle>(child_id, m_particles[child_id])
|
||||
;
|
||||
items.Add(item);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
public Particle GetParticleFromId(int id)
|
||||
{
|
||||
return m_particles[id];
|
||||
}
|
||||
|
||||
public Particle GetParticleFromTransform(Transform t)
|
||||
{
|
||||
foreach (var p in m_particles)
|
||||
{
|
||||
if (p.Transform == t)
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public void UseBaseSettings(Transform t)
|
||||
{
|
||||
if (t == null) return;
|
||||
for (int i = 0; i < m_particles.Count; ++i)
|
||||
{
|
||||
var p = m_particles[i];
|
||||
if (p.Transform == t)
|
||||
{
|
||||
p.Mode = ParticleMode.Base;
|
||||
p.Settings = BaseSettings;
|
||||
m_particles[i] = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetSettings(Transform t, BlittableJointMutable settings)
|
||||
{
|
||||
if (t == null) return;
|
||||
for (int i = 0; i < m_particles.Count; ++i)
|
||||
{
|
||||
var p = m_particles[i];
|
||||
if (p.Transform == t)
|
||||
{
|
||||
p.Mode = ParticleMode.Custom;
|
||||
p.Settings = settings;
|
||||
m_particles[i] = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDrawGizmosSelected()
|
||||
{
|
||||
Gizmos.DrawSphere(transform.position, BaseSettings.radius);
|
||||
|
||||
foreach (var p in Particles)
|
||||
{
|
||||
if (p.Transform == null || p.Mode == ParticleMode.Disabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
Gizmos.DrawWireSphere(p.Transform.position, p.Settings.radius);
|
||||
|
||||
if (TryGetClosestParent(p.Transform, out var parent))
|
||||
{
|
||||
Gizmos.DrawLine(p.Transform.position, parent.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TryGetClosestParent(Transform t, out Transform parent)
|
||||
{
|
||||
for (var current = t.parent; current != null; current = current.parent)
|
||||
{
|
||||
if (current == transform)
|
||||
{
|
||||
parent = transform;
|
||||
return true;
|
||||
}
|
||||
|
||||
var index = m_map[current];
|
||||
var p = m_particles[index];
|
||||
if (p.Mode != ParticleMode.Disabled)
|
||||
{
|
||||
parent = p.Transform;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
parent = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6d9e9d5010d025b4c9cdfb03eb8b1b0b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
namespace UniVRM10.ClothWarp.Components
|
||||
{
|
||||
[AddComponentMenu("ClothWarp/ClothWarpRuntimeProvider")]
|
||||
[DisallowMultipleComponent]
|
||||
public class ClothWarpRuntimeProvider : MonoBehaviour, IVrm10SpringBoneRuntimeProvider
|
||||
{
|
||||
[SerializeField]
|
||||
public List<ClothWarpRoot> Warps = new();
|
||||
|
||||
[SerializeField]
|
||||
public List<ClothGrid> Cloths = new();
|
||||
|
||||
[SerializeField]
|
||||
public bool UseJob;
|
||||
|
||||
IVrm10SpringBoneRuntime m_runtime;
|
||||
public IVrm10SpringBoneRuntime CreateSpringBoneRuntime()
|
||||
{
|
||||
m_runtime = UseJob
|
||||
? new Jobs.ClothWarpJobRuntime()
|
||||
: new ClothWarpRuntime()
|
||||
;
|
||||
return m_runtime;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Warps = GetComponentsInChildren<ClothWarpRoot>().ToList();
|
||||
Cloths = GetComponentsInChildren<ClothGrid>().ToList();
|
||||
}
|
||||
|
||||
void OnDrawGizmos()
|
||||
{
|
||||
if (m_runtime == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
m_runtime.DrawGizmos();
|
||||
}
|
||||
|
||||
public static void FromVrm10(Vrm10Instance instance,
|
||||
Func<GameObject, ClothWarpRoot> addWarp,
|
||||
Action<UnityEngine.Object> deleteObject)
|
||||
{
|
||||
foreach (var spring in instance.SpringBone.Springs)
|
||||
{
|
||||
if (spring.Joints == null || spring.Joints[0] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var root_joint = spring.Joints[0].gameObject;
|
||||
if (root_joint == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var warp = root_joint.GetComponent<ClothWarpRoot>();
|
||||
if (warp == null)
|
||||
{
|
||||
// var warp = Undo.AddComponent<Warp>(root_joint);
|
||||
warp = addWarp(root_joint);
|
||||
var joints = spring.Joints.Where(x => x != null).ToArray();
|
||||
for (int i = 0; i < joints.Length; ++i)
|
||||
{
|
||||
var joint = joints[i];
|
||||
var settings = new UniGLTF.SpringBoneJobs.Blittables.BlittableJointMutable
|
||||
{
|
||||
dragForce = joint.m_dragForce,
|
||||
gravityDir = joint.m_gravityDir,
|
||||
gravityPower = joint.m_gravityPower,
|
||||
// mod
|
||||
stiffnessForce = joint.m_stiffnessForce * 6,
|
||||
};
|
||||
if (i == 0)
|
||||
{
|
||||
settings.radius = joints[0].m_jointRadius;
|
||||
warp.BaseSettings = settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
// breaking change from vrm-1.0
|
||||
settings.radius = joints[i - 1].m_jointRadius;
|
||||
var useInheritSettings = warp.BaseSettings.Equals(settings);
|
||||
if (useInheritSettings)
|
||||
{
|
||||
warp.UseBaseSettings(joint.transform);
|
||||
}
|
||||
else
|
||||
{
|
||||
warp.SetSettings(joint.transform, settings);
|
||||
}
|
||||
}
|
||||
// Undo.DestroyObjectImmediate(joint);
|
||||
deleteObject(joint);
|
||||
}
|
||||
spring.Joints.Clear();
|
||||
warp.ColliderGroups = spring.ColliderGroups.ToList();
|
||||
}
|
||||
}
|
||||
instance.SpringBone.Springs.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: de8d095eee60f134ebd9ccaa9efcc01e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
public struct FrameTime
|
||||
{
|
||||
public readonly float DeltaTime;
|
||||
public readonly float SqDt;
|
||||
public FrameTime(float deltaTime)
|
||||
{
|
||||
DeltaTime = deltaTime;
|
||||
SqDt = deltaTime * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 568e5702a44c18d4487775441e011a61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
public class HumanoidPose
|
||||
{
|
||||
public Vector3 HipsPosition;
|
||||
public List<(HumanBodyBones, Quaternion)> Rotations = new();
|
||||
|
||||
public HumanoidPose(Animator aninmator)
|
||||
{
|
||||
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
|
||||
{
|
||||
if (bone == HumanBodyBones.LastBone)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var t = aninmator.GetBoneTransform(bone);
|
||||
if (t != null)
|
||||
{
|
||||
if (bone == HumanBodyBones.Hips)
|
||||
{
|
||||
HipsPosition = t.localPosition;
|
||||
}
|
||||
Rotations.Add((bone, t.localRotation));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyLerp(Animator a, HumanoidPose start, HumanoidPose end, float t)
|
||||
{
|
||||
foreach (var (b, sr, er) in Enumerable.Zip(start.Rotations, end.Rotations, (s, e) =>
|
||||
{
|
||||
var (sb, sr) = s;
|
||||
var (eb, er) = e;
|
||||
if (sb != eb)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
return (sb, sr, er);
|
||||
}))
|
||||
{
|
||||
var transform = a.GetBoneTransform(b);
|
||||
if (transform == null)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
transform.localRotation = Quaternion.Slerp(sr, er, t);
|
||||
if (b == HumanBodyBones.Hips)
|
||||
{
|
||||
transform.localPosition = Vector3.Lerp(start.HipsPosition, end.HipsPosition, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 500c97fb71417394e8144b008b462e6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 88171b192ea95594c88871ba5b00d0ef
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,439 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UniGLTF;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
using UniVRM10;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public class ClothWarpJobRuntime : IVrm10SpringBoneRuntime
|
||||
{
|
||||
Vrm10Instance _vrm;
|
||||
Action<Vrm10Instance> _onInit;
|
||||
bool _building = false;
|
||||
|
||||
//
|
||||
// collider
|
||||
//
|
||||
List<Transform> _colliderTransforms;
|
||||
TransformAccessArray _colliderTransformAccessArray;
|
||||
NativeArray<Matrix4x4> _currentColliders;
|
||||
NativeArray<BlittableCollider> _colliders;
|
||||
|
||||
//
|
||||
// particle
|
||||
//
|
||||
List<Transform> _transforms;
|
||||
TransformAccessArray _transformAccessArray;
|
||||
NativeArray<TransformData> _inputData;
|
||||
NativeArray<TransformInfo> _info;
|
||||
NativeArray<Vector3> _currentPositions;
|
||||
NativeArray<Vector3> _prevPositions;
|
||||
NativeArray<Vector3> _nextPositions;
|
||||
NativeArray<Quaternion> _nextRotations;
|
||||
|
||||
NativeArray<Vector3> _strandCollision;
|
||||
NativeArray<int> _clothCollisionCount;
|
||||
NativeArray<Vector3> _clothCollisionDelta;
|
||||
NativeArray<Vector3> _forces;
|
||||
|
||||
//
|
||||
// warp
|
||||
//
|
||||
NativeArray<WarpInfo> _warps;
|
||||
|
||||
//
|
||||
// cloth
|
||||
//
|
||||
NativeArray<bool> _clothUsedParticles;
|
||||
NativeArray<(SpringConstraint, SphereTriangle.ClothRect)> _clothRects;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_colliderTransformAccessArray.isCreated) _colliderTransformAccessArray.Dispose();
|
||||
if (_currentColliders.IsCreated) _currentColliders.Dispose();
|
||||
|
||||
if (_warps.IsCreated) _warps.Dispose();
|
||||
if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
|
||||
if (_inputData.IsCreated) _inputData.Dispose();
|
||||
if (_info.IsCreated) _info.Dispose();
|
||||
if (_currentPositions.IsCreated) _currentPositions.Dispose();
|
||||
if (_prevPositions.IsCreated) _prevPositions.Dispose();
|
||||
if (_nextPositions.IsCreated) _nextPositions.Dispose();
|
||||
if (_nextRotations.IsCreated) _nextRotations.Dispose();
|
||||
if (_strandCollision.IsCreated) _strandCollision.Dispose();
|
||||
if (_clothCollisionCount.IsCreated) _clothCollisionCount.Dispose();
|
||||
if (_clothCollisionDelta.IsCreated) _clothCollisionDelta.Dispose();
|
||||
if (_forces.IsCreated) _forces.Dispose();
|
||||
|
||||
if (_warps.IsCreated) _warps.Dispose();
|
||||
|
||||
if (_clothUsedParticles.IsCreated) _clothUsedParticles.Dispose();
|
||||
if (_clothRects.IsCreated) _clothRects.Dispose();
|
||||
}
|
||||
|
||||
(int index, bool isNew) GetTransformIndex(Transform t,
|
||||
TransformInfo info,
|
||||
List<TransformInfo> infos,
|
||||
List<Vector3> positions)
|
||||
{
|
||||
Debug.Assert(t != null);
|
||||
var i = _transforms.IndexOf(t);
|
||||
if (i != -1)
|
||||
{
|
||||
return (i, false);
|
||||
}
|
||||
|
||||
i = _transforms.Count;
|
||||
_transforms.Add(t);
|
||||
infos.Add(info);
|
||||
positions.Add(t.position);
|
||||
return (i, true);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
|
||||
{
|
||||
_vrm = vrm;
|
||||
_building = true;
|
||||
|
||||
if (_onInit != null)
|
||||
{
|
||||
_onInit(vrm);
|
||||
_onInit = null;
|
||||
}
|
||||
|
||||
//
|
||||
// colliders
|
||||
//
|
||||
_colliderTransforms = new();
|
||||
List<BlittableCollider> colliders = new();
|
||||
foreach (var collider in vrm.GetComponentsInChildren<VRM10SpringBoneCollider>())
|
||||
{
|
||||
colliders.Add(new BlittableCollider
|
||||
{
|
||||
offset = collider.Offset,
|
||||
radius = collider.Radius,
|
||||
tailOrNormal = collider.TailOrNormal,
|
||||
colliderType = TranslateColliderType(collider.ColliderType)
|
||||
});
|
||||
_colliderTransforms.Add(collider.transform);
|
||||
}
|
||||
_colliderTransformAccessArray = new(_colliderTransforms.ToArray(), 128);
|
||||
_colliders = new(colliders.ToArray(), Allocator.Persistent);
|
||||
_currentColliders = new(_colliderTransforms.Count, Allocator.Persistent);
|
||||
|
||||
//
|
||||
// warps
|
||||
//
|
||||
_transforms = new();
|
||||
List<TransformInfo> info = new();
|
||||
List<Vector3> positions = new();
|
||||
List<WarpInfo> warps = new();
|
||||
var warpSrcs = vrm.GetComponentsInChildren<Components.ClothWarpRoot>();
|
||||
for (int warpIndex = 0; warpIndex < warpSrcs.Length; ++warpIndex)
|
||||
{
|
||||
var warp = warpSrcs[warpIndex];
|
||||
var start = _transforms.Count;
|
||||
|
||||
if (warp.Center != null)
|
||||
{
|
||||
GetTransformIndex(warp.Center, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.Center
|
||||
}, info, positions);
|
||||
start += 1;
|
||||
}
|
||||
|
||||
var warpRootParentTransformIndex = GetTransformIndex(warp.transform.parent, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.WarpRootParent
|
||||
}, info, positions);
|
||||
Debug.Assert(warpRootParentTransformIndex.index != -1);
|
||||
if (warpRootParentTransformIndex.isNew)
|
||||
{
|
||||
start += 1;
|
||||
}
|
||||
|
||||
var warpRootTransformIndex = GetTransformIndex(warp.transform, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.ClothWarp,
|
||||
ParentIndex = warpRootParentTransformIndex.index,
|
||||
InitLocalPosition = vrm.DefaultTransformStates[warp.transform].LocalPosition,
|
||||
InitLocalRotation = vrm.DefaultTransformStates[warp.transform].LocalRotation,
|
||||
}, info, positions);
|
||||
Debug.Assert(warpRootTransformIndex.index != -1);
|
||||
Debug.Assert(warpRootTransformIndex.isNew);
|
||||
|
||||
var parentIndex = warpRootTransformIndex.index;
|
||||
foreach (var particle in warp.Particles)
|
||||
{
|
||||
if (particle.Transform != null && particle.Mode != Components.ClothWarpRoot.ParticleMode.Disabled)
|
||||
{
|
||||
var outputParticleTransformIndex = GetTransformIndex((Transform)particle.Transform, new TransformInfo
|
||||
{
|
||||
TransformType = TransformType.Particle,
|
||||
ParentIndex = parentIndex,
|
||||
InitLocalPosition = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalPosition,
|
||||
InitLocalRotation = vrm.DefaultTransformStates[(Transform)particle.Transform].LocalRotation,
|
||||
Settings = particle.Settings,
|
||||
}, info, positions);
|
||||
parentIndex = outputParticleTransformIndex.index;
|
||||
}
|
||||
}
|
||||
|
||||
warps.Add(new WarpInfo
|
||||
{
|
||||
StartIndex = start,
|
||||
EndIndex = _transforms.Count,
|
||||
});
|
||||
|
||||
await awaitCaller.NextFrame();
|
||||
}
|
||||
_warps = new(warps.ToArray(), Allocator.Persistent);
|
||||
_transformAccessArray = new(_transforms.ToArray(), 128);
|
||||
_inputData = new(_transforms.Count, Allocator.Persistent);
|
||||
var pos = positions.ToArray();
|
||||
_currentPositions = new(pos, Allocator.Persistent);
|
||||
_prevPositions = new(pos, Allocator.Persistent);
|
||||
_nextPositions = new(pos.Length, Allocator.Persistent);
|
||||
_nextRotations = new(pos.Length, Allocator.Persistent);
|
||||
_info = new(info.ToArray(), Allocator.Persistent);
|
||||
_strandCollision = new(pos.Length, Allocator.Persistent);
|
||||
_clothCollisionCount = new(pos.Length, Allocator.Persistent);
|
||||
_clothCollisionDelta = new(pos.Length, Allocator.Persistent);
|
||||
_forces = new(pos.Length, Allocator.Persistent);
|
||||
|
||||
//
|
||||
// cloths
|
||||
//
|
||||
var clothRects = new ClothRectList(_transforms, vrm);
|
||||
_clothRects = new(clothRects.List.ToArray(), Allocator.Persistent);
|
||||
_clothUsedParticles = new(clothRects.ClothUsedParticles, Allocator.Persistent);
|
||||
_building = false;
|
||||
}
|
||||
|
||||
private static BlittableColliderType TranslateColliderType(VRM10SpringBoneColliderTypes colliderType)
|
||||
{
|
||||
switch (colliderType)
|
||||
{
|
||||
case VRM10SpringBoneColliderTypes.Sphere:
|
||||
return BlittableColliderType.Sphere;
|
||||
case VRM10SpringBoneColliderTypes.Capsule:
|
||||
return BlittableColliderType.Capsule;
|
||||
case VRM10SpringBoneColliderTypes.Plane:
|
||||
return BlittableColliderType.Plane;
|
||||
case VRM10SpringBoneColliderTypes.SphereInside:
|
||||
return BlittableColliderType.SphereInside;
|
||||
case VRM10SpringBoneColliderTypes.CapsuleInside:
|
||||
return BlittableColliderType.CapsuleInside;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Process()
|
||||
{
|
||||
Process(Time.deltaTime);
|
||||
}
|
||||
|
||||
public void Process(float deltaTime)
|
||||
{
|
||||
var frame = new FrameInfo(deltaTime, Vector3.zero);
|
||||
|
||||
JobHandle handle = default;
|
||||
|
||||
// input
|
||||
handle = new InputColliderJob
|
||||
{
|
||||
CurrentCollider = _currentColliders,
|
||||
}.Schedule(_colliderTransformAccessArray, handle);
|
||||
|
||||
handle = new InputTransformJob
|
||||
{
|
||||
Info = _info,
|
||||
InputData = _inputData,
|
||||
CurrentPositions = _currentPositions,
|
||||
Forces = _forces,
|
||||
|
||||
CollisionCount = _clothCollisionCount,
|
||||
CollisionDelta = _clothCollisionDelta,
|
||||
}.Schedule(_transformAccessArray, handle);
|
||||
|
||||
// spring(cloth weft)
|
||||
handle = new WeftConstraintJob
|
||||
{
|
||||
ClothRects = _clothRects,
|
||||
CurrentPositions = _currentPositions,
|
||||
|
||||
Force = _forces,
|
||||
}.Schedule(_clothRects.Length, 128, handle);
|
||||
|
||||
// verlet
|
||||
handle = new VerletJob
|
||||
{
|
||||
Frame = frame,
|
||||
Info = _info,
|
||||
CurrentTransforms = _inputData,
|
||||
PrevPositions = _prevPositions,
|
||||
CurrentPositions = _currentPositions,
|
||||
Forces = _forces,
|
||||
|
||||
NextPositions = _nextPositions,
|
||||
NextRotations = _nextRotations,
|
||||
}.Schedule(_info.Length, 128, handle);
|
||||
|
||||
// 親子の長さで拘束
|
||||
handle = new ParentLengthConstraintJob
|
||||
{
|
||||
Warps = _warps,
|
||||
Info = _info,
|
||||
NextPositions = _nextPositions,
|
||||
}.Schedule(_warps.Length, 16, handle);
|
||||
|
||||
// collision
|
||||
{
|
||||
var handle0 = new StrandCollisionJob
|
||||
{
|
||||
Colliders = _colliders,
|
||||
CurrentColliders = _currentColliders,
|
||||
|
||||
Info = _info,
|
||||
NextPositions = _nextPositions,
|
||||
ClothUsedParticles = _clothUsedParticles,
|
||||
StrandCollision = _strandCollision,
|
||||
}.Schedule(_info.Length, 128, handle);
|
||||
|
||||
var handle1 = new ClothCollisionJob
|
||||
{
|
||||
Colliders = _colliders,
|
||||
CurrentColliders = _currentColliders,
|
||||
|
||||
Info = _info,
|
||||
NextPositions = _nextPositions,
|
||||
CollisionCount = _clothCollisionCount,
|
||||
CollisionDelta = _clothCollisionDelta,
|
||||
|
||||
ClothRects = _clothRects,
|
||||
}.Schedule(_clothRects.Length, 128, handle);
|
||||
|
||||
handle = JobHandle.CombineDependencies(handle0, handle1);
|
||||
|
||||
handle = new CollisionApplyJob
|
||||
{
|
||||
ClothUsedParticles = _clothUsedParticles,
|
||||
StrandCollision = _strandCollision,
|
||||
ClothCollisionCount = _clothCollisionCount,
|
||||
ClothCollisionDelta = _clothCollisionDelta,
|
||||
NextPosition = _nextPositions,
|
||||
}.Schedule(_info.Length, 128, handle);
|
||||
}
|
||||
|
||||
// NextPositions から NextRotations を作る
|
||||
handle = new ApplyRotationJob
|
||||
{
|
||||
Warps = _warps,
|
||||
Info = _info,
|
||||
CurrentTransforms = _inputData,
|
||||
NextPositions = _nextPositions,
|
||||
NextRotations = _nextRotations,
|
||||
}.Schedule(_warps.Length, 16, handle);
|
||||
|
||||
// output
|
||||
handle = new OutputTransformJob
|
||||
{
|
||||
Info = _info,
|
||||
NextRotations = _nextRotations,
|
||||
}.Schedule(_transformAccessArray, handle);
|
||||
|
||||
handle.Complete();
|
||||
|
||||
// update state
|
||||
NativeArray<Vector3>.Copy(_currentPositions, _prevPositions);
|
||||
NativeArray<Vector3>.Copy(_nextPositions, _currentPositions);
|
||||
}
|
||||
|
||||
public void RestoreInitialTransform()
|
||||
{
|
||||
foreach (var warp in _warps)
|
||||
{
|
||||
for (int i = warp.StartIndex; i < warp.EndIndex; ++i)
|
||||
{
|
||||
var p = _info[i];
|
||||
var t = _transforms[i];
|
||||
switch (p.TransformType)
|
||||
{
|
||||
case TransformType.Particle:
|
||||
t.localRotation = _vrm.DefaultTransformStates[t].LocalRotation;
|
||||
_currentPositions[i] = t.position;
|
||||
_prevPositions[i] = t.position;
|
||||
_nextPositions[i] = t.position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGizmos()
|
||||
{
|
||||
for (int i = 0; i < _info.Length; ++i)
|
||||
{
|
||||
var info = _info[i];
|
||||
var v = _currentPositions[i];
|
||||
switch (info.TransformType)
|
||||
{
|
||||
case TransformType.Center:
|
||||
case TransformType.WarpRootParent:
|
||||
break;
|
||||
case TransformType.ClothWarp:
|
||||
Gizmos.color = Color.white;
|
||||
Gizmos.DrawSphere(v, info.Settings.radius);
|
||||
break;
|
||||
case TransformType.Particle:
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireSphere(v, info.Settings.radius);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SetJointLevel(Transform joint, BlittableJointMutable jointSettings)
|
||||
{
|
||||
var i = _transforms.IndexOf(joint);
|
||||
if (i != -1)
|
||||
{
|
||||
var info = _info[i];
|
||||
info.Settings = jointSettings;
|
||||
_info[i] = info;
|
||||
}
|
||||
}
|
||||
|
||||
public ClothWarpJobRuntime(Action<Vrm10Instance> onInit = null)
|
||||
{
|
||||
_onInit = onInit;
|
||||
}
|
||||
|
||||
public bool ReconstructSpringBone()
|
||||
{
|
||||
if (_vrm == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (_building)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var task = InitializeAsync(_vrm, new ImmediateCaller());
|
||||
return true;
|
||||
}
|
||||
|
||||
public void SetModelLevel(Transform modelRoot, BlittableModelLevel modelSettings)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e10706647b096be48917b33c33fbdefa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,319 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SphereTriangle;
|
||||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public struct InputColliderJob : IJobParallelForTransform
|
||||
{
|
||||
[WriteOnly] public NativeArray<Matrix4x4> CurrentCollider;
|
||||
|
||||
public void Execute(int colliderIndex, TransformAccess transform)
|
||||
{
|
||||
CurrentCollider[colliderIndex] = transform.localToWorldMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
public struct StrandCollisionJob : IJobParallelFor
|
||||
{
|
||||
// collider
|
||||
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
|
||||
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
|
||||
|
||||
// particle
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[ReadOnly] public NativeArray<Vector3> NextPositions;
|
||||
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
|
||||
[WriteOnly] public NativeArray<Vector3> StrandCollision;
|
||||
|
||||
public void Execute(int particleIndex)
|
||||
{
|
||||
if (!ClothUsedParticles[particleIndex])
|
||||
{
|
||||
var info = Info[particleIndex];
|
||||
var pos = NextPositions[particleIndex];
|
||||
for (int colliderIndex = 0; colliderIndex < Colliders.Length; ++colliderIndex)
|
||||
{
|
||||
var c = Colliders[colliderIndex];
|
||||
var m = CurrentColliders[colliderIndex];
|
||||
|
||||
if (c.colliderType == BlittableColliderType.Capsule)
|
||||
{
|
||||
if (TryCollideCapsuleAndSphere(m.MultiplyPoint(c.offset), m.MultiplyPoint(c.tailOrNormal), c.radius,
|
||||
pos, info.Settings.radius, out var l))
|
||||
{
|
||||
pos += l.GetDelta(c.radius);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryCollideSphereAndSphere(m.MultiplyPoint(c.offset), c.radius,
|
||||
pos, info.Settings.radius, out var l))
|
||||
{
|
||||
pos += l.GetDelta(c.radius);
|
||||
}
|
||||
}
|
||||
StrandCollision[particleIndex] = pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide sphere a and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="ra"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="ba"></param>
|
||||
/// <param name="resolved"></param>
|
||||
/// <returns></returns>
|
||||
static bool TryCollideSphereAndSphere(
|
||||
in Vector3 from, float ra,
|
||||
in Vector3 to, float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var d = Vector3.Distance(from, to);
|
||||
if (d > (ra + rb))
|
||||
{
|
||||
resolved = default;
|
||||
return false;
|
||||
}
|
||||
Vector3 normal = (to - from).normalized;
|
||||
resolved = new(from, from + normal * (d - rb));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide capsule and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="capsuleHead"></param>
|
||||
/// <param name="capsuleTail"></param>
|
||||
/// <param name="capsuleRadius"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="rb"></param>
|
||||
static bool TryCollideCapsuleAndSphere(
|
||||
in Vector3 capsuleHead,
|
||||
in Vector3 capsuleTail,
|
||||
float capsuleRadius,
|
||||
in Vector3 b,
|
||||
float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var P = (capsuleTail - capsuleHead).normalized;
|
||||
var Q = b - capsuleHead;
|
||||
var dot = Vector3.Dot(P, Q);
|
||||
if (dot <= 0)
|
||||
{
|
||||
// head側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
var t = dot / P.magnitude;
|
||||
if (t >= 1.0f)
|
||||
{
|
||||
// tail側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
// head-tail上の m_transform.position との最近点
|
||||
var p = capsuleHead + P * t;
|
||||
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ClothCollisionJob : IJobParallelFor
|
||||
{
|
||||
// collider
|
||||
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
|
||||
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
|
||||
|
||||
// particle
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[ReadOnly] public NativeArray<Vector3> NextPositions;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<int> CollisionCount;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Vector3> CollisionDelta;
|
||||
|
||||
// cloth
|
||||
[ReadOnly] public NativeArray<(SpringConstraint, ClothRect)> ClothRects;
|
||||
|
||||
private void CollisionMove(int particleIndex, Vector3 delta)
|
||||
{
|
||||
CollisionCount[particleIndex] += 1;
|
||||
CollisionDelta[particleIndex] += delta;
|
||||
}
|
||||
|
||||
public void Execute(int rectIndex)
|
||||
{
|
||||
var (spring, rect) = ClothRects[rectIndex];
|
||||
|
||||
// using (new ProfileSample("Rect: Prepare"))
|
||||
// _s0.BeginFrame();
|
||||
// _s1.BeginFrame();
|
||||
|
||||
var a = NextPositions[rect._a];
|
||||
var b = NextPositions[rect._b];
|
||||
var c = NextPositions[rect._c];
|
||||
var d = NextPositions[rect._d];
|
||||
var aabb = GetBoundsFrom4(a, b, c, d);
|
||||
|
||||
// d x-x c
|
||||
// |/
|
||||
// a x
|
||||
var _triangle1 = new Triangle(c, d, a);
|
||||
// x c
|
||||
// /|
|
||||
// a x-x b
|
||||
var _triangle0 = new Triangle(a, b, c);
|
||||
|
||||
for (int colliderIndex = 0; colliderIndex < Colliders.Length; ++colliderIndex)
|
||||
{
|
||||
var collider = Colliders[colliderIndex];
|
||||
var collider_matrix = CurrentColliders[colliderIndex];
|
||||
if (!aabb.Intersects(GetBounds(collider, collider_matrix)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 面の片側だけにヒットさせる
|
||||
// 行き過ぎて戻るときに素通りする
|
||||
// var p = _triangle0.Plane.ClosestPointOnPlane(col_pos);
|
||||
// var dot = Vector3.Dot(_triangle0.Plane.normal, col_pos - p);
|
||||
// if (_initialColliderNormalSide[collider] * dot < 0)
|
||||
// {
|
||||
// // 片側
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (TryCollide(collider, collider_matrix, _triangle0, out var l0))
|
||||
{
|
||||
CollisionMove(rect._a, l0.GetDelta(collider.radius));
|
||||
CollisionMove(rect._b, l0.GetDelta(collider.radius));
|
||||
CollisionMove(rect._c, l0.GetDelta(collider.radius));
|
||||
}
|
||||
if (TryCollide(collider, collider_matrix, _triangle1, out var l1))
|
||||
{
|
||||
CollisionMove(rect._c, l1.GetDelta(collider.radius));
|
||||
CollisionMove(rect._d, l1.GetDelta(collider.radius));
|
||||
CollisionMove(rect._a, l1.GetDelta(collider.radius));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool TryCollide(BlittableCollider collider, in Matrix4x4 colliderMatrix, in Triangle t, out LineSegment l)
|
||||
{
|
||||
var col_pos = colliderMatrix.MultiplyPoint(collider.offset);
|
||||
if (collider.colliderType == BlittableColliderType.Capsule)
|
||||
{
|
||||
// capsule
|
||||
var tail_pos = colliderMatrix.MultiplyPoint(collider.tailOrNormal);
|
||||
var result = TriangleCapsuleCollisionSolver.Collide(t, new LineSegment(col_pos, tail_pos), collider.radius);
|
||||
var type = result.TryGetClosest(out l);
|
||||
return type.HasValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// sphere
|
||||
return TryCollideSphere(t, col_pos, collider.radius, out l);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="triangle"></param>
|
||||
/// <param name="collider"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns>collider => 衝突点 への線分を返す</returns>
|
||||
static bool TryCollideSphere(in Triangle triangle, in Vector3 collider, float radius, out LineSegment l)
|
||||
{
|
||||
var p = triangle.Plane.ClosestPointOnPlane(collider);
|
||||
var distance = Vector3.Distance(p, collider);
|
||||
if (distance > radius)
|
||||
{
|
||||
l = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (triangle.IsSameSide(p))
|
||||
{
|
||||
l = new LineSegment(collider, p);
|
||||
return true;
|
||||
}
|
||||
|
||||
var (closestPoint, d) = triangle.GetClosest(collider);
|
||||
if (d > radius)
|
||||
{
|
||||
l = default;
|
||||
return false;
|
||||
}
|
||||
l = new LineSegment(collider, closestPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Bounds GetBoundsFrom4(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d)
|
||||
{
|
||||
var aabb = new Bounds(a, Vector3.zero);
|
||||
aabb.Encapsulate(b);
|
||||
aabb.Encapsulate(c);
|
||||
aabb.Encapsulate(d);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
public static Bounds GetBounds(BlittableCollider collider, Matrix4x4 m)
|
||||
{
|
||||
switch (collider.colliderType)
|
||||
{
|
||||
case BlittableColliderType.Capsule:
|
||||
{
|
||||
var h = m.MultiplyPoint(collider.offset);
|
||||
var t = m.MultiplyPoint(collider.tailOrNormal);
|
||||
var d = h - t;
|
||||
var aabb = new Bounds((h + t) * 0.5f, new Vector3(Mathf.Abs(d.x), Mathf.Abs(d.y), Mathf.Abs(d.z)));
|
||||
aabb.Expand(collider.radius * 2);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
case BlittableColliderType.Sphere:
|
||||
return new Bounds(m.MultiplyPoint(collider.offset), new Vector3(collider.radius, collider.radius, collider.radius));
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct CollisionApplyJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
|
||||
[ReadOnly] public NativeArray<Vector3> StrandCollision;
|
||||
[ReadOnly] public NativeArray<int> ClothCollisionCount;
|
||||
[ReadOnly] public NativeArray<Vector3> ClothCollisionDelta;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPosition;
|
||||
|
||||
public void Execute(int particleIndex)
|
||||
{
|
||||
if (ClothUsedParticles[particleIndex])
|
||||
{
|
||||
if (ClothCollisionCount[particleIndex] > 0)
|
||||
{
|
||||
NextPosition[particleIndex] += (ClothCollisionDelta[particleIndex] / ClothCollisionCount[particleIndex]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
NextPosition[particleIndex] = StrandCollision[particleIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 874e448619fc4d54f900f52f83708a6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using SphereTriangle;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
/// <summary>
|
||||
/// 親の位置に依存。再帰
|
||||
/// </summary>
|
||||
public struct ParentLengthConstraintJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<WarpInfo> Warps;
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPositions;
|
||||
public void Execute(int warpIndex)
|
||||
{
|
||||
var warp = Warps[warpIndex];
|
||||
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
|
||||
{
|
||||
// 位置を長さで拘束
|
||||
NextPositions[particleIndex + 1] = NextPositions[particleIndex] +
|
||||
(NextPositions[particleIndex + 1] - NextPositions[particleIndex]).normalized
|
||||
* Info[particleIndex + 1].InitLocalPosition.magnitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct WeftConstraintJob : IJobParallelFor
|
||||
{
|
||||
public float Hookean;
|
||||
FrameInfo Frame;
|
||||
[ReadOnly] public NativeArray<(SpringConstraint, ClothRect)> ClothRects;
|
||||
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Vector3> Force;
|
||||
|
||||
public void Execute(int rectIndex)
|
||||
{
|
||||
var (spring, rect) = ClothRects[rectIndex];
|
||||
var p0 = CurrentPositions[spring._p0];
|
||||
var p1 = CurrentPositions[spring._p1];
|
||||
var d = Vector3.Distance(p0, p1);
|
||||
var f = (d - spring._rest) * Hookean;
|
||||
var dx = (p1 - p0).normalized * f / Frame.SqDeltaTime;
|
||||
Force[spring._p0] += dx;
|
||||
Force[spring._p1] -= dx;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 親の回転に依存。再帰
|
||||
/// </summary>
|
||||
public struct ApplyRotationJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<WarpInfo> Warps;
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[ReadOnly] public NativeArray<TransformData> CurrentTransforms;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPositions;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<Quaternion> NextRotations;
|
||||
public void Execute(int warpIndex)
|
||||
{
|
||||
var warp = Warps[warpIndex];
|
||||
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
|
||||
{
|
||||
//回転を適用
|
||||
var p = Info[particleIndex];
|
||||
var rotation = NextRotations[p.ParentIndex] * Info[particleIndex].InitLocalRotation;
|
||||
NextRotations[particleIndex] = Quaternion.FromToRotation(
|
||||
rotation * Info[particleIndex + 1].InitLocalPosition,
|
||||
NextPositions[particleIndex + 1] - NextPositions[particleIndex]) * rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 658703b04bb98bc438094be1f7959d73
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public struct FrameInfo
|
||||
{
|
||||
public readonly float DeltaTime;
|
||||
public readonly float SqDeltaTime;
|
||||
public readonly Vector3 Force; // += env.External / time.DeltaTime;
|
||||
|
||||
public FrameInfo(float deltaTime, Vector3 force)
|
||||
{
|
||||
DeltaTime = deltaTime;
|
||||
SqDeltaTime = deltaTime * deltaTime;
|
||||
Force = force;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ea8f33e691eb01f4ca5377804f9ce333
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
using UniGLTF.SpringBoneJobs.Blittables;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Jobs;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public struct TransformInfo
|
||||
{
|
||||
public TransformType TransformType;
|
||||
public int ParentIndex;
|
||||
public Quaternion InitLocalRotation;
|
||||
public Vector3 InitLocalPosition;
|
||||
public BlittableJointMutable Settings;
|
||||
}
|
||||
|
||||
public struct TransformData
|
||||
{
|
||||
public Matrix4x4 ToWorld;
|
||||
public Vector3 Position => ToWorld.GetPosition();
|
||||
public Quaternion Rotation => ToWorld.rotation;
|
||||
public Matrix4x4 ToLocal;
|
||||
|
||||
public TransformData(TransformAccess t)
|
||||
{
|
||||
ToWorld = t.localToWorldMatrix;
|
||||
ToLocal = t.worldToLocalMatrix;
|
||||
}
|
||||
public TransformData(Transform t)
|
||||
{
|
||||
ToWorld = t.localToWorldMatrix;
|
||||
ToLocal = t.worldToLocalMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
// [Input]
|
||||
public struct InputTransformJob : IJobParallelForTransform
|
||||
{
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[WriteOnly] public NativeArray<TransformData> InputData;
|
||||
[WriteOnly] public NativeArray<Vector3> CurrentPositions;
|
||||
|
||||
[WriteOnly] public NativeArray<int> CollisionCount;
|
||||
[WriteOnly] public NativeArray<Vector3> CollisionDelta;
|
||||
[WriteOnly] public NativeArray<Vector3> Forces;
|
||||
|
||||
public void Execute(int particleIndex, TransformAccess transform)
|
||||
{
|
||||
InputData[particleIndex] = new TransformData(transform);
|
||||
|
||||
var particle = Info[particleIndex];
|
||||
if (particle.TransformType.PositionInput())
|
||||
{
|
||||
// only warp root position update
|
||||
CurrentPositions[particleIndex] = transform.position;
|
||||
}
|
||||
|
||||
// clear cloth
|
||||
CollisionCount[particleIndex] = 0;
|
||||
CollisionDelta[particleIndex] = Vector3.zero;
|
||||
Forces[particleIndex] = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
public struct VerletJob : IJobParallelFor
|
||||
{
|
||||
public FrameInfo Frame;
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[ReadOnly] public NativeArray<TransformData> CurrentTransforms;
|
||||
[ReadOnly] public NativeArray<Vector3> Forces;
|
||||
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
|
||||
[ReadOnly] public NativeArray<Vector3> PrevPositions;
|
||||
[WriteOnly] public NativeArray<Vector3> NextPositions;
|
||||
[WriteOnly] public NativeArray<Quaternion> NextRotations;
|
||||
|
||||
public void Execute(int particleIndex)
|
||||
{
|
||||
var particle = Info[particleIndex];
|
||||
if (particle.TransformType.Movable())
|
||||
{
|
||||
var parentIndex = particle.ParentIndex;
|
||||
// var parentPosition = CurrentPositions[parentIndex];
|
||||
var parent = Info[parentIndex];
|
||||
var parentParentRotation = CurrentTransforms[parent.ParentIndex].Rotation;
|
||||
|
||||
var external = (particle.Settings.gravityDir * particle.Settings.gravityPower + Frame.Force) * Frame.DeltaTime;
|
||||
|
||||
var newPosition = CurrentPositions[particleIndex]
|
||||
+ (CurrentPositions[particleIndex] - PrevPositions[particleIndex]) * (1.0f - particle.Settings.dragForce)
|
||||
+ parentParentRotation * parent.InitLocalRotation * particle.InitLocalPosition *
|
||||
particle.Settings.stiffnessForce * Frame.DeltaTime // 親の回転による子ボーンの移動目標
|
||||
+ external
|
||||
;
|
||||
|
||||
NextPositions[particleIndex] = newPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
// kinematic
|
||||
NextPositions[particleIndex] = CurrentPositions[particleIndex];
|
||||
}
|
||||
|
||||
NextRotations[particleIndex] = CurrentTransforms[particleIndex].Rotation;
|
||||
}
|
||||
}
|
||||
|
||||
// [Output]
|
||||
public struct OutputTransformJob : IJobParallelForTransform
|
||||
{
|
||||
[ReadOnly] public NativeArray<TransformInfo> Info;
|
||||
[ReadOnly] public NativeArray<Quaternion> NextRotations;
|
||||
public void Execute(int particleIndex, TransformAccess transform)
|
||||
{
|
||||
var info = Info[particleIndex];
|
||||
if (info.TransformType.Writable())
|
||||
{
|
||||
transform.rotation = NextRotations[particleIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 69453c7639ad8df4c94cbe3dae50e72c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public enum TransformType
|
||||
{
|
||||
Center,
|
||||
WarpRootParent,
|
||||
ClothWarp,
|
||||
Particle,
|
||||
}
|
||||
|
||||
public static class TransformTypeExtensions
|
||||
{
|
||||
public static bool PositionInput(this TransformType t)
|
||||
{
|
||||
return t == TransformType.ClothWarp;
|
||||
}
|
||||
|
||||
public static bool Movable(this TransformType t)
|
||||
{
|
||||
return t == TransformType.Particle;
|
||||
}
|
||||
|
||||
public static bool Writable(this TransformType t)
|
||||
{
|
||||
switch (t)
|
||||
{
|
||||
case TransformType.ClothWarp:
|
||||
case TransformType.Particle:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 56860caf8e0babc41807521ed55a4f20
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace UniVRM10.ClothWarp.Jobs
|
||||
{
|
||||
public struct WarpInfo
|
||||
{
|
||||
public int StartIndex;
|
||||
public int EndIndex;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 139cb8a384ad8c64abc4dcfab37bcaca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
[Serializable]
|
||||
public struct ParticleInitState
|
||||
{
|
||||
public readonly int Index;
|
||||
|
||||
[SerializeField]
|
||||
public Vector3 LocalPosition;
|
||||
[SerializeField]
|
||||
public Quaternion LocalRotation;
|
||||
|
||||
[SerializeField]
|
||||
public Vector3 BoneAxis;
|
||||
|
||||
[SerializeField]
|
||||
public float StrandLength;
|
||||
|
||||
[SerializeField]
|
||||
public float Radius;
|
||||
|
||||
// 0 は移動しない固定(回転はしてもよい)
|
||||
// TODO: force / mass => accelaration
|
||||
public float Mass;
|
||||
|
||||
public ParticleInitState(int index, Transform t, float radius, float mass)
|
||||
{
|
||||
Index = index;
|
||||
LocalPosition = t.localPosition;
|
||||
LocalRotation = t.localRotation;
|
||||
StrandLength = LocalPosition.magnitude;
|
||||
BoneAxis = LocalPosition.normalized;
|
||||
Radius = radius;
|
||||
Mass = mass;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"create Particle: {LocalPosition}, {BoneAxis}, {StrandLength}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4bccf867d0bc7b244841c8ecff9ce65a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
[Serializable]
|
||||
public class ParticleList
|
||||
{
|
||||
public List<ClothWarpNode> _particles = new();
|
||||
public List<Transform> _particleTransforms = new();
|
||||
|
||||
public Strand MakeParticleStrand(SimulationEnv env, Components.ClothWarpRoot warp)
|
||||
{
|
||||
var strand = new Strand();
|
||||
|
||||
// root kinematic
|
||||
var particle_index = _MakeAParticle(null, env, warp.transform, 0, 0);
|
||||
var joint = _particles[particle_index];
|
||||
strand.Particles.Add(joint);
|
||||
|
||||
foreach (var particle in warp.Particles)
|
||||
{
|
||||
var child_index = _MakeAParticle(joint, env, particle.Transform,
|
||||
particle.Settings.radius, 1);
|
||||
var child = _particles[child_index];
|
||||
strand.Particles.Add(child);
|
||||
joint.Children.Add(child);
|
||||
joint = child;
|
||||
}
|
||||
|
||||
return strand;
|
||||
}
|
||||
|
||||
int _MakeAParticle(ClothWarpNode parent, SimulationEnv env, Transform t, float radius, float mass)
|
||||
{
|
||||
var index = _particles.Count;
|
||||
_particleTransforms.Add(t);
|
||||
_particles.Add(new ClothWarpNode(index, parent, env, t, radius, mass));
|
||||
return index;
|
||||
}
|
||||
|
||||
public void EndInitialize(SphereTriangle.InitPosition initPos)
|
||||
{
|
||||
for (int i = 0; i < _particles.Count; ++i)
|
||||
{
|
||||
initPos(i, _particles[i].Init.Mass, _particleTransforms[i].position);
|
||||
}
|
||||
}
|
||||
|
||||
public void BeginFrame(SimulationEnv env, FrameTime time, IReadOnlyList<Vector3> restPositions)
|
||||
{
|
||||
for (int i = 0; i < _particles.Count; ++i)
|
||||
{
|
||||
var p = _particles[i];
|
||||
if (p.Init.Mass <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rest = restPositions[p.Init.Index];
|
||||
p.BeginFrame(env, time, rest);
|
||||
}
|
||||
}
|
||||
|
||||
public void Verlet(SimulationEnv env, FrameTime time, SphereTriangle.InitPosition initPos)
|
||||
{
|
||||
for (int i = 0; i < _particles.Count; ++i)
|
||||
{
|
||||
var p = _particles[i];
|
||||
if (p.Init.Mass > 0)
|
||||
{
|
||||
var newPos = p.Verlet(env, time);
|
||||
initPos(i, p.Init.Mass, newPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGizmos()
|
||||
{
|
||||
foreach (var p in _particles)
|
||||
{
|
||||
p.OnDrawGizmos(_particleTransforms[p.Init.Index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fee618dfb5846ea47978b49ed4c0282b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
public class ParticleRuntimeState
|
||||
{
|
||||
public Vector3 Current;
|
||||
public Vector3 Prev;
|
||||
|
||||
public ParticleRuntimeState(SimulationEnv env, Transform transform)
|
||||
{
|
||||
if (env.Center == null)
|
||||
{
|
||||
Prev = transform.position;
|
||||
Current = transform.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(Transform t)
|
||||
{
|
||||
Current = t.position;
|
||||
}
|
||||
|
||||
public void Apply(in Vector3 newPos, bool zeroVelocity = false)
|
||||
{
|
||||
if (zeroVelocity)
|
||||
{
|
||||
Prev = newPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
Prev = Current;
|
||||
}
|
||||
Current = newPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: eddc8c1b804734c49a6eb04d72afb0a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
public struct RigidTransform
|
||||
{
|
||||
public readonly Vector3 Position;
|
||||
public readonly Quaternion Rotation;
|
||||
|
||||
public RigidTransform(in Vector3 pos, in Quaternion rot)
|
||||
{
|
||||
Position = pos;
|
||||
Rotation = rot;
|
||||
}
|
||||
|
||||
public RigidTransform(Transform t)
|
||||
{
|
||||
Position = t.position;
|
||||
Rotation = t.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1fe036d9e9352e54ea009e8322a4faa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace UniVRM10.ClothWarp
|
||||
{
|
||||
[System.Serializable]
|
||||
public class SimulationEnv
|
||||
{
|
||||
public Transform Center;
|
||||
|
||||
[Range(0, 1)]
|
||||
public float Stiffness = 0.1f;
|
||||
|
||||
[Range(0, 1)]
|
||||
public float DragForce = 0.4f;
|
||||
|
||||
public Vector3 External = new Vector3(0, -0.001f, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c9c034f40b6adaf4ba23f456fd5a2ec1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 36a9770569fe64c4f98dbe384596e4b2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public struct ClothRect
|
||||
{
|
||||
// 2枚の三角形
|
||||
// abc
|
||||
// cda
|
||||
// に対する衝突(球 or カプセル)を管理する
|
||||
public readonly int _a;
|
||||
public readonly int _b;
|
||||
public readonly int _c;
|
||||
public readonly int _d;
|
||||
|
||||
/// <summary>
|
||||
/// two triangles
|
||||
/// d x-x c
|
||||
/// |/|
|
||||
/// a x-x b
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <param name="d"></param>
|
||||
public ClothRect(
|
||||
int a, int b, int c, int d)
|
||||
{
|
||||
_a = a;
|
||||
_b = b;
|
||||
_c = c;
|
||||
_d = d;
|
||||
}
|
||||
}
|
||||
|
||||
public class ClothRectCollision
|
||||
{
|
||||
Triangle _triangle0;
|
||||
float _trinagle0Collision;
|
||||
Triangle _triangle1;
|
||||
float _triangle1Collision;
|
||||
|
||||
// TriangleCapsuleCollisionSolver _s0 = new();
|
||||
// TriangleCapsuleCollisionSolver _s1 = new();
|
||||
|
||||
// 各コライダーが初期姿勢で三角形ABCの法線の正か負のどちらにあるのかを記録する
|
||||
Dictionary<VRM10SpringBoneCollider, float> _initialColliderNormalSide = new();
|
||||
|
||||
public void InitializeColliderSide(PositionList list, IReadOnlyList<VRM10SpringBoneColliderGroup> colliderGroups, ClothRect _rect)
|
||||
{
|
||||
var a = list.Get(_rect._a);
|
||||
var b = list.Get(_rect._b);
|
||||
var c = list.Get(_rect._c);
|
||||
var d = list.Get(_rect._d);
|
||||
|
||||
// x c
|
||||
// /|
|
||||
// a x-x b
|
||||
var t = new Triangle(a, b, c);
|
||||
|
||||
foreach (var g in colliderGroups)
|
||||
{
|
||||
foreach (var collider in g.Colliders)
|
||||
{
|
||||
var headWorldPosition = collider.transform.TransformPoint(collider.Offset);
|
||||
var p = t.Plane.ClosestPointOnPlane(headWorldPosition);
|
||||
var dot = Vector3.Dot(t.Plane.normal, headWorldPosition - p);
|
||||
_initialColliderNormalSide[collider] = dot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Collide(PositionList list, IList<VRM10SpringBoneCollider> colliders, ClothRect _rect)
|
||||
{
|
||||
// using (new ProfileSample("Rect: Prepare"))
|
||||
{
|
||||
// _s0.BeginFrame();
|
||||
// _s1.BeginFrame();
|
||||
|
||||
var a = list.Get(_rect._a);
|
||||
var b = list.Get(_rect._b);
|
||||
var c = list.Get(_rect._c);
|
||||
var d = list.Get(_rect._d);
|
||||
|
||||
// d x-x c
|
||||
// |/
|
||||
// a x
|
||||
_triangle1 = new Triangle(c, d, a);
|
||||
_triangle1Collision -= 0.1f;
|
||||
if (_triangle1Collision < 0)
|
||||
{
|
||||
_triangle1Collision = 0;
|
||||
}
|
||||
// x c
|
||||
// /|
|
||||
// a x-x b
|
||||
_triangle0 = new Triangle(a, b, c);
|
||||
_trinagle0Collision -= 0.1f;
|
||||
if (_trinagle0Collision < 0)
|
||||
{
|
||||
_trinagle0Collision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// using (new ProfileSample("Rect: Collide"))
|
||||
{
|
||||
var aabb = list.GetBounds(_rect);
|
||||
|
||||
for (int i = 0; i < colliders.Count; ++i)
|
||||
{
|
||||
var collider = colliders[i];
|
||||
// using (new ProfileSample("EaryOut"))
|
||||
{
|
||||
if (!aabb.Intersects(GetBounds(collider)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var headWorldPosition = collider.transform.TransformPoint(collider.Offset);
|
||||
var p = _triangle0.Plane.ClosestPointOnPlane(headWorldPosition);
|
||||
var dot = Vector3.Dot(_triangle0.Plane.normal, headWorldPosition - p);
|
||||
if (_initialColliderNormalSide[collider] * dot < 0)
|
||||
{
|
||||
// 片側
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCollide(collider, _triangle0, out var l0))
|
||||
{
|
||||
_trinagle0Collision = 1.0f;
|
||||
list.CollisionMove(_rect._a, l0, collider.Radius);
|
||||
list.CollisionMove(_rect._b, l0, collider.Radius);
|
||||
list.CollisionMove(_rect._c, l0, collider.Radius);
|
||||
}
|
||||
if (TryCollide(collider, _triangle1, out var l1))
|
||||
{
|
||||
_triangle1Collision = 1.0f;
|
||||
list.CollisionMove(_rect._c, l1, collider.Radius);
|
||||
list.CollisionMove(_rect._d, l1, collider.Radius);
|
||||
list.CollisionMove(_rect._a, l1, collider.Radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Bounds GetBounds(VRM10SpringBoneCollider c)
|
||||
{
|
||||
var headWorldPosition = c.transform.TransformPoint(c.Offset);
|
||||
switch (c.ColliderType)
|
||||
{
|
||||
case VRM10SpringBoneColliderTypes.Capsule:
|
||||
{
|
||||
var h = headWorldPosition;
|
||||
var t = c.transform.TransformPoint(c.TailOrNormal);
|
||||
var d = h - t;
|
||||
var aabb = new Bounds((h + t) * 0.5f, new Vector3(Mathf.Abs(d.x), Mathf.Abs(d.y), Mathf.Abs(d.z)));
|
||||
aabb.Expand(c.Radius * 2);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
case VRM10SpringBoneColliderTypes.Sphere:
|
||||
return new Bounds(headWorldPosition, new Vector3(c.Radius, c.Radius, c.Radius));
|
||||
|
||||
default:
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 衝突して移動デルタを得る
|
||||
/// </summary>
|
||||
/// <param name="collider"></param>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="l"></param>
|
||||
/// <returns></returns>
|
||||
static bool TryCollide(VRM10SpringBoneCollider collider, in Triangle t, out LineSegment l)
|
||||
{
|
||||
var headWorldPosition = collider.transform.TransformPoint(collider.Offset);
|
||||
if (collider.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
|
||||
{
|
||||
// capsule
|
||||
var tailWorldPosition = collider.transform.TransformPoint(collider.TailOrNormal);
|
||||
var result = TriangleCapsuleCollisionSolver.Collide(t, new(headWorldPosition, tailWorldPosition), collider.Radius);
|
||||
var type = result.TryGetClosest(out l);
|
||||
return type.HasValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// sphere
|
||||
// using var profile = new ProfileSample("Sphere");
|
||||
return TryCollideSphere(t, headWorldPosition, collider.Radius, out l);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="triangle"></param>
|
||||
/// <param name="collider"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns>collider => 衝突点 への線分を返す</returns>
|
||||
static bool TryCollideSphere(in Triangle triangle, in Vector3 collider, float radius, out LineSegment l)
|
||||
{
|
||||
var p = triangle.Plane.ClosestPointOnPlane(collider);
|
||||
var distance = Vector3.Distance(p, collider);
|
||||
if (distance > radius)
|
||||
{
|
||||
l = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (triangle.IsSameSide(p))
|
||||
{
|
||||
l = new LineSegment(collider, p);
|
||||
return true;
|
||||
}
|
||||
|
||||
var (closestPoint, d) = triangle.GetClosest(collider);
|
||||
if (d > radius)
|
||||
{
|
||||
l = default;
|
||||
return false;
|
||||
}
|
||||
l = new LineSegment(collider, closestPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
// public void DrawGizmos()
|
||||
// {
|
||||
// var r = Vector3.Distance(_triangle0.b, _triangle0.c) * 0.1f;
|
||||
// _DrawGizmos(_triangle0, _s0, _trinagle0Collision, r);
|
||||
// _DrawGizmos(_triangle1, _s1, _triangle1Collision, r);
|
||||
|
||||
// #if AABB_DEBUG
|
||||
// Gizmos.matrix = Matrix4x4.identity;
|
||||
// Gizmos.color = Color.cyan;
|
||||
// var aabb = GetBoundsFrom4(_triangle0.a, _triangle0.b, _triangle1.a, _triangle1.b);
|
||||
// Gizmos.DrawWireCube(aabb.center, aabb.size);
|
||||
// #endif
|
||||
// }
|
||||
|
||||
// void _DrawGizmos(in Triangle t, TriangleCapsuleCollisionSolver solver, float collision, float radius)
|
||||
// {
|
||||
// solver.DrawGizmos(t, collision, radius);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c16dea6c47fa81d4184547a209d8fe61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class FrameRate : MonoBehaviour
|
||||
{
|
||||
[Range(15, 200)]
|
||||
public int _FrameRate = 30;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
Application.targetFrameRate = _FrameRate;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 39108f27e89d7514d86e56e2ffb0550c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
using UnityEngine;
|
||||
using UniVRM10;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public class LineDistanceGizmo : MonoBehaviour
|
||||
{
|
||||
public VRM10SpringBoneCollider LineA;
|
||||
public VRM10SpringBoneCollider LineB;
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (LineA == null)
|
||||
{
|
||||
LineA = GetComponent<VRM10SpringBoneCollider>();
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDrawGizmos()
|
||||
{
|
||||
if (LineA.ColliderType == VRM10SpringBoneColliderTypes.Capsule && LineB.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
|
||||
{
|
||||
var a = new LineSegment(LineA.transform.TransformPoint(LineA.Offset), LineA.transform.TransformPoint(LineA.TailOrNormal));
|
||||
var b = new LineSegment(LineB.transform.TransformPoint(LineB.Offset), LineB.transform.TransformPoint(LineB.TailOrNormal));
|
||||
var (s, t) = LineSegment.CalcClosest(a, b);
|
||||
var a_s = a.GetPoint(s);
|
||||
var b_t = b.GetPoint(t);
|
||||
Gizmos.color = Color.magenta;
|
||||
Gizmos.DrawLine(a_s, b_t);
|
||||
|
||||
Gizmos.color = Color.gray;
|
||||
if (s < 0)
|
||||
{
|
||||
Gizmos.DrawLine(LineA.transform.TransformPoint(LineA.Offset), a_s);
|
||||
}
|
||||
else if (s > 1)
|
||||
{
|
||||
Gizmos.DrawLine(LineA.transform.TransformPoint(LineA.TailOrNormal), a_s);
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.DrawWireSphere(a_s, 0.05f);
|
||||
}
|
||||
if (t < 0)
|
||||
{
|
||||
Gizmos.DrawLine(LineB.transform.TransformPoint(LineB.Offset), b_t);
|
||||
}
|
||||
else if (t > 1)
|
||||
{
|
||||
Gizmos.DrawLine(LineB.transform.TransformPoint(LineB.TailOrNormal), b_t);
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.DrawWireSphere(b_t, 0.05f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: cceeecd3d3db5cd4ba74dfa6490e38b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
using UnityEngine;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public readonly struct LineSegment
|
||||
{
|
||||
public readonly Vector3 Start;
|
||||
public readonly Vector3 End;
|
||||
|
||||
public Vector3 Vector => End - Start;
|
||||
public Ray Ray => new Ray(Start, Vector);
|
||||
public float Length => Vector.magnitude;
|
||||
public float SqLength => Vector.sqrMagnitude;
|
||||
|
||||
public LineSegment(in Vector3 start, in Vector3 end) => (Start, End) = (start, end);
|
||||
|
||||
public Vector3 GetPoint(float t)
|
||||
{
|
||||
return Start + Vector * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 球との衝突時の移動ベクトル
|
||||
/// Start が球の中心。End 衝突点。
|
||||
/// </summary>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns></returns>
|
||||
public Vector3 GetDelta(float radius)
|
||||
{
|
||||
return Vector.normalized * (radius - Length);
|
||||
}
|
||||
|
||||
public float Project(in Vector3 p)
|
||||
{
|
||||
var dir = p - Start;
|
||||
return Vector3.Dot(Vector.normalized, dir) / dir.magnitude;
|
||||
}
|
||||
|
||||
// P(s)
|
||||
// Q(t)
|
||||
public static (float s, float t) CalcClosest(in LineSegment p, in LineSegment q)
|
||||
{
|
||||
var d_p2 = Vector3.Dot(p.Vector, p.Vector);
|
||||
var d_q2 = Vector3.Dot(q.Vector, q.Vector);
|
||||
var d_pq = Vector3.Dot(p.Vector, q.Vector);
|
||||
var d_q_p_p = Vector3.Dot(q.Start - p.Start, p.Vector);
|
||||
var d_p_q_q = Vector3.Dot(p.Start - q.Start, q.Vector);
|
||||
var denom = d_p2 * d_q2 - d_pq * d_pq;
|
||||
if (denom < 1e-4)
|
||||
{
|
||||
var pq = q.Start - p.Start;
|
||||
var d = Vector3.Dot(p.Vector, pq);
|
||||
var s = d / p.Length;
|
||||
return (s, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var f = 1 / denom;
|
||||
var s = d_q2 * d_q_p_p + d_pq * d_p_q_q;
|
||||
var t = d_pq * d_q_p_p + d_p2 * d_p_q_q;
|
||||
return (f * s, f * t);
|
||||
}
|
||||
}
|
||||
|
||||
// public static bool Intersect(in LineSegment p, in LineSegment q, out float s, out float t)
|
||||
// {
|
||||
// (s, t) = CalcClosest(p, q);
|
||||
// if (s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0)
|
||||
// {
|
||||
// if (Vector3.Distance(p.GetPoint(s), q.GetPoint(t)) < 1e4f)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
public bool TryClampPlaneDistance(in Plane p, float distance, out LineSegment clamped, out Vector3 o)
|
||||
{
|
||||
var ray = Ray;
|
||||
var hit = p.Raycast(ray, out var t);
|
||||
if (!hit && t == 0)
|
||||
{
|
||||
// 平行
|
||||
clamped = default;
|
||||
o = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
o = ray.GetPoint(t);
|
||||
var vs = Start - o;
|
||||
var ve = End - o;
|
||||
|
||||
var ds = p.GetDistanceToPoint(Start);
|
||||
var de = p.GetDistanceToPoint(End);
|
||||
var ts = 1.0f;
|
||||
var te = 1.0f;
|
||||
if (ds > 0)
|
||||
{
|
||||
if (de > 0)
|
||||
{
|
||||
//+s
|
||||
//+e
|
||||
//==
|
||||
if (ds > distance && de > distance)
|
||||
{
|
||||
clamped = default;
|
||||
return false;
|
||||
}
|
||||
ts = Mathf.Min(distance, ds) / ds;
|
||||
te = Mathf.Min(distance, de) / de;
|
||||
}
|
||||
else
|
||||
{
|
||||
//+s
|
||||
//==
|
||||
//-e
|
||||
ts = Mathf.Min(distance, ds) / ds;
|
||||
te = Mathf.Max(-distance, de) / de;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (de < 0)
|
||||
{
|
||||
//==
|
||||
//-s
|
||||
//-e
|
||||
if (ds < -distance && de < -distance)
|
||||
{
|
||||
clamped = default;
|
||||
return false;
|
||||
}
|
||||
ts = Mathf.Max(-distance, ds) / ds;
|
||||
te = Mathf.Max(-distance, de) / de;
|
||||
}
|
||||
else
|
||||
{
|
||||
//+e
|
||||
//==
|
||||
//-s
|
||||
ts = Mathf.Max(-distance, ds) / ds;
|
||||
te = Mathf.Min(distance, de) / de;
|
||||
}
|
||||
}
|
||||
|
||||
var cs = new Ray(o, vs).GetPoint(vs.magnitude * ts);
|
||||
var ce = new Ray(o, ve).GetPoint(ve.magnitude * te);
|
||||
clamped = new(cs, ce);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DrawGizmos(float radius = 0.01f)
|
||||
{
|
||||
Gizmos.DrawLine(Start, End);
|
||||
Gizmos.DrawWireSphere(Start, radius);
|
||||
Gizmos.DrawWireSphere(End, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: add9de55aee3f344cab64a1c99c7c6aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public delegate void InitPosition(int index, float mass, in Vector3 position);
|
||||
|
||||
public class PositionList
|
||||
{
|
||||
// 初期状態
|
||||
// 質点の重さ(0は移動しない)
|
||||
float[] Mass;
|
||||
|
||||
// 衝突前
|
||||
Vector3[] Positions;
|
||||
public int[] CollisionCount;
|
||||
|
||||
// 衝突による移動距離
|
||||
public Vector3[] Delta;
|
||||
// Positions に Delta を反映した結果
|
||||
public Vector3[] Result;
|
||||
|
||||
public PositionList(int count)
|
||||
{
|
||||
Positions = new Vector3[count];
|
||||
CollisionCount = new int[count];
|
||||
Mass = new float[count];
|
||||
Delta = new Vector3[count];
|
||||
Result = new Vector3[count];
|
||||
}
|
||||
|
||||
public Dictionary<ClothRect, Bounds> BoundsCache = new();
|
||||
|
||||
public static Bounds GetBoundsFrom4(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d)
|
||||
{
|
||||
var aabb = new Bounds(a, Vector3.zero);
|
||||
aabb.Encapsulate(b);
|
||||
aabb.Encapsulate(c);
|
||||
aabb.Encapsulate(d);
|
||||
return aabb;
|
||||
}
|
||||
|
||||
public Bounds GetBounds(ClothRect rect)
|
||||
{
|
||||
if (BoundsCache.TryGetValue(rect, out var b))
|
||||
{
|
||||
return b;
|
||||
}
|
||||
else
|
||||
{
|
||||
b = GetBoundsFrom4(Get(rect._a), Get(rect._b), Get(rect._c), Get(rect._d));
|
||||
BoundsCache.Add(rect, b);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 Get(int index)
|
||||
{
|
||||
return Positions[index];
|
||||
}
|
||||
|
||||
public void Init(int index, float mass, in Vector3 pos)
|
||||
{
|
||||
Mass[index] = mass;
|
||||
Positions[index] = pos;
|
||||
CollisionCount[index] = 0;
|
||||
Delta[index] = Vector3.zero;
|
||||
}
|
||||
|
||||
public void EndInitialize()
|
||||
{
|
||||
// Buffer.BlockCopy(Positions, 0, Result, 0, Positions.Length);
|
||||
for (int i = 0; i < Positions.Length; ++i)
|
||||
{
|
||||
Result[i] = Positions[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 衝突した。移動を蓄積
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="pos"></param>
|
||||
public void CollisionMove(int index, in LineSegment l, float radius, float factor = 1.0f)
|
||||
{
|
||||
// using var profile = new ProfileSample("CollisionMove");
|
||||
if (Mass[index] > 0)
|
||||
{
|
||||
Delta[index] += l.GetDelta(radius) * factor;
|
||||
++CollisionCount[index];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Vector3> Resolve()
|
||||
{
|
||||
for (int i = 0; i < Positions.Length; ++i)
|
||||
{
|
||||
if (CollisionCount[i] > 0)
|
||||
{
|
||||
Result[i] = Positions[i] + Delta[i] / CollisionCount[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Result[i] = Positions[i];
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 衝突前後の状態を描画
|
||||
/// </summary>
|
||||
public void DrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
for (int i = 0; i < Positions.Length; ++i)
|
||||
{
|
||||
if (CollisionCount[i] > 0)
|
||||
{
|
||||
Gizmos.DrawLine(Positions[i], Result[i]);
|
||||
// Gizmos.DrawWireSphere(Positions[i], 0.01f);
|
||||
// Gizmos.DrawSphere(Result[i], 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 836b32456872256409962a684cbee1b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user