diff --git a/Assets/VRM10_Samples/ClothSample.meta b/Assets/VRM10_Samples/ClothSample.meta new file mode 100644 index 000000000..f64df945b --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6871c022e581b3742bbc892f38900607 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle.meta new file mode 100644 index 000000000..453904a79 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a00a3e7f5bad4ae4cbbf0e2acc739fa8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef b/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef new file mode 100644 index 000000000..023d57489 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef @@ -0,0 +1,16 @@ +{ + "name": "RotateParticle", + "rootNamespace": "", + "references": [ + "GUID:0138a2c7639266f45b948ea162125de5" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef.meta new file mode 100644 index 000000000..8aae2c5d4 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/RotateParticle.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 308b348fb80d89d42a9620951b0f60db +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime.meta new file mode 100644 index 000000000..5fa557c3f --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2ba7e498c4d5eef44aba4cf125686089 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs new file mode 100644 index 000000000..6a998a38f --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs @@ -0,0 +1,13 @@ +namespace RotateParticle +{ + public struct FrameTime + { + public readonly float DeltaTime; + public readonly float SqDt; + public FrameTime(float deltaTime) + { + DeltaTime = deltaTime; + SqDt = deltaTime * deltaTime; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs.meta new file mode 100644 index 000000000..be6af65f6 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/FrameTime.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 568e5702a44c18d4487775441e011a61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs new file mode 100644 index 000000000..622a34a78 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs @@ -0,0 +1,233 @@ +using System.Collections.Generic; +using System.Linq; +using SphereTriangle; +using UnityEngine; + + +namespace RotateParticle +{ + public class HumanoidAutoSetup : MonoBehaviour + { + 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), + }; + + T GetOrAddComponent() where T : Component + { + var t = GetComponent(); + if (t != null) + { + return t; + } + return gameObject.AddComponent(); + } + + bool TryAddGroup(CollisionGroupMask mask, string name, Animator animator, HumanBodyBones humanBone, string[] targets, StrandConnectionType type, + out StrandGroup group) + { + var bone = animator.GetBoneTransform(humanBone); + if (bone == null) + { + Debug.LogWarning($"{humanBone} not found"); + group = default; + return false; + } + + List transforms = new(); + foreach (Transform child in bone) + { + foreach (var target in targets) + { + if (child.name.ToLower().Contains(target.ToLower())) + { + transforms.Add(child); + break; + } + } + } + if (transforms.Count == 0) + { + Debug.LogWarning($"{string.Join(',', targets)} not found"); + group = default; + return false; + } + + group = new StrandGroup + { + Name = name, + CollisionMask = mask, + DefaultStrandRaius = 0.02f, + Connection = type + }; + group.Roots.AddRange(transforms); + + // sort + group.Roots.Sort(new TransformSort(bone.position)); + return true; + } + + bool TryAddGroupChildChild(CollisionGroupMask mask, string name, Animator animator, HumanBodyBones humanBone, + string[] targets, string[] excludes, + StrandConnectionType type, + bool sort, + out StrandGroup group) + { + var bone = animator.GetBoneTransform(humanBone); + if (bone == null) + { + Debug.LogWarning($"{humanBone} not found"); + group = default; + return false; + } + + List 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())) + { + transforms.Add(childchild); + break; + } + } + } + } + if (transforms.Count == 0) + { + Debug.LogWarning($"{string.Join(',', targets)} not found"); + group = default; + return false; + } + + group = new StrandGroup + { + Name = name, + CollisionMask = mask, + DefaultStrandRaius = 0.02f, + Connection = type + }; + group.Roots.AddRange(transforms); + + // sort + if (sort) + { + group.Roots.Sort(new TransformSort(bone.position)); + } + return true; + } + + public void Reset() + { + var animator = GetComponent(); + if (animator == null) + { + Debug.LogWarning("no animator"); + return; + } + var avatar = animator.avatar; + if (!avatar.isHuman) + { + Debug.LogWarning("not humanoid"); + return; + } + + var system = GetOrAddComponent(); + system.Env.DragForce = 0.6f; + system.Env.Stiffness = 0.07f; + + foreach (var (group, head, tail, radius) in Capsules) + { + system.AddColliderIfNotExists(group, + animator.GetBoneTransform(head), animator.GetBoneTransform(tail), radius); + } + + // skirt + { + if (TryAddGroup(CollisionGroupMask.Group01, "Skirt", animator, HumanBodyBones.Hips, new[] { "skirt", "スカート", "スカート" }, StrandConnectionType.ClothLoop, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroupChildChild(CollisionGroupMask.Group01, "Skirt", animator, HumanBodyBones.Hips, new[] { "skirt", "スカート", "スカート" }, new string[]{}, StrandConnectionType.ClothLoop, false, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroup(CollisionGroupMask.Group02, "髪", animator, HumanBodyBones.Head, new[] { "髪", "hair" }, StrandConnectionType.Strand, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroup(CollisionGroupMask.Group01, "裾", animator, HumanBodyBones.Hips, new[] { "裾" }, StrandConnectionType.Cloth, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroupChildChild(CollisionGroupMask.Group02, "左袖", animator, HumanBodyBones.LeftUpperArm, new[] { "袖" }, new[] { "ひじ袖" }, StrandConnectionType.ClothLoop, + false, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroupChildChild(CollisionGroupMask.Group02, "左ひじ袖", animator, HumanBodyBones.LeftLowerArm, new[] { "袖" }, new string[] { }, StrandConnectionType.ClothLoop, + false, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroupChildChild(CollisionGroupMask.Group02, "右袖", animator, HumanBodyBones.RightUpperArm, new[] { "袖" }, new[] { "ひじ袖" }, StrandConnectionType.ClothLoop, + false, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroupChildChild(CollisionGroupMask.Group02, "右ひじ袖", animator, HumanBodyBones.RightLowerArm, new[] { "袖" }, new string[] { }, StrandConnectionType.ClothLoop, + false, + out var g)) + { + system._strandGroups.Add(g); + } + } + { + if (TryAddGroup(CollisionGroupMask.Group01 | CollisionGroupMask.Group02, "マント", animator, HumanBodyBones.Chest, new[] { "マント" }, StrandConnectionType.Cloth, + out var g)) + { + system._strandGroups.Add(g); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs.meta new file mode 100644 index 000000000..7bc096b42 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidAutoSetup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2c23a62e016b3244b286404bc339540 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs new file mode 100644 index 000000000..56362c7d3 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace RotateParticle +{ + 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); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs.meta new file mode 100644 index 000000000..cdf78bb99 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/HumanoidPose.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 500c97fb71417394e8144b008b462e6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs new file mode 100644 index 000000000..48c2f897b --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs @@ -0,0 +1,49 @@ +using System; +using SphereTriangle; +using UnityEngine; + +namespace RotateParticle +{ + [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 CollisionGroupMask CollisionMask; + + public ParticleInitState(int index, Transform t, float radius, float mass, CollisionGroupMask collisionMask) + { + Index = index; + LocalPosition = t.localPosition; + LocalRotation = t.localRotation; + StrandLength = LocalPosition.magnitude; + BoneAxis = LocalPosition.normalized; + Radius = radius; + Mass = mass; + CollisionMask = collisionMask; + } + + public override string ToString() + { + return $"create Particle: {LocalPosition}, {BoneAxis}, {StrandLength}"; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs.meta new file mode 100644 index 000000000..d4999be02 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleInitState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4bccf867d0bc7b244841c8ecff9ce65a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs new file mode 100644 index 000000000..0b235f0c2 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs @@ -0,0 +1,81 @@ +using Unity.Collections; +using Unity.Jobs; +using UnityEngine; + +namespace RotateParticle +{ + public struct ParticleJob : IJob + { + public float DeltaTime; + public float Stiffness; + public float DragForce; + public Vector3 External; + + public int Index; + public int ParentIndex; + + public float Mass; + public Vector3 Rest; + public Vector3 CurrentPosition; + public Vector3 Prev; + + public Quaternion ParentParentRotation; + public Quaternion InitParentLocalRotation; + public Vector3 InitBoneAxis; + public Vector3 ParentPosition; + + public NativeArray NewPos; + public NativeArray NewRot; + + public void Execute() + { + var tr = Step(); + if (tr.HasValue) + { + var (newPos, parentRot) = tr.Value; + NewPos[Index] = newPos; + NewRot[ParentIndex] = parentRot; + } + } + + public (Vector3 NewPos, Quaternion ParentRotation)? Step() + { + if (Mass == 0) + { + return default; + } + + var sqDt = DeltaTime * DeltaTime; + + var f = (Rest - CurrentPosition) * Stiffness / sqDt; + // Debug.Log($"{rest}"); + + f += (External / sqDt); + + var newPos = CurrentPosition + (CurrentPosition - Prev) * (1 - DragForce) + f * sqDt; + + var parentRotation = CalcParentRotation(newPos, ParentParentRotation, InitParentLocalRotation, InitBoneAxis, ParentPosition); + + return (newPos, parentRotation); + } + + public static Quaternion CalcParentRotation(in Vector3 newPos, + in Quaternion parentparent, + in Quaternion initParentLocalRotation, + in Vector3 initBoneAxis, + in Vector3 parentPosition) + { + // 親の回転として結果を適用する(位置から回転を作る) + var restRotation = parentparent * initParentLocalRotation; + var r = CalcRotation(restRotation, initBoneAxis, newPos - parentPosition); + // _runtime = new ParticleRuntimeState(_runtime.CurrentPosition, transform.position); + return r; + } + + static Quaternion CalcRotation(Quaternion restRotation, Vector3 boneAxis, Vector3 to) + { + Quaternion aimRotation = Quaternion.FromToRotation(restRotation * boneAxis, to); + return aimRotation * restRotation; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs.meta new file mode 100644 index 000000000..08d8ff997 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleJob.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7127f077eba307343a5f5ce15213a595 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs new file mode 100644 index 000000000..de7babbed --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using SphereTriangle; +using UnityEngine; + + +namespace RotateParticle +{ + [Serializable] + public class ParticleList + { + public List _particles = new(); + public List _particleTransforms = new(); + + public Strand MakeParticleStrand(SimulationEnv env, Transform t, float radius, CollisionGroupMask mask) + { + var strand = new Strand(mask); + _MakeParticlesRecursive(strand, env, 0, t, radius, null); + return strand; + } + + RotateParticle _MakeParticlesRecursive(Strand strand, SimulationEnv env, int child_index, Transform t, float radius, RotateParticle parent) + { + var mass = parent == null ? 0 : 1; + if (child_index > 0) + { + // 枝分かれ動かない + mass = 0; + } + + var particle_index = _MakeAParticle(env, t, radius, parent, mass, strand.CollisionMask); + var joint = _particles[particle_index]; + strand.Particles.Add(joint); + for (int i = 0; i < t.childCount; ++i) + { + var child = t.GetChild(i); + var child_joint = _MakeParticlesRecursive(strand, env, i, child, radius, joint); + joint.Children.Add(child_joint); + } + return joint; + } + + int _MakeAParticle(SimulationEnv env, Transform t, float radius, RotateParticle parent, float mass, CollisionGroupMask collisionMask) + { + var index = _particles.Count; + _particleTransforms.Add(t); + _particles.Add(new RotateParticle(index, parent, env, t, radius, mass, collisionMask)); + 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 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]); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs.meta new file mode 100644 index 000000000..062930b59 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fee618dfb5846ea47978b49ed4c0282b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs new file mode 100644 index 000000000..1eed46f94 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs @@ -0,0 +1,42 @@ +using System; +using UnityEngine; + +namespace RotateParticle +{ + 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; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs.meta new file mode 100644 index 000000000..1a193c839 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/ParticleRuntimeState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eddc8c1b804734c49a6eb04d72afb0a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs new file mode 100644 index 000000000..e3a2bcdd1 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +namespace RotateParticle +{ + 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; + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs.meta new file mode 100644 index 000000000..542915cf0 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RigidTransform.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1fe036d9e9352e54ea009e8322a4faa2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs new file mode 100644 index 000000000..b6ddbb481 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using SphereTriangle; +using UnityEngine; + + +namespace RotateParticle +{ + [Serializable] + public class RotateParticle + { + public ParticleInitState Init; + public ParticleRuntimeState State; + + public readonly List Children = new(); + public readonly RotateParticle Parent; + + // 現フレームの力積算 + public Vector3 Force = Vector3.zero; + + // 直前で接触があった + public bool HasCollide = false; + + public RotateParticle(int index, RotateParticle parent, SimulationEnv env, Transform transform, float radius, float mass, CollisionGroupMask collisionMask) + { + Init = new ParticleInitState(index, transform, radius, mass, collisionMask); + 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; + } + + /// + /// get ParentParent.rotatio * Parent.Init.LocalRotation + /// + /// + /// + public Quaternion RestRotation(IReadOnlyList 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); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs.meta new file mode 100644 index 000000000..9afb92224 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 125347015f2366e4b8957431407ddf9d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs new file mode 100644 index 000000000..e0f8ef2d7 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using SphereTriangle; +using UnityEngine; + + +namespace RotateParticle +{ + [DisallowMultipleComponent] + public class RotateParticleSystem : MonoBehaviour + { + // TODO: param to _strandGroups + [SerializeField] + public SimulationEnv Env = new(); + + [SerializeField] + public List _strandGroups = new List(); + + [SerializeField] + public List _colliderGroups = new(); + + [Range(0, 1)] + public float _clothFactor = 0.5f; + + // runtime + bool _initialized = false; + List _strands = new List(); + public ParticleList _list = new(); + public List<(SpringConstraint, ClothRectCollision)> _clothRects = new(); + + public PositionList _newPos; + Vector3[] _restPositions; + + static Color[] Colors = new Color[] + { + Color.yellow, + Color.green, + Color.magenta, + }; + + Color GetGizmoColor(ColliderGroup g) + { + for (int i = 0; i < _colliderGroups.Count; ++i) + { + if (_colliderGroups[i] == g) + { + return Colors[i]; + } + } + + return Color.gray; + } + + public void AddColliderIfNotExists(string groupName, Transform head, Transform tail, float radius) + { + ColliderGroup group = default; + foreach (var g in _colliderGroups) + { + if (g.Name == groupName) + { + group = g; + break; + } + } + if (group == null) + { + group = new ColliderGroup { Name = groupName }; + _colliderGroups.Add(group); + } + + foreach (var collider in group.Colliders) + { + if (collider.transform == head) + { + return; + } + } + + var c = GetOrAddComponent(head.gameObject); + c.Tail = tail; + c.Radius = radius; + c.GizmoColor = GetGizmoColor(group); + group.Colliders.Add(c); + } + + static T GetOrAddComponent(GameObject o) where T : Component + { + var t = o.GetComponent(); + if (t != null) + { + return t; + } + return o.AddComponent(); + } + + HashSet _clothUsedParticles = new(); + + public void InitializeCloth( + StrandGroup g, + ParticleList list, + List strands, + List<(SpringConstraint, ClothRectCollision)> clothRects) + { + if (g.Connection == StrandConnectionType.Cloth || g.Connection == StrandConnectionType.ClothLoop) + { + for (int i = 1; i < strands.Count; ++i) + { + var s0 = strands[i - 1]; + var s1 = strands[i]; + for (int j = 1; j < s0.Particles.Count && j < s1.Particles.Count; ++j) + { + // d x x c + // | | + // a x-x b + var a = s0.Particles[j]; + var b = s1.Particles[j]; + var c = s1.Particles[j - 1]; + var d = s0.Particles[j - 1]; + _clothUsedParticles.Add(a.Init.Index); + _clothUsedParticles.Add(b.Init.Index); + _clothUsedParticles.Add(c.Init.Index); + _clothUsedParticles.Add(d.Init.Index); + if (i % 2 == 1) + { + // 互い違いに + // abcd to badc + (a, b) = (b, a); + (c, d) = (d, c); + } + clothRects.Add(( + new SpringConstraint( + list._particles[a.Init.Index], + list._particles[b.Init.Index]), + new ClothRectCollision( + a.Init.Index, b.Init.Index, c.Init.Index, d.Init.Index))); + } + } + if (strands.Count >= 3) + { + if (g.Connection == StrandConnectionType.ClothLoop) + { + var i = strands.Count; + var s0 = strands.Last(); + var s1 = strands.First(); + for (int j = 1; j < s0.Particles.Count && j < s1.Particles.Count; ++j) + { + var a = s0.Particles[j]; + var b = s1.Particles[j]; + var c = s1.Particles[j - 1]; + var d = s0.Particles[j - 1]; + _clothUsedParticles.Add(a.Init.Index); + _clothUsedParticles.Add(b.Init.Index); + _clothUsedParticles.Add(c.Init.Index); + _clothUsedParticles.Add(d.Init.Index); + if (i % 2 == 1) + { + // 互い違いに + // abcd to badc + (a, b) = (b, a); + (c, d) = (d, c); + } + clothRects.Add(( + new SpringConstraint( + list._particles[a.Init.Index], + list._particles[b.Init.Index]), + new ClothRectCollision( + a.Init.Index, b.Init.Index, c.Init.Index, d.Init.Index + ) + )); + } + } + } + } + } + + public void Initialize() + { + _initialized = true; + foreach (var g in _strandGroups) + { + if (g.Roots?.Count == 0) + { + continue; + } + var strands = new List(); + foreach (var root in g.Roots) + { + var strand = _list.MakeParticleStrand(Env, root, g.DefaultStrandRaius, g.CollisionMask); + strands.Add(strand); + } + + InitializeCloth(g, _list, strands, _clothRects); + _strands.AddRange(strands); + } + + _newPos = new(_list._particles.Count); + _list.EndInitialize(_newPos.Init); + _restPositions = new Vector3[_list._particles.Count]; + _newPos.EndInitialize(); + + foreach (var (s, c) in _clothRects) + { + c.InitializeColliderSide(_newPos, _colliderGroups); + } + } + + void Start() + { + if (!_initialized) + { + Initialize(); + } + } + + /// + /// すべての Particle を Init 状態にする。 + /// Verlet の Prev を現在地に更新する(速度0)。 + /// + public void ResetParticle() + { + foreach (var strand in _strands) + { + strand.Reset(_list._particleTransforms); + } + } + + public void Update() + { + if (Time.deltaTime == 0) + { + return; + } + Process(Time.deltaTime); + } + + public void Process(float deltaTime) + { + using var profile = new ProfileSample("RotateParticle"); + + 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) + { + // cloth constraint + spring.Resolve(time, _clothFactor); + } + _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]; + + + foreach (var (spring, rect) in _clothRects) + { + using var prof = new ProfileSample("Collision: Cloth"); + // 頂点 abcd は同じ CollisionMask + if (_list._particles[rect._a].Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1))) + { + // cloth + rect.Collide(_newPos, g.Colliders); + } + } + + for (int j = 0; j < _list._particles.Count; ++j) + { + using var prof = new ProfileSample("Collision: Strand"); + if (_clothUsedParticles.Contains(j)) + { + // 布で処理された + continue; + } + + var particle = _list._particles[j]; + if (particle.Init.Mass == 0) + { + continue; + } + + // 紐の当たり判定 + if (particle.Init.CollisionMask.HasFlag((CollisionGroupMask)(i + 1))) + { + var p = _newPos.Get(j); + foreach (var c in g.Colliders) + { + // strand + if (c != null && c.TryCollide(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); + } + } + } + + void OnDrawGizmos() + { + _list.DrawGizmos(); + + foreach (var (spring, rect) in _clothRects) + { + rect.DrawGizmos(); + } + + if (_newPos != null) + { + _newPos.DrawGizmos(); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs.meta new file mode 100644 index 000000000..5bf73e159 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/RotateParticleSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67a32b7c5e169f64daa6a8717d3de4f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs new file mode 100644 index 000000000..94728205f --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs @@ -0,0 +1,18 @@ +using UnityEngine; + +namespace RotateParticle +{ + [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); + } +} diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs.meta new file mode 100644 index 000000000..3eb703729 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SimulationEnv.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c9c034f40b6adaf4ba23f456fd5a2ec1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs new file mode 100644 index 000000000..cec219e18 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs @@ -0,0 +1,41 @@ +using System; +using UnityEngine; + +namespace RotateParticle +{ + public class SpringConstraint + { + RotateParticle _p0; + + RotateParticle _p1; + + // 初期長さ + float _rest; + + public SpringConstraint(RotateParticle p0, RotateParticle p1) + { + _p0 = p0; + _p1 = p1; + _rest = Vector3.Distance(p0.State.Current, p1.State.Current); + } + + /// + /// フックの法則 + /// + /// + public void Resolve(FrameTime time, float hookean) + { + var d = Vector3.Distance(_p0.State.Current, _p1.State.Current); + var f = (d - _rest) * hookean; + var dx = (_p1.State.Current - _p0.State.Current).normalized * f / time.SqDt; + + _p0.Force += dx; + _p1.Force -= dx; + } + + public void DrawGizmo() + { + Gizmos.DrawLine(_p0.State.Current, _p1.State.Current); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs.meta new file mode 100644 index 000000000..dfb815753 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/SpringConstraint.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f9f187b56235e024bb4edef8b3bb2142 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs new file mode 100644 index 000000000..a36b1178b --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs @@ -0,0 +1,165 @@ +using System.Collections.Generic; +using SphereTriangle; +using UnityEngine; + +namespace RotateParticle +{ + public class Strand + { + public CollisionGroupMask CollisionMask; + + public Strand(CollisionGroupMask mask) + { + CollisionMask = mask; + } + + public readonly List Particles = new(); + + public void UpdateRoot(IReadOnlyList transforms, SphereTriangle.PositionList positions, Vector3[] restPositions) + { + var root = Particles[0]; + + // only root affected + var t = transforms[root.Init.Index]; + root.State.Update(t); + positions.Init(root.Init.Index, root.Init.Mass, t.position); + + // Debug.Assert(root.Children.Count == 1); + foreach (var child in root.Children) + { + _CalcRest(transforms, positions.Result, restPositions, root.Children[0]); + } + } + + void _CalcRest(IReadOnlyList transforms, IReadOnlyList positions, + Vector3[] restPositions, + RotateParticle particle) + { + var restRotation = particle.RestRotation(transforms); + + + if (particle.Parent != null) + { + // var localPosition = particle.Init.BoneAxis * Vector3.Distance(positions[particle.Parent.Init.Index], positions[particle.Init.Index]); + restPositions[particle.Init.Index] = positions[particle.Parent.Init.Index] + restRotation * particle.Init.LocalPosition; + } + else + { + restPositions[particle.Init.Index] = restRotation * particle.Init.LocalPosition; + } + + foreach (var child in particle.Children) + { + _CalcRest(transforms, positions, restPositions, child); + } + } + + public void ForceLength(IReadOnlyList transforms, PositionList positions) + { + var root = Particles[0]; + for (int i = 0; i < root.Children.Count; ++i) + { + _ForceConstraint(transforms, positions, i, root.Children[i], positions.Get(root.Init.Index)); + } + } + + void _ForceConstraint(IReadOnlyList transforms, PositionList positions, int child_index, RotateParticle particle, in Vector3 parent) + { + // update position + Vector3 newPosition; + if (child_index == 0) + { + newPosition = parent + (positions.Get(particle.Init.Index) - parent).normalized * particle.Init.StrandLength; + } + else + { + // 枝分かれ。特別処理 + var firstSibling = particle.Parent.Children[0]; + var firstPosition = positions.Get(firstSibling.Init.Index); + newPosition = firstPosition + transforms[particle.Parent.Init.Index].rotation * (particle.Init.LocalPosition - firstSibling.Init.LocalPosition); + } + positions.Init(particle.Init.Index, particle.Init.Mass, newPosition); + + for (int i = 0; i < particle.Children.Count; ++i) + { + _ForceConstraint(transforms, positions, i, particle.Children[i], newPosition); + } + } + + public void Reset(IReadOnlyList transforms) + { + var root = Particles[0]; + _ResetRecursive(transforms, root); + } + + void _ResetRecursive(IReadOnlyList transforms, + RotateParticle joint) + { + var t = transforms[joint.Init.Index]; + t.localPosition = joint.Init.LocalPosition; + t.localRotation = joint.Init.LocalRotation; + joint.State.Apply(t.position, zeroVelocity: true); + for (int i = 0; i < joint.Children.Count; ++i) + { + _ResetRecursive(transforms, joint.Children[i]); + } + } + + public void Apply(IReadOnlyList transforms, IReadOnlyList positions) + { + var root = Particles[0]; + for (int i = 0; i < root.Children.Count; ++i) + { + var child = root.Children[i]; + _ApplyRecursive(transforms, positions, i, child); + } + } + + void _ApplyRecursive(IReadOnlyList transforms, IReadOnlyList positions, + int child_index, RotateParticle joint) + { + var t = transforms[joint.Init.Index]; + + if (child_index > 0) + { + // 枝分かれ。mass=0 にもしている。 + // self position + t.position = positions[joint.Init.Index]; + joint.State.Apply(t.position); + } + else if (joint.Init.Mass > 0) + { + Debug.Assert(joint.Parent != null); + var restRotation = joint.RestRotation(transforms); + + Quaternion aimRotation = Quaternion.FromToRotation( + // 初期状態 + restRotation * joint.Init.BoneAxis, + // 現状 + positions[joint.Init.Index] - positions[joint.Parent.Init.Index]); + + var r = aimRotation * restRotation; + + // parent rotation + transforms[joint.Parent.Init.Index].rotation = r; + + // self position + t.position = positions[joint.Init.Index]; + joint.State.Apply(t.position); + } + + if (joint.Children.Count > 0) + { + for (int i = 0; i < joint.Children.Count; ++i) + { + _ApplyRecursive(transforms, positions, i, joint.Children[i]); + } + } + else + { + // tail + t.rotation = t.parent.rotation * joint.Init.LocalRotation; + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs.meta new file mode 100644 index 000000000..a9bafb7c1 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Runtime/Strand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d9aee349d7a2c2344a232a0e158f57dc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes.meta new file mode 100644 index 000000000..20e52fe13 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cfa6b58a8a8571e478cf6bac4a00a23d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity new file mode 100644 index 000000000..6a9a31a28 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity @@ -0,0 +1,1756 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &18281988 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 18281990} + - component: {fileID: 18281989} + - component: {fileID: 18281991} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &18281989 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 18281988} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &18281990 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 18281988} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!114 &18281991 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 18281988} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Version: 1 + m_UsePipelineSettings: 1 + m_AdditionalLightsShadowResolutionTier: 2 + m_LightLayerMask: 1 + m_CustomShadowLayers: 0 + m_ShadowLayerMask: 1 + m_LightCookieSize: {x: 1, y: 1} + m_LightCookieOffset: {x: 0, y: 0} +--- !u!1 &89594358 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 89594359} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &89594359 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 89594358} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 764167383} + m_Father: {fileID: 1795237625} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &254332538 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 254332539} + m_Layer: 0 + m_Name: p00 (3) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &254332539 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 254332538} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.5, y: -0.5, z: -0.017} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2079542329} + m_Father: {fileID: 1776840502} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &378890423 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 378890424} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &378890424 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 378890423} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 495122916} + m_Father: {fileID: 510828803} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &495122915 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 495122916} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &495122916 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 495122915} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 567596718} + m_Father: {fileID: 378890424} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &510828802 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 510828803} + m_Layer: 0 + m_Name: p00 (2) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &510828803 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 510828802} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.5, y: -0.5, z: 0.397} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 378890424} + m_Father: {fileID: 1776840502} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &567596717 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 567596718} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &567596718 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 567596717} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 495122916} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &697358959 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 697358962} + - component: {fileID: 697358961} + m_Layer: 0 + m_Name: LineDistance + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &697358961 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 697358959} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4d8072930ebf6d34ea30547c75dfb378, type: 3} + m_Name: + m_EditorClassIdentifier: + Radius: 0.181 + Tail: {fileID: 1499507829} + GizmoColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + SolidGizmo: 0 +--- !u!4 &697358962 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 697358959} + m_LocalRotation: {x: -0.17039932, y: 0.6416098, z: -0.14918736, w: 0.7328329} + m_LocalPosition: {x: 2.054, y: 0.227, z: 0.198} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1499507829} + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: -3.343, y: 83.177, z: -25.98} +--- !u!1 &764167382 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 764167383} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &764167383 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 764167382} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1649862058} + m_Father: {fileID: 89594359} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &784028318 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 784028319} + m_Layer: 0 + m_Name: p00 (4) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &784028319 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 784028318} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.5, y: -0.5, z: -0.434} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1282382606} + m_Father: {fileID: 1776840502} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &852854938 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 852854939} + m_Layer: 0 + m_Name: c + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &852854939 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 852854938} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.94, y: 0, z: -0.438} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1716731905} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &869331620 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 869331621} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &869331621 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 869331620} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1309984319} + m_Father: {fileID: 1291439358} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &993404687 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 993404690} + - component: {fileID: 993404689} + - component: {fileID: 993404688} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &993404688 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 993404687} + m_Enabled: 1 +--- !u!20 &993404689 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 993404687} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &993404690 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 993404687} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1051717020 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1051717021} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1051717021 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1051717020} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1909109868} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1061526277 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1061526278} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1061526278 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1061526277} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1604317965} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1064316579 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1064316580} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1064316580 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1064316579} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1885955863} + m_Father: {fileID: 1566314966} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1068665243 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1068665244} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1068665244 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1068665243} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.76, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 2045284903} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1100075149 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1100075150} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1100075150 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1100075149} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2054899972} + m_Father: {fileID: 2141907755} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1111855027 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1111855028} + m_Layer: 0 + m_Name: p00 (6) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1111855028 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1111855027} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.467, y: -0.5, z: -0.49} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 2141907755} + m_Father: {fileID: 1776840502} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1274286520 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1274286521} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1274286521 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1274286520} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1866045913} + m_Father: {fileID: 2079542329} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1282382605 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1282382606} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1282382606 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1282382605} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1909109868} + m_Father: {fileID: 784028319} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1291439357 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1291439358} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1291439358 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1291439357} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 869331621} + m_Father: {fileID: 1620666996} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1309984318 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1309984319} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1309984319 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1309984318} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 869331621} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1471300592 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1471300593} + m_Layer: 0 + m_Name: b + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1471300593 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1471300592} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.037, y: 0, z: -0.42} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1716731905} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1499507828 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1499507829} + m_Layer: 0 + m_Name: GameObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1499507829 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1499507828} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.475, y: 0, z: -0.147} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 697358962} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1519617806 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1519617807} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1519617807 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1519617806} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1604317965} + m_Father: {fileID: 1890118283} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1566314965 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1566314966} + m_Layer: 0 + m_Name: p00 (7) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1566314966 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1566314965} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.478, y: -0.5, z: -0.023} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1064316580} + m_Father: {fileID: 1776840502} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1604317964 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1604317965} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1604317965 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1604317964} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1061526278} + m_Father: {fileID: 1519617807} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1620666995 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1620666996} + m_Layer: 0 + m_Name: p00 (1) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1620666996 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1620666995} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.5, z: 0.393} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1291439358} + m_Father: {fileID: 1776840502} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1649862057 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1649862058} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1649862058 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1649862057} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 764167383} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1716731903 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1716731905} + - component: {fileID: 1716731904} + m_Layer: 0 + m_Name: Triangle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1716731904 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1716731903} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c201feca8fd8c644b987521e89706c4f, type: 3} + m_Name: + m_EditorClassIdentifier: + B: {fileID: 1471300593} + C: {fileID: 852854939} + Capsule: {fileID: 697358961} +--- !u!4 &1716731905 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1716731903} + m_LocalRotation: {x: -0, y: -0.05464378, z: -0, w: 0.99850595} + m_LocalPosition: {x: 1.49, y: 0.146, z: 0.92} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1471300593} + - {fileID: 852854939} + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: -6.265, z: 0} +--- !u!1 &1776840497 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1776840502} + - component: {fileID: 1776840501} + - component: {fileID: 1776840500} + - component: {fileID: 1776840499} + - component: {fileID: 1776840498} + m_Layer: 0 + m_Name: Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1776840498 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1776840497} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 67a32b7c5e169f64daa6a8717d3de4f1, type: 3} + m_Name: + m_EditorClassIdentifier: + Env: + Center: {fileID: 0} + Stiffness: 0.1 + DragForce: 0.607 + External: {x: 0, y: -0.001, z: 0} + _strandGroups: + - Name: + CollisionMask: 4294967295 + Connection: 1 + Roots: + - {fileID: 1890118283} + - {fileID: 1620666996} + - {fileID: 510828803} + - {fileID: 254332539} + - {fileID: 784028319} + - {fileID: 1795237625} + - {fileID: 1111855028} + - {fileID: 1566314966} + DefaultStrandRaius: 0 + _colliderGroups: + - Name: + Colliders: + - {fileID: 2045284902} + _clothFactor: 0.5 + _list: + _particles: [] + _particleTransforms: [] +--- !u!65 &1776840499 +BoxCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1776840497} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Size: {x: 2, y: 2, z: 2} + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1776840500 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1776840497} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1776840501 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1776840497} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1776840502 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1776840497} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1890118283} + - {fileID: 1620666996} + - {fileID: 510828803} + - {fileID: 254332539} + - {fileID: 784028319} + - {fileID: 1795237625} + - {fileID: 1111855028} + - {fileID: 1566314966} + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1788457790 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1788457791} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1788457791 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1788457790} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1885955863} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1795237624 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1795237625} + m_Layer: 0 + m_Name: p00 (5) + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1795237625 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1795237624} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0.011, y: -0.5, z: -0.468} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 89594359} + m_Father: {fileID: 1776840502} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1866045912 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1866045913} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1866045913 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1866045912} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1274286521} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1885955862 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1885955863} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1885955863 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1885955862} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1788457791} + m_Father: {fileID: 1064316580} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1890118282 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1890118283} + m_Layer: 0 + m_Name: p00 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1890118283 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1890118282} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -0.5, y: -0.5, z: 0.452} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1519617807} + m_Father: {fileID: 1776840502} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1909109867 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1909109868} + m_Layer: 0 + m_Name: p02 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1909109868 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1909109867} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1051717021} + m_Father: {fileID: 1282382606} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2045284901 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2045284903} + - component: {fileID: 2045284902} + m_Layer: 0 + m_Name: collider + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &2045284902 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2045284901} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4d8072930ebf6d34ea30547c75dfb378, type: 3} + m_Name: + m_EditorClassIdentifier: + Radius: 0.201 + Tail: {fileID: 1068665244} + GizmoColor: {r: 1, g: 0.92156863, b: 0.015686275, a: 1} + SolidGizmo: 0 +--- !u!4 &2045284903 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2045284901} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.26, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1068665244} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2054899971 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2054899972} + m_Layer: 0 + m_Name: p03 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2054899972 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2054899971} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1100075150} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2079542328 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2079542329} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2079542329 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2079542328} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1274286521} + m_Father: {fileID: 254332539} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &2141907754 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2141907755} + m_Layer: 0 + m_Name: p01 + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2141907755 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2141907754} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: -0.2, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1100075150} + m_Father: {fileID: 1111855028} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity.meta b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity.meta new file mode 100644 index 000000000..34d1b6092 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7caaf005bf5d55e41860411c0c3d0710 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle.meta new file mode 100644 index 000000000..680b09da3 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 36a9770569fe64c4f98dbe384596e4b2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs new file mode 100644 index 000000000..4b151ebf9 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs @@ -0,0 +1,117 @@ +using UnityEngine; + +namespace SphereTriangle +{ + struct CapsuleInfo + { + public SphereCapsuleCollider Collider; + public Triangle Triangle; + + public Vector3 MinOnPlane; + public float MinDistance; + public float MinDistanceClmap; + public Vector3 MinOnPlaneClamp; + public Vector3 MinPos; + public Vector3 MinClamp; + + public Vector3 MaxOnPlane; + public float MaxDistance; + public float MaxDistanceClamp; + public Vector3 MaxOnPlaneClamp; + public Vector3 MaxPos; + public Vector3 MaxClamp; + + /// + /// min and max is tail and head. + /// + public bool Reverse; + + public bool Intersected; + + public CapsuleInfo(in Triangle t, SphereCapsuleCollider collider) + { + Collider = collider; + Triangle = t; + var headDistance = t.Plane.GetDistanceToPoint(collider.transform.position); + var tailDistance = t.Plane.GetDistanceToPoint(collider.Tail.position); + if (headDistance <= tailDistance) + { + Reverse = false; + MinDistance = headDistance; + MaxDistance = tailDistance; + MinOnPlane = t.Plane.ClosestPointOnPlane(collider.transform.position); + MaxOnPlane = t.Plane.ClosestPointOnPlane(collider.Tail.position); + MinClamp = collider.transform.position; + MaxClamp = collider.Tail.position; + MinPos = collider.transform.position; + MaxPos = collider.Tail.position; + } + else + { + Reverse = true; + MaxDistance = headDistance; + MinDistance = tailDistance; + MaxOnPlane = t.Plane.ClosestPointOnPlane(collider.transform.position); + MinOnPlane = t.Plane.ClosestPointOnPlane(collider.Tail.position); + MaxClamp = collider.transform.position; + MinClamp = collider.Tail.position; + MaxPos = collider.transform.position; + MinPos = collider.Tail.position; + } + + // Intersect + Intersected = true; + MinDistanceClmap = MinDistance; + MinOnPlaneClamp = MinOnPlane; + MaxDistanceClamp = MaxDistance; + MaxOnPlaneClamp = MaxOnPlane; + if (MinDistance < -Collider.Radius) + { + if (MaxDistance < -Collider.Radius) + { + Intersected = false; + } + else + { + // clamp Min + MinDistanceClmap = -Collider.Radius; + } + } + else if (MaxDistance > Collider.Radius) + { + if (MinDistance > Collider.Radius) + { + Intersected = false; + } + else + { + // clamp Max + MaxDistanceClamp = Collider.Radius; + } + } + + // + MaxOnPlaneClamp = Vector3.Lerp(MinOnPlane, MaxOnPlane, (MaxDistanceClamp - MinDistance) / (MaxDistance - MinDistance)); + MinOnPlaneClamp = Vector3.Lerp(MaxOnPlane, MinOnPlane, (MinDistanceClmap - MaxDistance) / (MinDistance - MaxDistance)); + MaxClamp = Vector3.Lerp(MinPos, MaxPos, (MaxDistanceClamp - MinDistance) / (MaxDistance - MinDistance)); + MinClamp = Vector3.Lerp(MaxPos, MinPos, (MinDistanceClmap - MaxDistance) / (MinDistance - MaxDistance)); + } + + public void DrawGizmo() + { + Gizmos.color = Color.blue; + Gizmos.DrawLine(Reverse ? Collider.Tail.position : Collider.transform.position, MinOnPlane); + Gizmos.DrawWireSphere(MinOnPlane, 0.01f); + + Gizmos.color = Color.blue; + Gizmos.DrawLine(Reverse ? Collider.transform.position : Collider.Tail.position, MaxOnPlane); + Gizmos.DrawWireSphere(MaxOnPlane, 0.01f); + + Gizmos.color = Color.magenta; + Gizmos.DrawLine(MinOnPlaneClamp, MaxOnPlaneClamp); + + Gizmos.color = Color.green; + Triangle.DrawGizmos(); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs.meta new file mode 100644 index 000000000..811eac0f5 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a797b9c09763964d84ef1497b260fb7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs new file mode 100644 index 000000000..b61537bdb --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs @@ -0,0 +1,244 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace SphereTriangle +{ + public class ClothRectCollision + { + // 2枚の三角形 + // abc + // cda + // に対する衝突(球 or カプセル)を管理する + public readonly int _a; + public readonly int _b; + public readonly int _c; + public readonly int _d; + + Triangle _triangle0; + float _trinagle0Collision; + Triangle _triangle1; + float _triangle1Collision; + + TriangleCapsuleCollisionSolver _s0 = new(); + TriangleCapsuleCollisionSolver _s1 = new(); + + // 各コライダーが初期姿勢で三角形ABCの法線の正か負のどちらにあるのかを記録する + Dictionary _initialColliderNormalSide = new(); + + /// + /// two triangles + /// d x-x c + /// |/| + /// a x-x b + /// + /// + /// + /// + /// + public ClothRectCollision( + int a, int b, int c, int d) + { + _a = a; + _b = b; + _c = c; + _d = d; + } + + public void InitializeColliderSide(PositionList list, IReadOnlyList colliderGroups) + { + var a = list.Get(_a); + var b = list.Get(_b); + var c = list.Get(_c); + var d = list.Get(_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 p = t.Plane.ClosestPointOnPlane(collider.transform.position); + var dot = Vector3.Dot(t.Plane.normal, collider.transform.position - p); + _initialColliderNormalSide[collider] = dot; + } + } + } + + 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; + } + + Bounds GetBounds(PositionList list) + { + return GetBoundsFrom4(list.Get(_a), list.Get(_b), list.Get(_c), list.Get(_d)); + } + + public void Collide(PositionList list, IReadOnlyCollection colliders) + { + using (new ProfileSample("Rect: Prepare")) + { + _s0.BeginFrame(); + _s1.BeginFrame(); + + var a = list.Get(_a); + var b = list.Get(_b); + var c = list.Get(_c); + var d = list.Get(_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 = GetBounds(list); + + foreach (var collider in colliders) + { + using (new ProfileSample("EaryOut")) + { + if (!aabb.Intersects(collider.GetBounds())) + { + continue; + } + + var p = _triangle0.Plane.ClosestPointOnPlane(collider.transform.position); + var dot = Vector3.Dot(_triangle0.Plane.normal, collider.transform.position - p); + if (_initialColliderNormalSide[collider] * dot < 0) + { + // 片側 + continue; + } + } + + if (TryCollide(_s0, collider, _triangle0, out var l0)) + { + _trinagle0Collision = 1.0f; + list.CollisionMove(_a, l0, collider.Radius); + list.CollisionMove(_b, l0, collider.Radius); + list.CollisionMove(_c, l0, collider.Radius); + } + if (TryCollide(_s1, collider, _triangle1, out var l1)) + { + _triangle1Collision = 1.0f; + list.CollisionMove(_c, l1, collider.Radius); + list.CollisionMove(_d, l1, collider.Radius); + list.CollisionMove(_a, l1, collider.Radius); + } + } + } + } + + /// + /// 衝突して移動デルタを得る + /// + /// + /// + /// + /// + static bool TryCollide(TriangleCapsuleCollisionSolver solver, SphereCapsuleCollider collider, in Triangle t, out LineSegment l) + { + if (collider.Tail == null) + { + using var profile = new ProfileSample("Sphere"); + // sphere + return TryCollideSphere(t, collider.transform.position, collider.Radius, out l); + } + else + { + // capsule + TriangleCapsuleCollisionSolver.Result result = default; + using (new ProfileSample("Capsule: Collide")) + { + result = solver.Collide(t, collider, new(collider.transform.position, collider.Tail.position), collider.Radius); + } + using (new ProfileSample("Capsule: TryGetClosest")) + { + var type = result.TryGetClosest(out l); + return type.HasValue; + } + } + } + + /// + /// + /// + /// + /// + /// + /// collider => 衝突点 への線分を返す + 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; + } + + // p を三辺に投影し t を得る + // var proj = triangle.Project(p); + // if (proj.TryGetClosest(collider, out var x)) + // { + // // 最近点の距離 + // // TODO: + // return new LineSegment(collider, x); + // } + throw new System.NotImplementedException(); + } + + public void DrawGizmos() + { + if (_triangle0.Points == null) + { + return; + } + 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); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs.meta new file mode 100644 index 000000000..f22245c92 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ClothRectCollision.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c16dea6c47fa81d4184547a209d8fe61 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs new file mode 100644 index 000000000..401d4d866 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SphereTriangle +{ + [Serializable] + public class ColliderGroup + { + [SerializeField] + public string Name; + + [SerializeField] + public List Colliders = new List(); + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs.meta new file mode 100644 index 000000000..c6e6b683c --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ColliderGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 07647b77d9934cd42b0bd0ab880ed321 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs new file mode 100644 index 000000000..1b4c4dfe2 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs @@ -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; + } +} diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs.meta new file mode 100644 index 000000000..c73a23360 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 39108f27e89d7514d86e56e2ffb0550c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs new file mode 100644 index 000000000..f555b5bba --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs @@ -0,0 +1,58 @@ +using UnityEngine; + +namespace SphereTriangle +{ + public class LineDistanceGizmo : MonoBehaviour + { + public SphereCapsuleCollider LineA; + public SphereCapsuleCollider LineB; + + void Reset() + { + if (LineA == null) + { + LineA = GetComponent(); + } + } + + public void OnDrawGizmos() + { + if (LineA?.Tail != null && LineB?.Tail != null) + { + var a = new LineSegment(LineA.transform.position, LineA.Tail.position); + var b = new LineSegment(LineB.transform.position, LineB.Tail.position); + 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.position, a_s); + } + else if (s > 1) + { + Gizmos.DrawLine(LineA.Tail.position, a_s); + } + else + { + Gizmos.DrawWireSphere(a_s, 0.05f); + } + if (t < 0) + { + Gizmos.DrawLine(LineB.transform.position, b_t); + } + else if (t > 1) + { + Gizmos.DrawLine(LineB.transform.position, b_t); + } + else + { + Gizmos.DrawWireSphere(b_t, 0.05f); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs.meta new file mode 100644 index 000000000..0f9f777f5 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineDistanceGizmo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cceeecd3d3db5cd4ba74dfa6490e38b1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs new file mode 100644 index 000000000..459066c37 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs @@ -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; + } + + /// + /// 球との衝突時の移動ベクトル + /// Start が球の中心。End 衝突点。 + /// + /// + /// + 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); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs.meta new file mode 100644 index 000000000..e5fb83755 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: add9de55aee3f344cab64a1c99c7c6aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs new file mode 100644 index 000000000..b13b87fe8 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs @@ -0,0 +1,103 @@ +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 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]; + } + } + + /// + /// 衝突した。移動を蓄積 + /// + /// + /// + 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 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; + } + + /// + /// 衝突前後の状態を描画 + /// + 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); + } + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs.meta new file mode 100644 index 000000000..0733c4dc1 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 836b32456872256409962a684cbee1b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs new file mode 100644 index 000000000..ac9a2d04a --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs @@ -0,0 +1,21 @@ +using System; +using UnityEngine.Profiling; + +namespace SphereTriangle +{ + public struct ProfileSample : IDisposable + { + string _name; + + public ProfileSample(string name) + { + _name = name; + Profiler.BeginSample(name); + } + + public void Dispose() + { + Profiler.EndSample(); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs.meta new file mode 100644 index 000000000..f44d2f9ef --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/ProfileSample.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e8375e9fc2aee4f448e349401c0bf068 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs new file mode 100644 index 000000000..2908963f8 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs @@ -0,0 +1,176 @@ +using UnityEngine; + + +namespace SphereTriangle +{ + [DisallowMultipleComponent] + public class SphereCapsuleCollider : MonoBehaviour + { + [SerializeField, Range(0.01f, 1f)] + public float Radius = 0.05f; + + public Transform Tail; + + public Ray? HeadTailRay => Tail == null ? null : new Ray { origin = transform.position, direction = (Tail.position - transform.position) }; + + public float CapsuleLength => Tail == null ? 0 : Vector3.Distance(Tail.position, transform.position); + + + public Color GizmoColor = Color.yellow; + public bool SolidGizmo = false; + + public Bounds GetBounds() + { + if (Tail == null) + { + return new Bounds(transform.position, new Vector3(Radius, Radius, Radius)); + } + + var h = transform.position; + var t = Tail.position; + 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(Radius * 2); + return aabb; + } + + public void Reset() + { + if (transform.childCount > 0) + { + Tail = transform.GetChild(0); + } + } + + /// + /// collide sphere a and sphere b. + /// move sphere b to resolved if collide. + /// + /// + /// + /// + /// + /// + /// + 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; + } + + /// + /// collide capsule and sphere b. + /// move sphere b to resolved if collide. + /// + /// + /// + /// + /// + /// + 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); + } + + /// + /// collision for strand + /// + /// + /// + /// + /// + public bool TryCollide(in Vector3 p, float radius, out LineSegment resolved) + { + if (Tail != null) + { + return TryCollideCapsuleAndSphere(transform.position, Tail.position, this.Radius, p, radius, out resolved); + } + else + { + return TryCollideSphereAndSphere(transform.position, this.Radius, p, radius, out resolved); + } + } + + public static void DrawCapsuleGizmo(Vector3 start, Vector3 end, float radius) + { + var tail = end - start; + var distance = (end - start).magnitude; + Gizmos.matrix = Matrix4x4.TRS(start, Quaternion.FromToRotation(Vector3.forward, tail), Vector3.one); + Gizmos.DrawWireSphere(Vector3.zero, radius); + Gizmos.DrawWireSphere(Vector3.forward * distance, radius); + var capsuleEnd = Vector3.forward * distance; + var offsets = new Vector3[] { new Vector3(-1.0f, 0.0f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), new Vector3(1.0f, 0.0f, 0.0f), new Vector3(0.0f, -1.0f, 0.0f) }; + for (int i = 0; i < offsets.Length; i++) + { + Gizmos.DrawLine(offsets[i] * radius, capsuleEnd + offsets[i] * radius); + } + Gizmos.matrix = Matrix4x4.identity; + } + + public void OnDrawGizmos() + { + if (SolidGizmo) + { + Gizmos.color = Color.white; + Gizmos.DrawSphere(transform.position, Radius); + } + + Gizmos.color = GizmoColor; + if (transform.parent) + { + Gizmos.DrawLine(transform.parent.position, transform.position); + } + if (Tail != null) + { + DrawCapsuleGizmo(transform.position, Tail.position, Radius); + } + else + { + Gizmos.DrawWireSphere(transform.position, Radius); + } + +#if AABB_DEBUG + Gizmos.matrix = Matrix4x4.identity; + Gizmos.color = Color.magenta; + var aabb = GetBounds(); + Gizmos.DrawWireCube(aabb.center, aabb.size); +#endif + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs.meta new file mode 100644 index 000000000..eb2d57d39 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereCapsuleCollider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d8072930ebf6d34ea30547c75dfb378 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef new file mode 100644 index 000000000..835b18877 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef @@ -0,0 +1,14 @@ +{ + "name": "SphereTriangle", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef.meta new file mode 100644 index 000000000..6281600a3 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/SphereTriangle.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0138a2c7639266f45b948ea162125de5 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs new file mode 100644 index 000000000..c4392467b --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs @@ -0,0 +1,73 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SphereTriangle +{ + public enum StrandConnectionType + { + Cloth, + ClothLoop, + Strand, + } + + [System.Flags] + public enum CollisionGroupMask : uint + { + None = 0, + Group01 = 0x00000001, + Group02 = 0x00000002, + Group03 = 0x00000004, + Group04 = 0x00000008, + Group05 = 0x00000010, + Group06 = 0x00000020, + Group07 = 0x00000040, + Group08 = 0x00000080, + Group09 = 0x00000100, + Group10 = 0x00000200, + Group11 = 0x00000400, + Group12 = 0x00000800, + Group13 = 0x00001000, + Group14 = 0x00002000, + Group15 = 0x00004000, + Group16 = 0x00008000, + Group17 = 0x00010000, + Group18 = 0x00020000, + Group19 = 0x00040000, + Group20 = 0x00080000, + Group21 = 0x00100000, + Group22 = 0x00200000, + Group23 = 0x00400000, + Group24 = 0x00800000, + Group25 = 0x01000000, + Group26 = 0x02000000, + Group27 = 0x04000000, + Group28 = 0x08000000, + Group29 = 0x10000000, + Group30 = 0x20000000, + Group31 = 0x40000000, + Group32 = 0x80000000, + All = uint.MaxValue, + } + + [Serializable] + public class StrandGroup + { + [SerializeField] + public string Name; + + [SerializeField] + public CollisionGroupMask CollisionMask; + + [SerializeField] + public StrandConnectionType Connection; + + [SerializeField] + public List Roots = new List(); + + [SerializeField] + [Range(0.001f, 0.5f)] + public float DefaultStrandRaius = 0.05f; + + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs.meta new file mode 100644 index 000000000..4dbbe515d --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/StrandGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efab592ca762fea46adb3c38b41e65d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs new file mode 100644 index 000000000..99f8db10a --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace SphereTriangle +{ + public class TransformSort : IComparer + { + Vector3 _o; + public TransformSort(in Vector3 origin) + { + _o = origin; + } + + static int Rot180(int d) + { + // d -= 180; + while (d < 0) + { + d += 360; + } + return d; + } + + public int Degree(Transform t) + { + var x = t.position.x - _o.x; + var y = t.position.z - _o.z; + var a = (int)(Mathf.Rad2Deg * Mathf.Atan2(x, y)); + a = Rot180(a); + // Debug.Log($"{t.name} => {a}"); + return a; + } + + public int Compare(Transform x, Transform y) + { + return Degree(x) - Degree(y); + } + } + +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs.meta new file mode 100644 index 000000000..6ecea035a --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TransformSort.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcbd60313a3b8e34fbe465e9bcbcab98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs new file mode 100644 index 000000000..082007891 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs @@ -0,0 +1,285 @@ +using UnityEngine; + + +namespace SphereTriangle +{ + /// + /// 三角形との交差が 2 => 交点 と 交点 + /// 三角形との交差が 1 => 交点 と 三角形内の線分端点 + /// 三角形との交差が 0 => 線分の端点が両方とも三角形内の場合。端点 と 端点 + /// + public struct TriangleSegmentIntersection + { + public float t0; + public float t1; + public TriangleSegmentIntersection(float _t0, float _t1) + { + if (_t0 <= _t1) + { + t0 = _t0; + t1 = _t1; + } + else + { + t0 = _t1; + t1 = _t0; + } + } + } + + public struct Triangle + { + public Plane Plane; + public Vector3[] Points; + + public Vector3 a => Points[0]; + public Vector3 b => Points[1]; + public Vector3 c => Points[2]; + + public Triangle(Vector3 a, Vector3 b, Vector3 c) + { + Plane = new Plane(a, b, c); + Points = new Vector3[] { a, b, c }; + } + + /// + /// p がすべての辺の同じ側(内積的な意味で)にある => 三角形の内側にある + /// + /// + /// + public bool IsSameSide(Vector3 p) + { + var da = Vector3.Dot(Vector3.Cross(p - a, b - a), Plane.normal); + var db = Vector3.Dot(Vector3.Cross(p - b, c - b), Plane.normal); + var dc = Vector3.Dot(Vector3.Cross(p - c, a - c), Plane.normal); + + if (da > 0) + { + if (db > 0) + { + if (dc > 0) + { + return true; + } + } + } + else if (da < 0) + { + if (db < 0) + { + if (dc < 0) + { + return true; + } + } + } + + return false; + } + + // /// + // /// p から lerp の係数を計算 + // /// + // /// + // /// + // /// + // /// + // static float getT(in Vector3 p0, in Vector3 p1, in Vector3 p) + // { + // return (p - p0).magnitude / (p1 - p0).magnitude; + // } + + // /// + // /// 三角形と線分の交差を判定する。 + // /// + // /// 線分始点(t=0)。三角形 abc と同一平面を期待 + // /// 線分終点(t=1)。三角形 abc と同一平面を期待 + // /// + // public bool TryIntersectSegment(in Vector3 p0, in Vector3 p1, out TriangleSegmentIntersection intersection) + // { + // // [ab bc ca] + // // [ab bc] [bc ca] [ca ab] + // // [ab] [bc] [ca] + // // [] + // if (TryIntersectSegments(p0, p1, a, b, out var ab)) + // { + // if (TryIntersectSegments(p0, p1, b, c, out var bc)) + // { + // // [ab bc] or [ab bc ca] + // intersection = new TriangleSegmentIntersection(getT(p0, p1, ab), getT(p0, p1, bc)); + // return true; + // } + // else + // { + // if (TryIntersectSegments(p0, p1, c, a, out var ca)) + // { + // // [ab ca] + // intersection = new TriangleSegmentIntersection(getT(p0, p1, ab), getT(p0, p1, ca)); + // return true; + // } + // else + // { + // // [ab] + // if (IsSameSide(p0)) + // { + // intersection = new TriangleSegmentIntersection(0, getT(p0, p1, ab)); + // return true; + // } + // else + // { + // intersection = new TriangleSegmentIntersection(getT(p0, p1, ab), 1.0f); + // return true; + // } + // } + // } + // } + // else + // { + // if (TryIntersectSegments(p0, p1, b, c, out var bc)) + // { + // if (TryIntersectSegments(p0, p1, c, a, out var ca)) + // { + // // [bc ca] + // intersection = new TriangleSegmentIntersection(getT(p0, p1, bc), getT(p0, p1, ca)); + // return true; + // } + // else + // { + // // [bc] + // if (IsSameSide(p0)) + // { + // intersection = new TriangleSegmentIntersection(0, getT(p0, p1, bc)); + // return true; + // } + // else + // { + // intersection = new TriangleSegmentIntersection(getT(p0, p1, bc), 1.0f); + // return true; + // } + // } + // } + // else + // { + // if (TryIntersectSegments(p0, p1, c, a, out var ca)) + // { + // // [ca] + // if (IsSameSide(p0)) + // { + // intersection = new TriangleSegmentIntersection(0, getT(p0, p1, ca)); + // return true; + // } + // else + // { + // intersection = new TriangleSegmentIntersection(getT(p0, p1, ca), 1.0f); + // return true; + // } + // } + // else + // { + // // [] + // if (IsSameSide(p0) && IsSameSide(p1)) + // { + // intersection = new TriangleSegmentIntersection(0, 1); + // return true; + // } + // else + // { + // intersection = default; + // return false; + // } + // } + // } + // } + // } + + // /// + // /// 線分と線分の交差を判定する + // /// a-b d + // /// / + // /// c + // /// https://qiita.com/zu_rin/items/09876d2c7ec12974bc0f + // /// + // /// + // /// + // /// + // /// + // /// + // static bool TryIntersectSegments(in Vector3 a, in Vector3 b, in Vector3 c, in Vector3 d, out Vector3 p) + // { + // var deno = Vector3.Cross(b - a, d - c).magnitude; + // if (deno < 1e-5) + // { + // // 線分が平行 + // p = default; + // return false; + // } + + // var s = Vector3.Cross(c - a, d - c).magnitude / deno; + // var t = Vector3.Cross(b - a, a - c).magnitude / deno; + // if (s < 0.0 || 1.0 < s || t < 0.0 || 1.0 < t) + // { + // // 線分が交差していない + // p = default; + // return false; + // } + + // p = new Vector3( + // a.x + s * (b - a).x, + // a.y + s * (b - a).y, + // a.z + s * (b - a).z + // ); + // return true; + // } + + // public (Vector3, float) ProjectAB(in Vector3 p) + // { + // var ab = (b - a).normalized; + // var d = Vector3.Dot(ab, (p - a)); + // var x = a + ab * d; + // return (x, d / (b - a).magnitude); + // } + // public (Vector3, float) ProjectBC(in Vector3 p) + // { + // var bc = (c - b).normalized; + // var d = Vector3.Dot(bc, (p - b)); + // var x = b + bc * d; + // return (x, d / (c - b).magnitude); + // } + // public (Vector3, float) ProjectCA(in Vector3 p) + // { + // var ca = (a - c).normalized; + // var d = Vector3.Dot(ca, (p - c)); + // var x = c + ca * d; + // return (x, d / (a - c).magnitude); + // } + + // public TriangleProjection Project(in Vector3 p) + // { + // var (ab, ta) = ProjectAB(p); + // var (bc, tb) = ProjectBC(p); + // var (ca, tc) = ProjectCA(p); + // return new TriangleProjection + // { + // ab = ab, + // ta = ta, + // bc = bc, + // tb = tb, + // ca = ca, + // tc = tc, + // }; + // } + + public void DrawGizmos() + { +#if UNITY_2022_3_OR_NEWER + Gizmos.DrawLineStrip(Points, true); +#else + for (int i = 0; i < Points.Length - 1; ++i) + { + Gizmos.DrawLine(Points[i], Points[i + 1]); + } + Gizmos.DrawLine(Points[Points.Length - 1], Points[0]); +#endif + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs.meta new file mode 100644 index 000000000..25ddcdea6 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2558c27234db0c741a6821682205da57 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs new file mode 100644 index 000000000..63d1cee79 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs @@ -0,0 +1,366 @@ +// #define USE_VERTEX_DISTANCE +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace SphereTriangle +{ + [Serializable] + public class TriangleCapsuleCollisionSolver + { + public enum IntersectionType + { + PlaneInTriangle, +#if USE_VERTEX_DISTANCE + VertexDistance, +#endif + EdgeDistance, + } + + public struct Result + { + /// + /// カプセルStartの投影点が三角形内部 + /// + public LineSegment? StartInTriangle; + /// + /// カプセルEndの投影点が三角形内部 + /// + public LineSegment? EndInTriangle; + +#if USE_VERTEX_DISTANCE + /// + /// カプセル線分と頂点Aの距離が半径以内 + /// + public LineSegment? VertexADistance; + /// + /// カプセル線分と頂点Bの距離が半径以内 + /// + public LineSegment? VertexBDistance; + /// + /// カプセル線分と頂点Cの距離が半径以内 + /// + public LineSegment? VertexCDistance; +#endif + + /// + /// カプセル線分と辺ABの距離が半径以内 + /// + public LineSegment? EdgeABDistance; + /// + /// カプセル線分と辺BCの距離が半径以内 + /// + public LineSegment? EdgeBCDistance; + /// + /// カプセル線分と辺CAの距離が半径以内 + /// + public LineSegment? EdgeCADistance; + + public IntersectionType? TryGetClosest(out LineSegment value) + { + using var profile = new ProfileSample("TryGetClosest"); + var d = float.PositiveInfinity; + value = default; + IntersectionType? it = default; + + if (StartInTriangle.HasValue && StartInTriangle.Value.SqLength < d) + { + value = StartInTriangle.Value; + it = IntersectionType.PlaneInTriangle; + } + + if (EndInTriangle.HasValue && EndInTriangle.Value.SqLength < d) + { + value = EndInTriangle.Value; + it = IntersectionType.PlaneInTriangle; + } + +#if USE_VERTEX_DISTANCE + if (VertexADistance.HasValue) yield return (IntersectionType.VertexDistance, VertexADistance.Value); + if (VertexBDistance.HasValue) yield return (IntersectionType.VertexDistance, VertexBDistance.Value); + if (VertexCDistance.HasValue) yield return (IntersectionType.VertexDistance, VertexCDistance.Value); +#endif + + if (EdgeABDistance.HasValue && EdgeABDistance.Value.SqLength < d) + { + value = EdgeABDistance.Value; + it = IntersectionType.EdgeDistance; + } + + if (EdgeBCDistance.HasValue && EdgeBCDistance.Value.SqLength < d) + { + value = EdgeBCDistance.Value; + it = IntersectionType.EdgeDistance; + } + + if (EdgeCADistance.HasValue && EdgeCADistance.Value.SqLength < d) + { + value = EdgeCADistance.Value; + it = IntersectionType.EdgeDistance; + } + + return it; + } + }; + + public class Status + { + /// + /// Capsule clamped by radius distance from plane + /// + public LineSegment Clamped; + + /// + /// capsule line intersect plane + /// + public Vector3 O; + + /// + /// capsule Start project plane + /// + public Vector3 PS; + /// + /// capsule End project plane + /// + public Vector3 PE; + + public int CollisionCount = 0; + + public Result Result; + } + // 複数コライダーのデバッグ表示のため + public Dictionary collider_status_map = new(); + + public void BeginFrame() + { + collider_status_map.Clear(); + } + + public Result Collide(in Triangle t, SphereCapsuleCollider collider, in LineSegment capsule, float radius) + { + if (collider == null) + { + throw new ArgumentNullException("collider"); + } + Status status = default; + + using (new ProfileSample("Parallel Prepare")) + { + if (collider_status_map.TryGetValue(collider, out status)) + { + } + else + { + status = new Status(); + collider_status_map[collider] = status; + } + + float dot = default; + using (new ProfileSample("Dot")) + { + dot = Vector3.Dot(t.Plane.normal, capsule.Vector); + } + + if (Mathf.Abs(dot) < 1e-4) + { + using var profile = new ProfileSample("Parallel"); + // 三角面とカプセルが平行 + var d = t.Plane.GetDistanceToPoint(capsule.Start); + if (d < -radius || d > radius) + { + return default; + } + // 距離による clamp できない + status.Clamped = capsule; + } + else + { + using var profile = new ProfileSample("TryClampPlaneDistance"); + if (capsule.TryClampPlaneDistance(t.Plane, radius, out status.Clamped, out status.O)) + { + } + else + { + return default; + } + } + + using (new ProfileSample("ClosestPointOnPlane")) + { + status.PS = t.Plane.ClosestPointOnPlane(status.Clamped.Start); + status.PE = t.Plane.ClosestPointOnPlane(status.Clamped.End); + } + } + + LineSegment? startInTriangle = default; + LineSegment? endInTriangle = default; + using (new ProfileSample("InTriangle")) + { + if (t.IsSameSide(status.PS)) + { + startInTriangle = new(status.Clamped.Start, status.PS); + ++status.CollisionCount; + } + + if (t.IsSameSide(status.PE)) + { + endInTriangle = new(status.Clamped.End, status.PE); + ++status.CollisionCount; + } + } + +#if USE_VERTEX_DISTANCE + var vertexADistance = calcVertexDistance(t.a, status.Clamped, radius); + if (vertexADistance.HasValue) + { + ++status.CollisionCount; + } + var vertexBDistance = calcVertexDistance(t.b, status.Clamped, radius); + if (vertexBDistance.HasValue) + { + ++status.CollisionCount; + } + var vertexCDistance = calcVertexDistance(t.c, status.Clamped, radius); + if (vertexCDistance.HasValue) + { + ++status.CollisionCount; + } +#endif + + LineSegment? edgeABDistance = default; + LineSegment? edgeBCDistance = default; + LineSegment? edgeCADistance = default; + using (new ProfileSample("EdgeDistance")) + { + // triangle edges + var ab = new LineSegment(t.a, t.b); + var bc = new LineSegment(t.b, t.c); + var ca = new LineSegment(t.c, t.a); + + edgeABDistance = calcEdgeDistance(ab, status.Clamped, radius); + if (edgeABDistance.HasValue) + { + ++status.CollisionCount; + } + edgeBCDistance = calcEdgeDistance(bc, status.Clamped, radius); + if (edgeBCDistance.HasValue) + { + ++status.CollisionCount; + } + edgeCADistance = calcEdgeDistance(ca, status.Clamped, radius); + if (edgeCADistance.HasValue) + { + ++status.CollisionCount; + } + } + + status.Result = new Result + { + StartInTriangle = startInTriangle, + EndInTriangle = endInTriangle, +#if USE_VERTEX_DISTANCE + VertexADistance = vertexADistance, + VertexBDistance = vertexBDistance, + VertexCDistance = vertexCDistance, +#endif + EdgeABDistance = edgeABDistance, + EdgeBCDistance = edgeBCDistance, + EdgeCADistance = edgeCADistance, + }; + + return status.Result; + } + + LineSegment? calcVertexDistance(in Vector3 a, in LineSegment clamped, float radius) + { + var t = clamped.Project(a); + if (t < 0 || t > 1) + { + return default; + } + + var p = clamped.GetPoint(t); + if (Vector3.Distance(a, p) > radius) + { + return default; + } + + return new LineSegment(p, a); + } + + LineSegment? calcEdgeDistance(in LineSegment ab, in LineSegment clamped, float radius) + { + var (a_s, a_t) = LineSegment.CalcClosest(ab, clamped); + + // clamp range + a_s = Mathf.Clamp(a_s, 0, 1); + a_t = Mathf.Clamp(a_t, 0, 1); + + var ab_s = ab.GetPoint(a_s); + var ab_t = clamped.GetPoint(a_t); + + var distance_a = (ab_s - ab_t).magnitude; + if (distance_a > radius) + { + return default; + } + return new LineSegment(ab_t, ab_s); + } + + Color GetGizmoColor(IntersectionType type, float color) + { + var g = new Color(0.3f, 0.3f, 0.3f); + return Color.Lerp(g, type.ToColor(), color); + } + + public void DrawGizmos(in Triangle t, float collision, float radius) + { + Gizmos.matrix = Matrix4x4.identity; + + var hit = false; + foreach (var (_, status) in collider_status_map) + { + var type = status.Result.TryGetClosest(out var l); + if (!type.HasValue) + { + continue; + } + + // capsule + hit = true; + Gizmos.color = GetGizmoColor(type.Value, collision); + t.DrawGizmos(); + l.DrawGizmos(); + Gizmos.DrawSphere(l.End, radius); + Gizmos.DrawWireSphere(l.Start, radius); + } + + if (!hit) + { + Gizmos.color = Color.gray; + t.DrawGizmos(); + } + } + } + + public static class IntersectionTypeExtensions + { + public static Color ToColor(this TriangleCapsuleCollisionSolver.IntersectionType type) + { + switch (type) + { + case TriangleCapsuleCollisionSolver.IntersectionType.PlaneInTriangle: + return Color.magenta; +#if USE_VERTEX_DISTANCE + case TriangleCapsuleCollisionSolver.IntersectionType.VertexDistance: + return Color.blue; +#endif + case TriangleCapsuleCollisionSolver.IntersectionType.EdgeDistance: + return Color.cyan; + + default: + throw new Exception(); + } + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs.meta new file mode 100644 index 000000000..0206377af --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleCollisionSolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c4efecee94290be43b3affbcc6bd191a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs new file mode 100644 index 000000000..f73cd67d4 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs @@ -0,0 +1,64 @@ +using UnityEngine; + + +namespace SphereTriangle +{ + public class TriangleCapsuleGizmo : MonoBehaviour + { + public Transform B; + public Transform C; + public SphereCapsuleCollider Capsule; + + void Reset() + { + for (int i = 0; i < transform.childCount; ++i) + { + switch (i) + { + case 0: + if (this.B == null) B = transform.GetChild(i); + break; + + case 1: + if (this.C == null) C = transform.GetChild(i); + break; + } + } + } + + [SerializeField] + TriangleCapsuleCollisionSolver _solver = new(); + + public void OnDrawGizmos() + { + if (B == null) return; + if (C == null) return; + var t = new Triangle(transform.position, B.position, C.position); + + if (Capsule?.Tail == null) return; + var capsule = new LineSegment(Capsule.transform.position, Capsule.Tail.position); + + _solver.BeginFrame(); + var result = _solver.Collide(t, Capsule, capsule, Capsule.Radius); + var type = result.TryGetClosest(out var l); + if (!type.HasValue) + { + Gizmos.color = Color.gray; + t.DrawGizmos(); + return; + } + + _solver.DrawGizmos(t, 1.0f, Capsule.Radius); + + Gizmos.color = Color.magenta; + Gizmos.DrawLine(l.Start, l.End); + Gizmos.DrawSphere(l.End, 0.01f); + Gizmos.DrawWireSphere(l.Start, 0.02f); + + var delta = l.Vector.normalized * (Capsule.Radius - l.Length); + Gizmos.color = Color.green; + Gizmos.DrawLine(l.End, l.End + delta); + Gizmos.DrawSphere(l.End + delta, 0.01f); + } + } +} \ No newline at end of file diff --git a/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs.meta b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs.meta new file mode 100644 index 000000000..035e2cf48 --- /dev/null +++ b/Assets/VRM10_Samples/ClothSample/SphereTriangle/TriangleCapsuleGizmo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c201feca8fd8c644b987521e89706c4f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: