Merge pull request #2517 from ousttrue/fix/fix_cloth_sample

[1.0][cloth] 開発版。いろいろ修正
This commit is contained in:
ousttrue 2024-12-13 17:43:10 +09:00 committed by GitHub
commit 472da49bef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 1500 additions and 634 deletions

View File

@ -138,7 +138,7 @@ namespace UniVRM10.Cloth.Viewer
var warp = childchild.gameObject.AddComponent<ClothWarpRoot>();
// Name = name,
// CollisionMask = mask,
warp.BaseSettings.radius = 0.02f;
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
break;
@ -179,7 +179,7 @@ namespace UniVRM10.Cloth.Viewer
if (warp != null)
{
// CollisionMask = mask,
warp.BaseSettings.radius = 0.02f;
warp.BaseSettings.Radius = 0.02f;
// Connection = type
transforms.Add(warp);
}

View File

@ -402,7 +402,7 @@ MonoBehaviour:
onValueChanged:
m_PersistentCalls:
m_Calls: []
m_IsOn: 0
m_IsOn: 1
--- !u!1 &153452228
GameObject:
m_ObjectHideFlags: 0
@ -686,6 +686,92 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 168425994}
m_CullTransparentMesh: 0
--- !u!1 &172483632
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 172483633}
- component: {fileID: 172483634}
m_Layer: 5
m_Name: AddClothToHips
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &172483633
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 172483632}
m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1472853923}
- {fileID: 1724807119}
m_Father: {fileID: 339774397}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 0, y: 0}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 162, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &172483634
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 172483632}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Navigation:
m_Mode: 3
m_WrapAround: 0
m_SelectOnUp: {fileID: 0}
m_SelectOnDown: {fileID: 0}
m_SelectOnLeft: {fileID: 0}
m_SelectOnRight: {fileID: 0}
m_Transition: 1
m_Colors:
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
m_ColorMultiplier: 1
m_FadeDuration: 0.1
m_SpriteState:
m_HighlightedSprite: {fileID: 0}
m_PressedSprite: {fileID: 0}
m_SelectedSprite: {fileID: 0}
m_DisabledSprite: {fileID: 0}
m_AnimationTriggers:
m_NormalTrigger: Normal
m_HighlightedTrigger: Highlighted
m_PressedTrigger: Pressed
m_SelectedTrigger: Highlighted
m_DisabledTrigger: Disabled
m_Interactable: 1
m_TargetGraphic: {fileID: 1472853924}
toggleTransition: 1
graphic: {fileID: 1236232219}
m_Group: {fileID: 0}
onValueChanged:
m_PersistentCalls:
m_Calls: []
m_IsOn: 1
--- !u!1 &175751362
GameObject:
m_ObjectHideFlags: 0
@ -1407,6 +1493,7 @@ RectTransform:
- {fileID: 1767706907}
- {fileID: 947409974}
- {fileID: 135168672}
- {fileID: 172483633}
- {fileID: 2144476967}
- {fileID: 1194499280}
- {fileID: 153452229}
@ -5027,6 +5114,81 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1215781541}
m_CullTransparentMesh: 0
--- !u!1 &1236232217
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1236232218}
- component: {fileID: 1236232220}
- component: {fileID: 1236232219}
m_Layer: 5
m_Name: Checkmark
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1236232218
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1472853923}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0.5, y: 0.5}
m_AnchorMax: {x: 0.5, y: 0.5}
m_AnchoredPosition: {x: 0, y: 0}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1236232219
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10901, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 0
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1236232220
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1236232217}
m_CullTransparentMesh: 0
--- !u!1 &1242458542
GameObject:
m_ObjectHideFlags: 0
@ -6228,6 +6390,82 @@ MonoBehaviour:
m_PersistentCalls:
m_Calls: []
m_IsOn: 0
--- !u!1 &1472853922
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1472853923}
- component: {fileID: 1472853925}
- component: {fileID: 1472853924}
m_Layer: 5
m_Name: Background
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1472853923
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children:
- {fileID: 1236232218}
m_Father: {fileID: 172483633}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 1}
m_AnchorMax: {x: 0, y: 1}
m_AnchoredPosition: {x: 10, y: -10}
m_SizeDelta: {x: 20, y: 20}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1472853924
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 1, g: 1, b: 1, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0}
m_Type: 1
m_PreserveAspect: 0
m_FillCenter: 1
m_FillMethod: 4
m_FillAmount: 1
m_FillClockwise: 1
m_FillOrigin: 0
m_UseSpriteMesh: 0
m_PixelsPerUnitMultiplier: 1
--- !u!222 &1472853925
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1472853922}
m_CullTransparentMesh: 0
--- !u!1 &1476033060
GameObject:
m_ObjectHideFlags: 0
@ -6774,6 +7012,85 @@ CanvasRenderer:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1684483641}
m_CullTransparentMesh: 0
--- !u!1 &1724807118
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1724807119}
- component: {fileID: 1724807121}
- component: {fileID: 1724807120}
m_Layer: 5
m_Name: Label
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!224 &1724807119
RectTransform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 172483633}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
m_AnchorMin: {x: 0, y: 0}
m_AnchorMax: {x: 1, y: 1}
m_AnchoredPosition: {x: 9, y: -0.5}
m_SizeDelta: {x: -28, y: -3}
m_Pivot: {x: 0.5, y: 0.5}
--- !u!114 &1724807120
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name:
m_EditorClassIdentifier:
m_Material: {fileID: 0}
m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_RaycastTarget: 1
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
m_Maskable: 1
m_OnCullStateChanged:
m_PersistentCalls:
m_Calls: []
m_FontData:
m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0}
m_FontSize: 14
m_FontStyle: 0
m_BestFit: 0
m_MinSize: 10
m_MaxSize: 40
m_Alignment: 0
m_AlignByGeometry: 0
m_RichText: 1
m_HorizontalOverflow: 0
m_VerticalOverflow: 0
m_LineSpacing: 1
m_Text: Add cloth to Hips
--- !u!222 &1724807121
CanvasRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1724807118}
m_CullTransparentMesh: 0
--- !u!1 &1761414315
GameObject:
m_ObjectHideFlags: 0
@ -7224,6 +7541,7 @@ MonoBehaviour:
m_openModel: {fileID: 2009818433}
m_showBoxMan: {fileID: 1767706908}
m_useJob: {fileID: 135168673}
m_addClothToHips: {fileID: 172483634}
m_reconstructSprngBone: {fileID: 2144476968}
m_resetSpringBone: {fileID: 1194499281}
m_pauseSpringBone: {fileID: 153452230}

View File

@ -21,6 +21,7 @@ namespace UniVRM10.Cloth.Viewer
[Header("Cloth")]
[SerializeField] Toggle m_useJob = default;
[SerializeField] Toggle m_addClothToHips = default;
[SerializeField] Button m_reconstructSprngBone = default;
[SerializeField] Button m_resetSpringBone = default;
[SerializeField] Toggle m_pauseSpringBone = default;
@ -61,6 +62,7 @@ namespace UniVRM10.Cloth.Viewer
m_showBoxMan = map.Get<Toggle>("ShowBoxMan");
m_useJob = map.Get<Toggle>("UseJob");
m_addClothToHips = map.Get<Toggle>("AddClothToHips");
m_reconstructSprngBone = map.Get<Button>("ReconstcutSpringBone");
m_resetSpringBone = map.Get<Button>("ResetSpringBone");
m_pauseSpringBone = map.Get<Toggle>("PauseSpringBone");
@ -402,18 +404,20 @@ namespace UniVRM10.Cloth.Viewer
else
{
ClothWarpRuntimeProvider.FromVrm10(vrm,
go => go.AddComponent<ClothWarpRoot>(),
o => GameObject.DestroyImmediate(o));
go => go.AddComponent<ClothWarpRoot>());
}
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
if (m_addClothToHips.isOn)
{
var cloth = hips.GetComponent<ClothGrid>();
if (cloth == null)
if (animator.GetBoneTransform(HumanBodyBones.Hips) is var hips)
{
cloth = hips.gameObject.AddComponent<ClothGrid>();
cloth.Reset();
cloth.LoopIsClosed = true;
var cloth = hips.GetComponent<ClothGrid>();
if (cloth == null)
{
cloth = hips.gameObject.AddComponent<ClothGrid>();
cloth.Reset();
cloth.LoopIsClosed = true;
}
}
}
}

View File

@ -4,7 +4,9 @@
"references": [
"GUID:308b348fb80d89d42a9620951b0f60db",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:3e5d614bc16b50d41bd94c8d7444ca46"
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@ -1,20 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using UniVRM10;
using UniGLTF;
using System.Linq;
namespace UniVRM10.ClothWarp.Components
{
[CustomEditor(typeof(ClothWarpRoot))]
class WarpRootEditor : Editor
class ClothWarpRootEditor : Editor
{
private ClothWarpRoot m_target;
private Vrm10Instance m_vrm;
private MultiColumnTreeView m_treeview;
VisualElement m_body;
void OnEnable()
{
@ -27,37 +28,20 @@ namespace UniVRM10.ClothWarp.Components
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
void BindColumn<T>(MultiColumnTreeView tree, string title,
int width, Func<T> makeVisualELment,
Func<int, bool> enableFunc, string subpath) where T : BindableElement
{
m_treeview.columns.Add(new Column
{
title = title,
width = width,
makeCell = makeVisualELmeent,
bindCell = (v, i) =>
makeCell = makeVisualELment,
bindCell = (v, index) =>
{
if (v is T prop)
{
var i = tree.GetIdForIndex(index);
var sb = new System.Text.StringBuilder();
sb.Append("m_particles.Array.data[");
sb.Append(i);
@ -85,8 +69,24 @@ namespace UniVRM10.ClothWarp.Components
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 IMGUIContainer(() =>
{
foreach (var v in m_target.Validations)
{
v.DrawGUI();
}
}));
m_body = new VisualElement();
root.Add(m_body);
m_body.style.display = m_target.Validations.All(x => x.ErrorLevel < ErrorLevels.Warning)
? DisplayStyle.Flex
: DisplayStyle.None
;
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.BaseSettings) });
m_body.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.Center) });
// root.Add(new PropertyField { bindingPath = "m_particles" });
{
@ -96,35 +96,43 @@ namespace UniVRM10.ClothWarp.Components
};
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");
BindColumn(m_treeview, "Transform", 120, () => new ObjectField(), (_) => false, "Transform");
BindColumn(m_treeview, "Mode", 40, () => new EnumField(), (_) => true, "Mode");
BindColumn(m_treeview, "Stiffness", 40, () => new FloatField(), isCustom, "Settings.Stiffness");
BindColumn(m_treeview, "Gravity", 120, () => new Vector3Field(), isCustom, "Settings.Gravity");
BindColumn(m_treeview, "Deceleration", 40, () => new FloatField(), isCustom, "Settings.Deceleration");
BindColumn(m_treeview, "Radius", 40, () => new FloatField(), isCustom, "Settings.Radius");
m_treeview.autoExpand = true;
m_treeview.SetRootItems(m_target.m_rootitems);
root.Add(m_treeview);
m_body.Add(m_treeview);
}
root.Add(new PropertyField { bindingPath = nameof(ClothWarpRoot.ColliderGroups) });
m_body.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 (m_vrm != null)
{
if (Application.isPlaying)
{
m_vrm.Runtime.SpringBone.SetJointLevel(m_target.transform, m_target.BaseSettings.ToBlittableJointMutable());
foreach (var p in m_target.Particles)
{
m_vrm.Runtime.SpringBone.SetJointLevel(p.Transform, p.Settings.ToBlittableJointMutable());
}
}
}
// 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);
m_body.style.display = m_target.Validations.All(x => x.ErrorLevel < ErrorLevels.Warning)
? DisplayStyle.Flex
: DisplayStyle.None
;
Repaint();
}
@ -147,7 +155,7 @@ namespace UniVRM10.ClothWarp.Components
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);
Handles.SphereHandleCap(t.GetInstanceID(), t.position, t.rotation, p.Settings.Radius * 2, EventType.Repaint);
}
}
}

View File

@ -1,7 +1,6 @@
using System.Linq;
using UnityEngine.UIElements;
using UnityEditor;
using UnityEngine;
using UniVRM10;
using UnityEditor.UIElements;
namespace UniVRM10.ClothWarp.Components
@ -9,60 +8,109 @@ namespace UniVRM10.ClothWarp.Components
[CustomEditor(typeof(ClothWarpRuntimeProvider))]
public class RotateParticleRuntimeProviderEditor : Editor
{
const string FROM_VRM10_MENU = "Replace VRM10 Springs to ClothWarp Warps";
ClothWarpRuntimeProvider _target;
Vrm10Instance _vrm;
[MenuItem(FROM_VRM10_MENU, true)]
public static bool IsFromVrm10()
void OnEnable()
{
var go = Selection.activeGameObject;
if (go == null)
_target = (ClothWarpRuntimeProvider)target;
if (_target != null)
{
return false;
_vrm = _target.GetComponent<Vrm10Instance>();
}
return go.GetComponent<Vrm10Instance>() != null;
}
public override void OnInspectorGUI()
public override VisualElement CreateInspectorGUI()
{
var provider = target as ClothWarpRuntimeProvider;
if (provider == null)
var root = new VisualElement();
root.Bind(serializedObject);
{
return;
var s = new PropertyField { bindingPath = "m_Script" };
s.SetEnabled(false);
root.Add(s);
}
var instance = provider.GetComponent<Vrm10Instance>();
using (new EditorGUI.DisabledScope(instance == null))
root.Add(new PropertyField { bindingPath = nameof(_target.UseJob) });
root.Add(new PropertyField { bindingPath = nameof(_target.Warps) });
root.Add(new PropertyField { bindingPath = nameof(_target.Cloths) });
{
if (GUILayout.Button("Replace VRM10 Springs to ClothWarp Warps"))
var setup = new Foldout { text = "Setup" };
var from_vrm10 = new Button { text = "Load VRM10 Springs to ClothWarp Warps" };
setup.Add(from_vrm10);
from_vrm10.RegisterCallback<ClickEvent>(e =>
{
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName(FROM_VRM10_MENU);
Undo.SetCurrentGroupName("Load Vrm-1.0 Springs to ClothWarp Warps");
var undo = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(instance, "RegisterCompleteObjectUndo");
ClothWarpRuntimeProvider.FromVrm10(instance, Undo.AddComponent<ClothWarpRoot>, Undo.DestroyObjectImmediate);
Undo.RegisterFullObjectHierarchyUndo(instance.gameObject, "RegisterFullObjectHierarchyUndo");
// attach ClothWarp from VRM10Instance.Springs
ClothWarpRuntimeProvider.FromVrm10(_vrm, Undo.AddComponent<ClothWarpRoot>);
Undo.RegisterFullObjectHierarchyUndo(_vrm.gameObject, "RegisterFullObjectHierarchyUndo");
Undo.RegisterCompleteObjectUndo(provider, "RegisterCompleteObjectUndo");
provider.Reset();
// update ClothWarpRuntimeProvider
Undo.RegisterCompleteObjectUndo(_target, "RegisterCompleteObjectUndo");
_target.Reset();
Undo.CollapseUndoOperations(undo);
}
}
});
using (new EditorGUI.DisabledScope(instance == null || !Application.isPlaying))
{
if (GUILayout.Button("RestoreInitialTransform"))
var clear_vrm10_springs = new Button { text = "Clear Vrm-1.0 springs" };
setup.Add(clear_vrm10_springs);
clear_vrm10_springs.RegisterCallback<ClickEvent>(e =>
{
instance.Runtime.SpringBone.RestoreInitialTransform();
}
Undo.IncrementCurrentGroup();
Undo.SetCurrentGroupName("Clear VRM10 Srpings");
var undo = Undo.GetCurrentGroup();
Undo.RegisterCompleteObjectUndo(_vrm, "RegisterCompleteObjectUndo");
foreach (var spring in _vrm.SpringBone.Springs)
{
if (spring != null)
{
foreach (var joint in spring.Joints)
{
if (joint != null)
{
Undo.DestroyObjectImmediate(joint);
}
}
}
spring.Joints.Clear();
}
_vrm.SpringBone.Springs.Clear();
Undo.RegisterFullObjectHierarchyUndo(_vrm.gameObject, "RegisterFullObjectHierarchyUndo");
Undo.CollapseUndoOperations(undo);
});
var reload = new Button { text = "Reload" };
setup.Add(reload);
reload.RegisterCallback<ClickEvent>(e =>
{
_target.Reset();
});
root.Add(setup);
}
if (GUILayout.Button("Reset"))
{
provider.Reset();
// runtime: reset button
var runtime = new Foldout { text = "Runtime" };
root.Add(runtime);
var button = new Button
{
text = "RestoreInitialTransform",
};
runtime.Add(button);
button.RegisterCallback<ClickEvent>((e) =>
{
_vrm.Runtime.SpringBone.RestoreInitialTransform();
});
}
base.OnInspectorGUI();
return root;
}
}
}

View File

@ -23,7 +23,7 @@ Base 設定を変更する場合は変える方を列挙設定できる。
Custom と Disable を選択できる。
- ゆれものの根元にアタッチする
- [ ] 子孫に HumanoidBone がある場合にアタッチ不可
- [x] 子孫に HumanoidBone がある場合にアタッチ不可
- [ ] 枝分かれ
- [ ] WarpRoot らからデフォルト以外の Warp を選び出す
- [ ] Center
@ -126,4 +126,4 @@ WarpRoot2 o=o=o
### Optimize
- [ ] 衝突グループ(現状総当たり)
- [x] 衝突グループ

View File

@ -10,8 +10,8 @@ namespace UniVRM10.ClothWarp
class ClothRectList
{
readonly List<Transform> _particles;
public List<(SpringConstraint, ClothRect)> List = new();
public readonly ClothGrid[] ClothGrids;
public List<(int, SpringConstraint, ClothRect)> List = new();
public readonly bool[] ClothUsedParticles;
public ClothRectList(List<Transform> particles, Vrm10Instance vrm)
@ -19,15 +19,16 @@ namespace UniVRM10.ClothWarp
_particles = particles;
ClothUsedParticles = new bool[_particles.Count];
var cloths = vrm.GetComponentsInChildren<ClothGrid>();
foreach (var cloth in cloths)
ClothGrids = vrm.GetComponentsInChildren<ClothGrid>();
for (int i = 0; i < ClothGrids.Length; ++i)
{
AddCloth(cloth, vrm);
AddCloth(i, vrm);
}
}
void AddCloth(ClothGrid cloth, Vrm10Instance vrm)
void AddCloth(int clothGridIndex, Vrm10Instance vrm)
{
var cloth = ClothGrids[clothGridIndex];
for (int i = 1; i < cloth.Warps.Count; ++i)
{
var s0 = cloth.Warps[i - 1];
@ -53,6 +54,7 @@ namespace UniVRM10.ClothWarp
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),
@ -91,6 +93,7 @@ namespace UniVRM10.ClothWarp
(c, d) = (d, c);
}
List.Add((
clothGridIndex,
new SpringConstraint(
_particles.IndexOf(a),
_particles.IndexOf(b),

View File

@ -5,7 +5,8 @@
"GUID:3e5d614bc16b50d41bd94c8d7444ca46",
"GUID:e47c917724578cc43b5506c17a27e9a0",
"GUID:8d76e605759c3f64a957d63ef96ada7c",
"GUID:1cd941934d098654fa21a13f28346412"
"GUID:1cd941934d098654fa21a13f28346412",
"GUID:5f875fdc81c40184c8333b9d63c6ddd5"
],
"includePlatforms": [],
"excludePlatforms": [],

View File

@ -11,6 +11,9 @@ using UnityEngine;
namespace UniVRM10.ClothWarp
{
/// <summary>
/// プロトタイプ。非 job
/// </summary>
public class ClothWarpRuntime : IVrm10SpringBoneRuntime
{
Vrm10Instance _vrm;
@ -58,7 +61,7 @@ namespace UniVRM10.ClothWarp
return Color.gray;
}
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
public Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
{
_building = true;
_vrm = vrm;
@ -102,7 +105,7 @@ namespace UniVRM10.ClothWarp
_clothRectCollisions = new();
for (int i = 0; i < _clothRects.List.Count; ++i)
{
var (s, r) = _clothRects.List[i];
var (grid, s, r) = _clothRects.List[i];
_clothRectCollisions.Add(new());
var c = _clothRectCollisions.Last();
c.InitializeColliderSide(_newPos, _colliderGroups, r);
@ -112,6 +115,8 @@ namespace UniVRM10.ClothWarp
_initialized = true;
_building = false;
return Task.CompletedTask;
}
/// <summary>
@ -167,7 +172,7 @@ namespace UniVRM10.ClothWarp
// verlet 積分
var time = new FrameTime(deltaTime);
_list.BeginFrame(Env, time, _restPositions);
foreach (var (spring, collision) in _clothRects.List)
foreach (var (gridIndex, spring, collision) in _clothRects.List)
{
// cloth constraint
spring.Resolve(time, _clothFactor, _list._particles);
@ -189,7 +194,7 @@ namespace UniVRM10.ClothWarp
for (int j = 0; j < _clothRects.List.Count; ++j)
{
var (spring, rect) = _clothRects.List[j];
var (gridIndex, spring, rect) = _clothRects.List[j];
var collision = _clothRectCollisions[j];
// using var prof = new ProfileSample("Collision: Cloth");
// 頂点 abcd は同じ CollisionMask

View File

@ -1,10 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UniGLTF.SpringBoneJobs.Blittables;
using UniGLTF;
using UnityEngine;
using UnityEngine.UIElements;
using UniVRM10;
namespace UniVRM10.ClothWarp.Components
@ -17,18 +16,6 @@ namespace UniVRM10.ClothWarp.Components
/// </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>
@ -53,28 +40,28 @@ namespace UniVRM10.ClothWarp.Components
{
public Transform Transform;
public ParticleMode Mode;
public BlittableJointMutable Settings;
public Jobs.ParticleSettings Settings;
public Particle(Transform t, ParticleMode mode, BlittableJointMutable settings)
public Particle(Transform t, ParticleMode mode, Jobs.ParticleSettings settings)
{
Transform = t;
Mode = mode;
Settings = settings;
}
public Particle(Transform t, BlittableJointMutable settings)
public Particle(Transform t, Jobs.ParticleSettings settings)
: this(t, ParticleMode.Custom, settings)
{
}
public Particle(Transform t)
: this(t, ParticleMode.Base, DefaultSetting())
: this(t, ParticleMode.Base, Jobs.ParticleSettings.Default)
{
}
}
[SerializeField]
public BlittableJointMutable BaseSettings = DefaultSetting();
public Jobs.ParticleSettings BaseSettings = Jobs.ParticleSettings.Default;
/// <summary>
/// null のときは world root ではなく model root で処理
@ -98,9 +85,59 @@ namespace UniVRM10.ClothWarp.Components
// 逆引き
Dictionary<Transform, int> m_map = new();
public readonly List<Validation> Validations = new();
bool HasHumanoidBonesInChildren(Animator animator, out Transform t)
{
foreach (HumanBodyBones bone in Enum.GetValues(typeof(HumanBodyBones)))
{
if (bone == HumanBodyBones.LastBone)
{
continue;
}
var b = animator.GetBoneTransform(bone);
if (b != null)
{
for (var parent = b.parent; parent != null; parent = parent.parent)
{
if (parent == transform)
{
t = transform;
return true;
}
}
}
}
t = default;
return false;
}
void OnValidate()
{
m_particles = GetComponentsInChildren<Transform>().Skip(1).Select(x => new Particle(x)).ToList();
Validations.Clear();
if (GetComponentInParent<Animator>() is var animator)
{
if (HasHumanoidBonesInChildren(animator, out var t))
{
Validations.Add(Validation.Error(
"アタッチできません。子孫にHumanoidBoneがあります",
ValidationContext.Create(t)
));
}
}
var backup = m_particles.ToDictionary(x => x.Transform, x => x);
m_particles = GetComponentsInChildren<Transform>().Skip(1).Select(x =>
{
foreach (var particle in m_particles)
{
if (particle.Transform == x)
{
return particle;
}
}
return new Particle(x);
}).ToList();
m_map.Clear();
for (int i = 0; i < m_particles.Count; ++i)
{
@ -164,7 +201,7 @@ namespace UniVRM10.ClothWarp.Components
}
}
public void SetSettings(Transform t, BlittableJointMutable settings)
public void SetSettings(Transform t, Jobs.ParticleSettings settings)
{
if (t == null) return;
for (int i = 0; i < m_particles.Count; ++i)
@ -182,7 +219,7 @@ namespace UniVRM10.ClothWarp.Components
public void OnDrawGizmosSelected()
{
Gizmos.DrawSphere(transform.position, BaseSettings.radius);
Gizmos.DrawSphere(transform.position, BaseSettings.Radius);
foreach (var p in Particles)
{
@ -190,7 +227,7 @@ namespace UniVRM10.ClothWarp.Components
{
continue;
}
Gizmos.DrawWireSphere(p.Transform.position, p.Settings.radius);
Gizmos.DrawWireSphere(p.Transform.position, p.Settings.Radius);
if (TryGetClosestParent(p.Transform, out var parent))
{

View File

@ -2,7 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UniVRM10;
namespace UniVRM10.ClothWarp.Components
{
@ -17,7 +17,7 @@ namespace UniVRM10.ClothWarp.Components
public List<ClothGrid> Cloths = new();
[SerializeField]
public bool UseJob;
public bool UseJob = true;
IVrm10SpringBoneRuntime m_runtime;
public IVrm10SpringBoneRuntime CreateSpringBoneRuntime()
@ -45,8 +45,7 @@ namespace UniVRM10.ClothWarp.Components
}
public static void FromVrm10(Vrm10Instance instance,
Func<GameObject, ClothWarpRoot> addWarp,
Action<UnityEngine.Object> deleteObject)
Func<GameObject, ClothWarpRoot> addWarp)
{
foreach (var spring in instance.SpringBone.Springs)
{
@ -64,29 +63,30 @@ namespace UniVRM10.ClothWarp.Components
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
// mod ?
var stiffness = Mathf.Min(0.08f, joint.m_stiffnessForce * 0.1f);
var settings = new Jobs.ParticleSettings
{
dragForce = joint.m_dragForce,
gravityDir = joint.m_gravityDir,
gravityPower = joint.m_gravityPower,
// mod
stiffnessForce = joint.m_stiffnessForce * 6,
Deceleration = joint.m_dragForce,
Gravity = joint.m_gravityDir * joint.m_gravityPower,
Stiffness = stiffness,
};
if (i == 0)
{
settings.radius = joints[0].m_jointRadius;
settings.Radius = joints[0].m_jointRadius;
warp.BaseSettings = settings;
}
else
{
// breaking change from vrm-1.0
settings.radius = joints[i - 1].m_jointRadius;
settings.Radius = joints[i - 1].m_jointRadius;
var useInheritSettings = warp.BaseSettings.Equals(settings);
if (useInheritSettings)
{
@ -97,14 +97,10 @@ namespace UniVRM10.ClothWarp.Components
warp.SetSettings(joint.transform, settings);
}
}
// Undo.DestroyObjectImmediate(joint);
deleteObject(joint);
}
spring.Joints.Clear();
warp.ColliderGroups = spring.ColliderGroups.ToList();
}
}
instance.SpringBone.Springs.Clear();
}
}
}

View File

@ -7,11 +7,13 @@ using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
using UniVRM10;
namespace UniVRM10.ClothWarp.Jobs
{
/// <summary>
/// Job 版
/// </summary>
public class ClothWarpJobRuntime : IVrm10SpringBoneRuntime
{
Vrm10Instance _vrm;
@ -19,12 +21,25 @@ namespace UniVRM10.ClothWarp.Jobs
bool _building = false;
//
// collider
// colliderTransform
//
List<Transform> _colliderTransforms;
TransformAccessArray _colliderTransformAccessArray;
NativeArray<Matrix4x4> _currentColliders;
NativeArray<BlittableCollider> _colliders;
//
// collider
//
List<VRM10SpringBoneCollider> _colliders;
NativeArray<BlittableCollider> _colliderInfo;
//
// colliderGroup
//
List<VRM10SpringBoneColliderGroup> _colliderGroups;
NativeArray<int> _colliderRef;
NativeArray<ArrayRange> _colliderGroup;
NativeArray<int> _colliderGroupRef;
//
// particle
@ -38,10 +53,10 @@ namespace UniVRM10.ClothWarp.Jobs
NativeArray<Vector3> _nextPositions;
NativeArray<Quaternion> _nextRotations;
NativeArray<Vector3> _strandCollision;
NativeArray<int> _clothCollisionCount;
NativeArray<Vector3> _clothCollisionDelta;
NativeArray<Vector3> _forces;
NativeArray<Vector3> _warpCollision;
NativeArray<int> _rectCollisionCount;
NativeArray<Vector3> _rectCollisionDelta;
NativeArray<Vector3> _impulsiveForces;
//
// warp
@ -49,16 +64,26 @@ namespace UniVRM10.ClothWarp.Jobs
NativeArray<WarpInfo> _warps;
//
// cloth
// cloth rect
//
NativeArray<bool> _clothUsedParticles;
NativeArray<(SpringConstraint, SphereTriangle.ClothRect)> _clothRects;
NativeArray<(int ClothGridIndex, SpringConstraint SpringConstraint, SphereTriangle.ClothRect Rect)> _clothRects;
NativeArray<(Vector3, Vector3, Vector3, Vector3)> _clothRectResults;
//
// cloth grid
//
NativeArray<ClothInfo> _cloths;
public void Dispose()
{
if (_colliderTransformAccessArray.isCreated) _colliderTransformAccessArray.Dispose();
if (_currentColliders.IsCreated) _currentColliders.Dispose();
if (_colliderRef.IsCreated) _colliderRef.Dispose();
if (_colliderGroup.IsCreated) _colliderGroup.Dispose();
if (_colliderGroupRef.IsCreated) _colliderGroupRef.Dispose();
if (_warps.IsCreated) _warps.Dispose();
if (_transformAccessArray.isCreated) _transformAccessArray.Dispose();
if (_inputData.IsCreated) _inputData.Dispose();
@ -67,15 +92,17 @@ namespace UniVRM10.ClothWarp.Jobs
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 (_warpCollision.IsCreated) _warpCollision.Dispose();
if (_rectCollisionCount.IsCreated) _rectCollisionCount.Dispose();
if (_rectCollisionDelta.IsCreated) _rectCollisionDelta.Dispose();
if (_impulsiveForces.IsCreated) _impulsiveForces.Dispose();
if (_warps.IsCreated) _warps.Dispose();
if (_cloths.IsCreated) _cloths.Dispose();
if (_clothUsedParticles.IsCreated) _clothUsedParticles.Dispose();
if (_clothRects.IsCreated) _clothRects.Dispose();
if (_clothRectResults.IsCreated) _clothRectResults.Dispose();
}
(int index, bool isNew) GetTransformIndex(Transform t,
@ -97,6 +124,33 @@ namespace UniVRM10.ClothWarp.Jobs
return (i, true);
}
Transform GetParent(Transform t, Transform root)
{
for (var parent = t.parent; parent != null; parent = parent.parent)
{
if (_transforms.Contains(parent))
{
return parent;
}
if (parent == root)
{
break;
}
}
throw new Exception();
}
int GetOrAddColliderTransform(Transform t)
{
var index = _colliderTransforms.IndexOf(t);
if (index == -1)
{
index = _colliderTransforms.Count;
_colliderTransforms.Add(t);
}
return index;
}
public async Task InitializeAsync(Vrm10Instance vrm, IAwaitCaller awaitCaller)
{
_vrm = vrm;
@ -111,30 +165,67 @@ namespace UniVRM10.ClothWarp.Jobs
//
// colliders
//
_colliders = new();
_colliderGroups = new();
_colliderTransforms = new();
List<BlittableCollider> colliders = new();
foreach (var collider in vrm.GetComponentsInChildren<VRM10SpringBoneCollider>())
List<BlittableCollider> colliderInfo = new();
List<int> colliderRef = new();
List<ArrayRange> colliderGroups = new();
foreach (var colliderGroup in vrm.GetComponentsInChildren<VRM10SpringBoneColliderGroup>())
{
colliders.Add(new BlittableCollider
if (colliderGroup == null)
{
offset = collider.Offset,
radius = collider.Radius,
tailOrNormal = collider.TailOrNormal,
colliderType = TranslateColliderType(collider.ColliderType)
continue;
}
var startColliderRef = colliderRef.Count;
foreach (var collider in colliderGroup.Colliders)
{
if (collider == null)
{
continue;
}
if (_colliders.Contains(collider))
{
continue;
}
var colliderTransformIndex = GetOrAddColliderTransform(collider.transform);
colliderRef.Add(colliderInfo.Count);
colliderInfo.Add(new BlittableCollider
{
offset = collider.Offset,
radius = collider.Radius,
tailOrNormal = collider.TailOrNormal,
colliderType = TranslateColliderType(collider.ColliderType),
transformIndex = colliderTransformIndex,
});
_colliders.Add(collider);
}
_colliderGroups.Add(colliderGroup);
colliderGroups.Add(new ArrayRange
{
Start = startColliderRef,
End = colliderRef.Count,
});
_colliderTransforms.Add(collider.transform);
}
_colliderTransformAccessArray = new(_colliderTransforms.ToArray(), 128);
_colliders = new(colliders.ToArray(), Allocator.Persistent);
_currentColliders = new(_colliderTransforms.Count, Allocator.Persistent);
_colliderInfo = new(colliderInfo.ToArray(), Allocator.Persistent);
_colliderRef = new(colliderRef.ToArray(), Allocator.Persistent);
_colliderGroup = new(colliderGroups.ToArray(), Allocator.Persistent);
//
// warps
// warps => particles
//
_transforms = new();
List<TransformInfo> info = new();
List<Vector3> positions = new();
List<WarpInfo> warps = new();
List<int> colliderGroupRef = new();
var warpSrcs = vrm.GetComponentsInChildren<Components.ClothWarpRoot>();
for (int warpIndex = 0; warpIndex < warpSrcs.Length; ++warpIndex)
{
@ -145,14 +236,16 @@ namespace UniVRM10.ClothWarp.Jobs
{
GetTransformIndex(warp.Center, new TransformInfo
{
TransformType = TransformType.Center
TransformType = TransformType.Center,
WarpIndex = warpIndex,
}, info, positions);
start += 1;
}
var warpRootParentTransformIndex = GetTransformIndex(warp.transform.parent, new TransformInfo
{
TransformType = TransformType.WarpRootParent
TransformType = TransformType.WarpRootParent,
WarpIndex = warpIndex,
}, info, positions);
Debug.Assert(warpRootParentTransformIndex.index != -1);
if (warpRootParentTransformIndex.isNew)
@ -166,31 +259,81 @@ namespace UniVRM10.ClothWarp.Jobs
ParentIndex = warpRootParentTransformIndex.index,
InitLocalPosition = vrm.DefaultTransformStates[warp.transform].LocalPosition,
InitLocalRotation = vrm.DefaultTransformStates[warp.transform].LocalRotation,
Settings = warp.BaseSettings,
WarpIndex = warpIndex,
}, info, positions);
Debug.Assert(warpRootTransformIndex.index != -1);
Debug.Assert(warpRootTransformIndex.isNew);
var parentIndex = warpRootTransformIndex.index;
foreach (var particle in warp.Particles)
var colliderGroupRefStart = colliderGroupRef.Count;
if (warpRootTransformIndex.isNew)
{
if (particle.Transform != null && particle.Mode != Components.ClothWarpRoot.ParticleMode.Disabled)
// var parentIndex = warpRootTransformIndex.index;
Func<int, int> GetFirstSiblingIndex = (parent) =>
{
var outputParticleTransformIndex = GetTransformIndex((Transform)particle.Transform, new TransformInfo
for (int i = 0; i < info.Count; ++i)
{
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;
if (info[i].ParentIndex == parent)
{
return i;
}
}
throw new Exception();
};
HashSet<int> parentIndexSet = new();
foreach (var particle in warp.Particles)
{
if (particle.Transform != null && particle.Mode != Components.ClothWarpRoot.ParticleMode.Disabled)
{
var parentIndex = _transforms.IndexOf(GetParent(particle.Transform, warp.transform));
BranchInfo? branch = default;
if (parentIndexSet.Contains(parentIndex))
{
branch = new BranchInfo
{
FirstSiblingIndex = GetFirstSiblingIndex(parentIndex),
};
}
else
{
parentIndexSet.Add(parentIndex);
}
var outputParticleTransformIndex = GetTransformIndex(particle.Transform, new TransformInfo
{
TransformType = TransformType.Particle,
ParentIndex = parentIndex,
InitLocalPosition = vrm.DefaultTransformStates[particle.Transform].LocalPosition,
InitLocalRotation = vrm.DefaultTransformStates[particle.Transform].LocalRotation,
Settings = particle.Settings,
WarpIndex = warpIndex,
Branch = branch,
}, info, positions);
// parentIndex = outputParticleTransformIndex.index;
}
}
foreach (var group in warp.ColliderGroups)
{
if (group != null)
{
colliderGroupRef.Add(_colliderGroups.IndexOf(group));
}
}
}
warps.Add(new WarpInfo
{
StartIndex = start,
EndIndex = _transforms.Count,
PrticleRange = new ArrayRange
{
Start = start,
End = _transforms.Count,
},
ColliderGroupRefRange = new ArrayRange
{
Start = colliderGroupRefStart,
End = colliderGroupRef.Count,
},
});
await awaitCaller.NextFrame();
@ -204,18 +347,48 @@ namespace UniVRM10.ClothWarp.Jobs
_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);
_warpCollision = new(pos.Length, Allocator.Persistent);
_rectCollisionCount = new(pos.Length, Allocator.Persistent);
_rectCollisionDelta = new(pos.Length, Allocator.Persistent);
_impulsiveForces = new(pos.Length, Allocator.Persistent);
//
// cloths
//
var clothRects = new ClothRectList(_transforms, vrm);
_clothRects = new(clothRects.List.ToArray(), Allocator.Persistent);
_clothRectResults = new(clothRects.List.Count, Allocator.Persistent);
_clothUsedParticles = new(clothRects.ClothUsedParticles, Allocator.Persistent);
_building = false;
List<ClothInfo> cloths = new();
foreach (var grid in clothRects.ClothGrids)
{
var colliderGroupRefStart = colliderGroupRef.Count;
HashSet<VRM10SpringBoneColliderGroup> groups = new();
foreach (var warp in grid.Warps)
{
foreach (var group in warp.ColliderGroups)
{
if (group != null && !groups.Contains(group))
{
groups.Add(group);
colliderGroupRef.Add(_colliderGroups.IndexOf(group));
}
}
}
cloths.Add(new ClothInfo
{
ColliderGroupRefRange = new ArrayRange
{
Start = colliderGroupRefStart,
End = colliderGroupRef.Count,
},
});
}
_cloths = new(cloths.ToArray(), Allocator.Persistent);
_colliderGroupRef = new(colliderGroupRef.ToArray(), Allocator.Persistent);
}
private static BlittableColliderType TranslateColliderType(VRM10SpringBoneColliderTypes colliderType)
@ -259,10 +432,10 @@ namespace UniVRM10.ClothWarp.Jobs
Info = _info,
InputData = _inputData,
CurrentPositions = _currentPositions,
Forces = _forces,
ImpulsiveForces = _impulsiveForces,
CollisionCount = _clothCollisionCount,
CollisionDelta = _clothCollisionDelta,
CollisionCount = _rectCollisionCount,
CollisionDelta = _rectCollisionDelta,
}.Schedule(_transformAccessArray, handle);
// spring(cloth weft)
@ -271,8 +444,8 @@ namespace UniVRM10.ClothWarp.Jobs
ClothRects = _clothRects,
CurrentPositions = _currentPositions,
Force = _forces,
}.Schedule(_clothRects.Length, 128, handle);
ImpulsiveForces = _impulsiveForces,
}.Schedule(_clothRects.Length, 1, handle);
// verlet
handle = new VerletJob
@ -282,58 +455,86 @@ namespace UniVRM10.ClothWarp.Jobs
CurrentTransforms = _inputData,
PrevPositions = _prevPositions,
CurrentPositions = _currentPositions,
Forces = _forces,
ImpulsiveForces = _impulsiveForces,
NextPositions = _nextPositions,
NextRotations = _nextRotations,
}.Schedule(_info.Length, 128, handle);
}.Schedule(_info.Length, 1, handle);
// 親子の長さで拘束
handle = new ParentLengthConstraintJob
{
Warps = _warps,
Info = _info,
Data = _inputData,
NextPositions = _nextPositions,
}.Schedule(_warps.Length, 16, handle);
}.Schedule(_warps.Length, 1, handle);
// collision
{
var handle0 = new StrandCollisionJob
var handle0 = new WarpCollisionJob
{
Colliders = _colliders,
Colliders = _colliderInfo,
CurrentColliders = _currentColliders,
Info = _info,
NextPositions = _nextPositions,
ClothUsedParticles = _clothUsedParticles,
StrandCollision = _strandCollision,
}.Schedule(_info.Length, 128, handle);
StrandCollision = _warpCollision,
var handle1 = new ClothCollisionJob
Warps = _warps,
ColliderGroupRef = _colliderGroupRef,
ColliderGroup = _colliderGroup,
ColliderRef = _colliderRef,
}.Schedule(_info.Length, 1, handle);
var handle1 = new RectCollisionJob
{
Colliders = _colliders,
ClothRects = _clothRects,
ClothRectResults = _clothRectResults,
Colliders = _colliderInfo,
CurrentColliders = _currentColliders,
Info = _info,
NextPositions = _nextPositions,
CollisionCount = _clothCollisionCount,
CollisionDelta = _clothCollisionDelta,
Cloths = _cloths,
ColliderGroupRef = _colliderGroupRef,
ColliderGroup = _colliderGroup,
ColliderRef = _colliderRef,
}.Schedule(_clothRects.Length, 1, handle);
handle1 = new RectCollisionReduceJob
{
ClothRects = _clothRects,
}.Schedule(_clothRects.Length, 128, handle);
ClothRectResults = _clothRectResults,
NextPositions = _nextPositions,
RectCollisionCount = _rectCollisionCount,
RectCollisionDelta = _rectCollisionDelta,
}.Schedule(handle1);
handle = JobHandle.CombineDependencies(handle0, handle1);
handle = new CollisionApplyJob
{
ClothUsedParticles = _clothUsedParticles,
StrandCollision = _strandCollision,
ClothCollisionCount = _clothCollisionCount,
ClothCollisionDelta = _clothCollisionDelta,
StrandCollision = _warpCollision,
RectCollisionCount = _rectCollisionCount,
RectCollisionDelta = _rectCollisionDelta,
NextPosition = _nextPositions,
}.Schedule(_info.Length, 128, handle);
}.Schedule(_info.Length, 1, handle);
}
// 親子の長さで拘束. TODO: ApplyRotationJob と合体
handle = new ParentLengthConstraintJob
{
Warps = _warps,
Info = _info,
Data = _inputData,
NextPositions = _nextPositions,
}.Schedule(_warps.Length, 1, handle);
// NextPositions から NextRotations を作る
handle = new ApplyRotationJob
{
@ -342,7 +543,7 @@ namespace UniVRM10.ClothWarp.Jobs
CurrentTransforms = _inputData,
NextPositions = _nextPositions,
NextRotations = _nextRotations,
}.Schedule(_warps.Length, 16, handle);
}.Schedule(_warps.Length, 1, handle);
// output
handle = new OutputTransformJob
@ -362,7 +563,7 @@ namespace UniVRM10.ClothWarp.Jobs
{
foreach (var warp in _warps)
{
for (int i = warp.StartIndex; i < warp.EndIndex; ++i)
for (int i = warp.PrticleRange.Start; i < warp.PrticleRange.End; ++i)
{
var p = _info[i];
var t = _transforms[i];
@ -392,11 +593,11 @@ namespace UniVRM10.ClothWarp.Jobs
break;
case TransformType.ClothWarp:
Gizmos.color = Color.white;
Gizmos.DrawSphere(v, info.Settings.radius);
Gizmos.DrawSphere(v, info.Settings.Radius);
break;
case TransformType.Particle:
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(v, info.Settings.radius);
Gizmos.DrawWireSphere(v, info.Settings.Radius);
break;
}
}
@ -408,7 +609,7 @@ namespace UniVRM10.ClothWarp.Jobs
if (i != -1)
{
var info = _info[i];
info.Settings = jointSettings;
info.Settings.FromBlittableJointMutable(jointSettings);
_info[i] = info;
}
}

View File

@ -1,319 +0,0 @@
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,51 @@
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct ClothInfo
{
public ArrayRange ColliderGroupRefRange;
}
public struct InputColliderJob : IJobParallelForTransform
{
[WriteOnly] public NativeArray<Matrix4x4> CurrentCollider;
public void Execute(int colliderIndex, TransformAccess transform)
{
CurrentCollider[colliderIndex] = transform.localToWorldMatrix;
}
}
public struct CollisionApplyJob : IJobParallelFor
{
[ReadOnly] public NativeArray<bool> ClothUsedParticles;
[ReadOnly] public NativeArray<Vector3> StrandCollision;
[ReadOnly] public NativeArray<int> RectCollisionCount;
[ReadOnly] public NativeArray<Vector3> RectCollisionDelta;
public NativeArray<Vector3> NextPosition;
public void Execute(int particleIndex)
{
if (ClothUsedParticles[particleIndex])
{
var count = RectCollisionCount[particleIndex];
if (count > 0)
{
// 一つの頂点が最大で近接する4つの rect で当たり判定をされる
// 衝突結果を足して割る
NextPosition[particleIndex] += RectCollisionDelta[particleIndex] / count;
}
}
else
{
NextPosition[particleIndex] = StrandCollision[particleIndex];
}
}
}
}

View File

@ -12,16 +12,31 @@ namespace UniVRM10.ClothWarp.Jobs
{
[ReadOnly] public NativeArray<WarpInfo> Warps;
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<TransformData> Data;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> NextPositions;
public void Execute(int warpIndex)
{
var warp = Warps[warpIndex];
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
for (int particleIndex = warp.PrticleRange.Start; particleIndex < warp.PrticleRange.End - 1; ++particleIndex)
{
// 位置を長さで拘束
NextPositions[particleIndex + 1] = NextPositions[particleIndex] +
(NextPositions[particleIndex + 1] - NextPositions[particleIndex]).normalized
* Info[particleIndex + 1].InitLocalPosition.magnitude;
var particle = Info[particleIndex + 1];
if (particle.Branch.HasValue)
{
var branch = particle.Branch.Value;
// 1番目の兄弟の情報を使う
// 枝分かれ。特別処理
var firstSibling = Info[branch.FirstSiblingIndex];
var firstPosition = NextPositions[branch.FirstSiblingIndex];
var local_d = particle.InitLocalPosition - firstSibling.InitLocalPosition;
NextPositions[particleIndex + 1] = firstPosition + Data[particle.ParentIndex].Rotation * local_d;
}
else
{
// 位置を長さで拘束
NextPositions[particleIndex + 1] = NextPositions[particleIndex] +
(NextPositions[particleIndex + 1] - NextPositions[particleIndex]).normalized
* Info[particleIndex + 1].InitLocalPosition.magnitude;
}
}
}
}
@ -29,21 +44,20 @@ namespace UniVRM10.ClothWarp.Jobs
public struct WeftConstraintJob : IJobParallelFor
{
public float Hookean;
FrameInfo Frame;
[ReadOnly] public NativeArray<(SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> Force;
[NativeDisableParallelForRestriction] public NativeArray<Vector3> ImpulsiveForces;
public void Execute(int rectIndex)
{
var (spring, rect) = ClothRects[rectIndex];
var (clothGridIndex, 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;
var dx = (p1 - p0).normalized * f;
ImpulsiveForces[spring._p0] += dx;
ImpulsiveForces[spring._p1] -= dx;
}
}
@ -60,7 +74,7 @@ namespace UniVRM10.ClothWarp.Jobs
public void Execute(int warpIndex)
{
var warp = Warps[warpIndex];
for (int particleIndex = warp.StartIndex; particleIndex < warp.EndIndex - 1; ++particleIndex)
for (int particleIndex = warp.PrticleRange.Start; particleIndex < warp.PrticleRange.End - 1; ++particleIndex)
{
//回転を適用
var p = Info[particleIndex];

View File

@ -1,66 +1,52 @@
using System;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct TransformInfo
/// <summary>
/// UniGLTF.SpringBoneJobs.Blittables.BlittableJointMutable と同じ。
/// Range が違う。
/// </summary>
[Serializable]
public struct ParticleSettings
{
public TransformType TransformType;
public int ParentIndex;
public Quaternion InitLocalRotation;
public Vector3 InitLocalPosition;
public BlittableJointMutable Settings;
}
[Range(0, 1)]
public float Stiffness;
[Range(0, 1)]
public float Deceleration;
public Vector3 Gravity;
public float Radius;
public struct TransformData
{
public Matrix4x4 ToWorld;
public Vector3 Position => ToWorld.GetPosition();
public Quaternion Rotation => ToWorld.rotation;
public Matrix4x4 ToLocal;
public TransformData(TransformAccess t)
public static readonly ParticleSettings Default = new ParticleSettings
{
ToWorld = t.localToWorldMatrix;
ToLocal = t.worldToLocalMatrix;
Stiffness = 0.08f,
Gravity = new Vector3(0, -1.0f, 0),
Deceleration = 0.4f,
Radius = 0.02f,
};
public void FromBlittableJointMutable(BlittableJointMutable src)
{
Stiffness = src.stiffnessForce;
Gravity = src.gravityDir * src.gravityPower;
Deceleration = src.dragForce;
Radius = src.radius;
}
public TransformData(Transform t)
public BlittableJointMutable ToBlittableJointMutable()
{
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())
return new BlittableJointMutable
{
// only warp root position update
CurrentPositions[particleIndex] = transform.position;
}
// clear cloth
CollisionCount[particleIndex] = 0;
CollisionDelta[particleIndex] = Vector3.zero;
Forces[particleIndex] = Vector3.zero;
stiffnessForce = Stiffness,
gravityPower = 1.0f,
gravityDir = Gravity,
dragForce = Deceleration,
radius = Radius,
};
}
}
@ -69,7 +55,7 @@ namespace UniVRM10.ClothWarp.Jobs
public FrameInfo Frame;
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<TransformData> CurrentTransforms;
[ReadOnly] public NativeArray<Vector3> Forces;
[ReadOnly] public NativeArray<Vector3> ImpulsiveForces;
[ReadOnly] public NativeArray<Vector3> CurrentPositions;
[ReadOnly] public NativeArray<Vector3> PrevPositions;
[WriteOnly] public NativeArray<Vector3> NextPositions;
@ -80,18 +66,19 @@ namespace UniVRM10.ClothWarp.Jobs
var particle = Info[particleIndex];
if (particle.TransformType.Movable())
{
var parentIndex = particle.ParentIndex;
// var parentPosition = CurrentPositions[parentIndex];
var parent = Info[parentIndex];
var parent = Info[particle.ParentIndex];
var parentParentRotation = CurrentTransforms[parent.ParentIndex].Rotation;
var external = (particle.Settings.gravityDir * particle.Settings.gravityPower + Frame.Force) * Frame.DeltaTime;
var local_rest = parentParentRotation * parent.InitLocalRotation * particle.InitLocalPosition;
var world_rest = CurrentPositions[particle.ParentIndex] + local_rest;
var resilience_force = world_rest - CurrentPositions[particleIndex];
var velocity = (CurrentPositions[particleIndex] - PrevPositions[particleIndex]) * (1.0f - particle.Settings.Deceleration);
var newPosition = CurrentPositions[particleIndex]
+ (CurrentPositions[particleIndex] - PrevPositions[particleIndex]) * (1.0f - particle.Settings.dragForce)
+ parentParentRotation * parent.InitLocalRotation * particle.InitLocalPosition *
particle.Settings.stiffnessForce * Frame.DeltaTime // 親の回転による子ボーンの移動目標
+ external
+ velocity
+ ImpulsiveForces[particleIndex]
+ resilience_force * particle.Settings.Stiffness
+ (particle.Settings.Gravity + Frame.Force) * Frame.SqDeltaTime
;
NextPositions[particleIndex] = newPosition;
@ -105,19 +92,4 @@ namespace UniVRM10.ClothWarp.Jobs
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,242 @@
using System;
using SphereTriangle;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace UniVRM10.ClothWarp.Jobs
{
public struct RectCollisionJob : IJobParallelFor
{
// cloth
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[WriteOnly] public NativeArray<(Vector3, Vector3, Vector3, Vector3)> ClothRectResults;
// collider
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[ReadOnly] public NativeArray<Matrix4x4> CurrentColliders;
// particle
[ReadOnly] public NativeArray<TransformInfo> Info;
[ReadOnly] public NativeArray<Vector3> NextPositions;
// collider group
[ReadOnly] public NativeArray<ClothInfo> Cloths;
[ReadOnly] public NativeArray<int> ColliderGroupRef;
[ReadOnly] public NativeArray<ArrayRange> ColliderGroup;
[ReadOnly] public NativeArray<int> ColliderRef;
public void Execute(int rectIndex)
{
var (clothGridIndex, spring, rect) = ClothRects[rectIndex];
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);
var cloth = Cloths[clothGridIndex];
for (int groupRefIndex = cloth.ColliderGroupRefRange.Start; groupRefIndex < cloth.ColliderGroupRefRange.End; ++groupRefIndex)
{
var groupIndex = ColliderGroupRef[groupRefIndex];
var group = ColliderGroup[groupIndex];
for (int colliderRefIndex = group.Start; colliderRefIndex < group.End; ++colliderRefIndex)
{
var colliderIndex = ColliderRef[colliderRefIndex];
var collider = Colliders[colliderIndex];
var collider_matrix = CurrentColliders[collider.transformIndex];
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;
// }
var abc = TryCollide(collider, collider_matrix, _triangle0, out var l0);
var cda = TryCollide(collider, collider_matrix, _triangle1, out var l1);
if (!Info[rect._c].TransformType.Movable())
{
// cloth の上端。cd が固定
if (abc)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
}
else if (cda)
{
a += l1.GetDelta(collider.radius);
b += l1.GetDelta(collider.radius);
}
}
else
{
if (abc && cda)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
c += l1.GetDelta(collider.radius);
d += l1.GetDelta(collider.radius);
}
else if (abc)
{
a += l0.GetDelta(collider.radius);
b += l0.GetDelta(collider.radius);
c += l0.GetDelta(collider.radius);
}
else if (cda)
{
c += l1.GetDelta(collider.radius);
d += l1.GetDelta(collider.radius);
a += l1.GetDelta(collider.radius);
}
}
}
}
ClothRectResults[rectIndex] = (a, b, c, d);
}
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 RectCollisionReduceJob : IJob
{
[ReadOnly] public NativeArray<(int, SpringConstraint, ClothRect)> ClothRects;
[ReadOnly] public NativeArray<(Vector3, Vector3, Vector3, Vector3)> ClothRectResults;
[ReadOnly] public NativeArray<Vector3> NextPositions;
public NativeArray<int> RectCollisionCount;
public NativeArray<Vector3> RectCollisionDelta;
public void Execute()
{
for (int rectIndex = 0; rectIndex < ClothRects.Length; ++rectIndex)
{
var (clothGridIndex, spring, rect) = ClothRects[rectIndex];
var (a, b, c, d) = ClothRectResults[rectIndex];
if (a != NextPositions[rect._a])
{
RectCollisionDelta[rect._a] += a - NextPositions[rect._a];
RectCollisionCount[rect._a] += 1;
}
if (b != NextPositions[rect._b])
{
RectCollisionDelta[rect._b] += b - NextPositions[rect._b];
RectCollisionCount[rect._b] += 1;
}
if (c != NextPositions[rect._c])
{
RectCollisionDelta[rect._c] += c - NextPositions[rect._c];
RectCollisionCount[rect._c] += 1;
}
if (d != NextPositions[rect._d])
{
RectCollisionDelta[rect._d] += d - NextPositions[rect._d];
RectCollisionCount[rect._d] += 1;
}
}
}
}
}

View File

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

View File

@ -0,0 +1,86 @@
using Unity.Collections;
using UnityEngine;
using UnityEngine.Jobs;
namespace UniVRM10.ClothWarp.Jobs
{
public struct BranchInfo
{
public int FirstSiblingIndex;
}
public struct TransformInfo
{
public TransformType TransformType;
public int ParentIndex;
public Quaternion InitLocalRotation;
public Vector3 InitLocalPosition;
public ParticleSettings Settings;
public int WarpIndex;
public BranchInfo? Branch;
}
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> ImpulsiveForces;
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;
ImpulsiveForces[particleIndex] = Vector3.zero;
}
}
// [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: 5f3379ab5e1202d43a95e72ece6bda3a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,8 +1,14 @@
namespace UniVRM10.ClothWarp.Jobs
{
public struct ArrayRange
{
public int Start;
public int End;
}
public struct WarpInfo
{
public int StartIndex;
public int EndIndex;
public ArrayRange PrticleRange;
public ArrayRange ColliderGroupRefRange;
}
}

View File

@ -0,0 +1,73 @@
using SphereTriangle;
using UniGLTF.SpringBoneJobs.Blittables;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
namespace UniVRM10.ClothWarp.Jobs
{
public struct WarpCollisionJob : 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;
// collider group
[ReadOnly] public NativeArray<WarpInfo> Warps;
[ReadOnly] public NativeArray<int> ColliderGroupRef;
[ReadOnly] public NativeArray<ArrayRange> ColliderGroup;
[ReadOnly] public NativeArray<int> ColliderRef;
public void Execute(int particleIndex)
{
if (
// cloth でない
!ClothUsedParticles[particleIndex]
// 枝のjointでない
&& !Info[particleIndex].Branch.HasValue
)
{
var info = Info[particleIndex];
var pos = NextPositions[particleIndex];
var warp = Warps[info.WarpIndex];
for (int groupRefIndex = warp.ColliderGroupRefRange.Start; groupRefIndex < warp.ColliderGroupRefRange.End; ++groupRefIndex)
{
var groupIndex = ColliderGroupRef[groupRefIndex];
var group = ColliderGroup[groupIndex];
for (int colliderRefIndex = group.Start; colliderRefIndex < group.End; ++colliderRefIndex)
{
var colliderIndex = ColliderRef[colliderRefIndex];
var c = Colliders[colliderIndex];
var m = CurrentColliders[c.transformIndex];
if (c.colliderType == BlittableColliderType.Capsule)
{
if (SphereSphereCollision.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 (SphereSphereCollision.TryCollideSphereAndSphere(m.MultiplyPoint(c.offset), c.radius,
pos, info.Settings.Radius, out var l))
{
pos += l.GetDelta(c.radius);
}
}
}
}
StrandCollision[particleIndex] = pos;
}
}
}
}

View File

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

View File

@ -23,7 +23,7 @@ namespace UniVRM10.ClothWarp
foreach (var particle in warp.Particles)
{
var child_index = _MakeAParticle(joint, env, particle.Transform,
particle.Settings.radius, 1);
particle.Settings.Radius, 1);
var child = _particles[child_index];
strand.Particles.Add(child);
joint.Children.Add(child);

View File

@ -230,19 +230,19 @@ namespace SphereTriangle
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);
// 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
// }
// #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)
// {

View File

@ -0,0 +1,74 @@
using UnityEngine;
namespace SphereTriangle
{
public static class SphereSphereCollision
{
/// <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>
public 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>
public static bool TryCollideCapsuleAndSphere(
in Vector3 capsuleHead,
in Vector3 capsuleTail,
float capsuleRadius,
in Vector3 b,
float rb,
out LineSegment resolved
)
{
var P = (capsuleTail - capsuleHead);
var Q = b - capsuleHead;
var dot = Vector3.Dot(P.normalized, 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);
}
}
}

View File

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