Merge pull request #2509 from ousttrue/feature/cloth_experiment

[cloth] ClothWarpLib 開発版 を追加
This commit is contained in:
ousttrue 2024-11-26 19:46:16 +09:00 committed by GitHub
commit a2cef9784c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
119 changed files with 21084 additions and 1 deletions

View File

@ -87,7 +87,10 @@ namespace UniVRM10
{
foreach (var collider in Colliders)
{
collider.DrawGizmos();
if (collider != null)
{
collider.DrawGizmos();
}
}
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6871c022e581b3742bbc892f38900607
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: db40660c554df69458e2f1385c6b5b89
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@ -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:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 974382c74d34d85488192569177a07a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: be508240e6f6f57418cca09778dc2546
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aad95bf34c27466469ba37298cd4028c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8ad944ee2631d2b4eb6a33e6bbcd349f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b68db84044b3ee84aba85929dad95494
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -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:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0bc0c019f7af45745aa41b25018fd61f
timeCreated: 1524045545
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 623c6cdde4acc6641978d011f67c5b87
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: bedad7250033c0b49ac1e7db4d7ee63e
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46079dccaf3b7ac4ebbba728f1971ed0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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>();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6fd7b5b1e402644088796a17d7c2df9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1431334beb4184340955317d5a35a9fe
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 487929d3039a63544a0825523ac6a8ab
timeCreated: 1546851178
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:

View 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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58a0b5125aeceaa41b333a57e049c99e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a00a3e7f5bad4ae4cbbf0e2acc739fa8
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78593d828a6c64142bffa9e5b8e514dc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: a3dafcb1fb23ff148bbc17714d01144c
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 80a9e5abcd0197a46b50bcd2a1c62561
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea017d386e4fdee47b8a896571e2a0de
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View 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
- [ ] 衝突グループ(現状総当たり)

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3c501aa9d3818bf419f0bcb53c59e91d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 2ba7e498c4d5eef44aba4cf125686089
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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)
)
));
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5215383e4117f748adc44f843ff8568
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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
}

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 308b348fb80d89d42a9620951b0f60db
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 125347015f2366e4b8957431407ddf9d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: edc50c3e3f828de469f8ae6af569c174
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a8d83c5cee1b1349a818b15bfe48c35
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb08bedf8ccf007488d5907f401f289d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6d9e9d5010d025b4c9cdfb03eb8b1b0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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();
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: de8d095eee60f134ebd9ccaa9efcc01e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 568e5702a44c18d4487775441e011a61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 500c97fb71417394e8144b008b462e6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 88171b192ea95594c88871ba5b00d0ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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)
{
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e10706647b096be48917b33c33fbdefa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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];
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 874e448619fc4d54f900f52f83708a6c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 658703b04bb98bc438094be1f7959d73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ea8f33e691eb01f4ca5377804f9ce333
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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];
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 69453c7639ad8df4c94cbe3dae50e72c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 56860caf8e0babc41807521ed55a4f20
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
namespace UniVRM10.ClothWarp.Jobs
{
public struct WarpInfo
{
public int StartIndex;
public int EndIndex;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 139cb8a384ad8c64abc4dcfab37bcaca
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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}";
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4bccf867d0bc7b244841c8ecff9ce65a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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]);
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fee618dfb5846ea47978b49ed4c0282b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eddc8c1b804734c49a6eb04d72afb0a8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1fe036d9e9352e54ea009e8322a4faa2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9c034f40b6adaf4ba23f456fd5a2ec1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 36a9770569fe64c4f98dbe384596e4b2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
// }
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c16dea6c47fa81d4184547a209d8fe61
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 39108f27e89d7514d86e56e2ffb0550c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cceeecd3d3db5cd4ba74dfa6490e38b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: add9de55aee3f344cab64a1c99c7c6aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -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