mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-08-13 13:16:02 -05:00
init cloth dev
This commit is contained in:
8
Assets/VRM10_Samples/ClothSample.meta
Normal file
8
Assets/VRM10_Samples/ClothSample.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6871c022e581b3742bbc892f38900607
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/RotateParticle.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/RotateParticle.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a00a3e7f5bad4ae4cbbf0e2acc739fa8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "RotateParticle",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:0138a2c7639266f45b948ea162125de5"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 308b348fb80d89d42a9620951b0f60db
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ba7e498c4d5eef44aba4cf125686089
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 568e5702a44c18d4487775441e011a61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<T>() where T : Component
|
||||
{
|
||||
var t = GetComponent<T>();
|
||||
if (t != null)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
return gameObject.AddComponent<T>();
|
||||
}
|
||||
|
||||
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<Transform> 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<Transform> 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<Animator>();
|
||||
if (animator == null)
|
||||
{
|
||||
Debug.LogWarning("no animator");
|
||||
return;
|
||||
}
|
||||
var avatar = animator.avatar;
|
||||
if (!avatar.isHuman)
|
||||
{
|
||||
Debug.LogWarning("not humanoid");
|
||||
return;
|
||||
}
|
||||
|
||||
var system = GetOrAddComponent<RotateParticleSystem>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2c23a62e016b3244b286404bc339540
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 500c97fb71417394e8144b008b462e6c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bccf867d0bc7b244841c8ecff9ce65a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<Vector3> NewPos;
|
||||
public NativeArray<Quaternion> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7127f077eba307343a5f5ce15213a595
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SphereTriangle;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace RotateParticle
|
||||
{
|
||||
[Serializable]
|
||||
public class ParticleList
|
||||
{
|
||||
public List<RotateParticle> _particles = new();
|
||||
public List<Transform> _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<Vector3> restPositions)
|
||||
{
|
||||
for (int i = 0; i < _particles.Count; ++i)
|
||||
{
|
||||
var p = _particles[i];
|
||||
if (p.Init.Mass <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rest = restPositions[p.Init.Index];
|
||||
p.BeginFrame(env, time, rest);
|
||||
}
|
||||
}
|
||||
|
||||
public void Verlet(SimulationEnv env, FrameTime time, SphereTriangle.InitPosition initPos)
|
||||
{
|
||||
for (int i = 0; i < _particles.Count; ++i)
|
||||
{
|
||||
var p = _particles[i];
|
||||
if (p.Init.Mass > 0)
|
||||
{
|
||||
var newPos = p.Verlet(env, time);
|
||||
initPos(i, p.Init.Mass, newPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawGizmos()
|
||||
{
|
||||
foreach (var p in _particles)
|
||||
{
|
||||
p.OnDrawGizmos(_particleTransforms[p.Init.Index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fee618dfb5846ea47978b49ed4c0282b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eddc8c1b804734c49a6eb04d72afb0a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fe036d9e9352e54ea009e8322a4faa2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<RotateParticle> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// get ParentParent.rotatio * Parent.Init.LocalRotation
|
||||
/// </summary>
|
||||
/// <param name="transforms"></param>
|
||||
/// <returns></returns>
|
||||
public Quaternion RestRotation(IReadOnlyList<Transform> transforms)
|
||||
{
|
||||
if (Parent == null)
|
||||
{
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
var parent = transforms[Parent.Init.Index];
|
||||
if (Parent.Parent == null)
|
||||
{
|
||||
var pt = parent.parent;
|
||||
if (pt == null)
|
||||
{
|
||||
return Parent.Init.LocalRotation;
|
||||
}
|
||||
|
||||
return pt.rotation * Parent.Init.LocalRotation;
|
||||
}
|
||||
|
||||
var parentparent = transforms[Parent.Parent.Init.Index];
|
||||
var restRotation = parentparent.rotation * Parent.Init.LocalRotation;
|
||||
return restRotation;
|
||||
}
|
||||
|
||||
public void OnDrawGizmos(Transform transform)
|
||||
{
|
||||
if (Init.Radius == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Gizmos.color = Init.Mass == 0 ? Color.red : Color.gray;
|
||||
if (transform.parent != null && Init.Mass > 0)
|
||||
{
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
Gizmos.DrawLine(transform.parent.position, transform.position);
|
||||
}
|
||||
|
||||
Gizmos.matrix = transform.localToWorldMatrix;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, Init.Radius);
|
||||
|
||||
var r = Init.Radius * 2;
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawLine(Vector3.left * r, Vector3.right * r);
|
||||
Gizmos.color = Color.green;
|
||||
Gizmos.DrawLine(Vector3.down * r, Vector3.up * r);
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawLine(Vector3.back * r, Vector3.forward * r);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 125347015f2366e4b8957431407ddf9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,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<StrandGroup> _strandGroups = new List<StrandGroup>();
|
||||
|
||||
[SerializeField]
|
||||
public List<ColliderGroup> _colliderGroups = new();
|
||||
|
||||
[Range(0, 1)]
|
||||
public float _clothFactor = 0.5f;
|
||||
|
||||
// runtime
|
||||
bool _initialized = false;
|
||||
List<Strand> _strands = new List<Strand>();
|
||||
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<SphereCapsuleCollider>(head.gameObject);
|
||||
c.Tail = tail;
|
||||
c.Radius = radius;
|
||||
c.GizmoColor = GetGizmoColor(group);
|
||||
group.Colliders.Add(c);
|
||||
}
|
||||
|
||||
static T GetOrAddComponent<T>(GameObject o) where T : Component
|
||||
{
|
||||
var t = o.GetComponent<T>();
|
||||
if (t != null)
|
||||
{
|
||||
return t;
|
||||
}
|
||||
return o.AddComponent<T>();
|
||||
}
|
||||
|
||||
HashSet<int> _clothUsedParticles = new();
|
||||
|
||||
public void InitializeCloth(
|
||||
StrandGroup g,
|
||||
ParticleList list,
|
||||
List<Strand> 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<Strand>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// すべての Particle を Init 状態にする。
|
||||
/// Verlet の Prev を現在地に更新する(速度0)。
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67a32b7c5e169f64daa6a8717d3de4f1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9c034f40b6adaf4ba23f456fd5a2ec1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// フックの法則
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9f187b56235e024bb4edef8b3bb2142
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<RotateParticle> Particles = new();
|
||||
|
||||
public void UpdateRoot(IReadOnlyList<Transform> 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<Transform> transforms, IReadOnlyList<Vector3> 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<Transform> 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<Transform> 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<Transform> transforms)
|
||||
{
|
||||
var root = Particles[0];
|
||||
_ResetRecursive(transforms, root);
|
||||
}
|
||||
|
||||
void _ResetRecursive(IReadOnlyList<Transform> 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<Transform> transforms, IReadOnlyList<Vector3> 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<Transform> transforms, IReadOnlyList<Vector3> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9aee349d7a2c2344a232a0e158f57dc
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfa6b58a8a8571e478cf6bac4a00a23d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
1756
Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity
Normal file
1756
Assets/VRM10_Samples/ClothSample/RotateParticle/Scenes/sample.unity
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7caaf005bf5d55e41860411c0c3d0710
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/VRM10_Samples/ClothSample/SphereTriangle.meta
Normal file
8
Assets/VRM10_Samples/ClothSample/SphereTriangle.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36a9770569fe64c4f98dbe384596e4b2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
117
Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs
Normal file
117
Assets/VRM10_Samples/ClothSample/SphereTriangle/CapsuleInfo.cs
Normal file
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// min and max is tail and head.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a797b9c09763964d84ef1497b260fb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<SphereCapsuleCollider, float> _initialColliderNormalSide = new();
|
||||
|
||||
/// <summary>
|
||||
/// two triangles
|
||||
/// d x-x c
|
||||
/// |/|
|
||||
/// a x-x b
|
||||
/// </summary>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="c"></param>
|
||||
/// <param name="d"></param>
|
||||
public ClothRectCollision(
|
||||
int a, int b, int c, int d)
|
||||
{
|
||||
_a = a;
|
||||
_b = b;
|
||||
_c = c;
|
||||
_d = d;
|
||||
}
|
||||
|
||||
public void InitializeColliderSide(PositionList list, IReadOnlyList<ColliderGroup> 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<SphereCapsuleCollider> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 衝突して移動デルタを得る
|
||||
/// </summary>
|
||||
/// <param name="collider"></param>
|
||||
/// <param name="t"></param>
|
||||
/// <param name="l"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="triangle"></param>
|
||||
/// <param name="collider"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns>collider => 衝突点 への線分を返す</returns>
|
||||
static bool TryCollideSphere(in Triangle triangle, in Vector3 collider, float radius, out LineSegment l)
|
||||
{
|
||||
var p = triangle.Plane.ClosestPointOnPlane(collider);
|
||||
var distance = Vector3.Distance(p, collider);
|
||||
if (distance > radius)
|
||||
{
|
||||
l = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (triangle.IsSameSide(p))
|
||||
{
|
||||
l = new LineSegment(collider, p);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c16dea6c47fa81d4184547a209d8fe61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<SphereCapsuleCollider> Colliders = new List<SphereCapsuleCollider>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07647b77d9934cd42b0bd0ab880ed321
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs
Normal file
20
Assets/VRM10_Samples/ClothSample/SphereTriangle/FrameRate.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class FrameRate : MonoBehaviour
|
||||
{
|
||||
[Range(15, 200)]
|
||||
public int _FrameRate = 30;
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
{
|
||||
}
|
||||
|
||||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
Application.targetFrameRate = _FrameRate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 39108f27e89d7514d86e56e2ffb0550c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<SphereCapsuleCollider>();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cceeecd3d3db5cd4ba74dfa6490e38b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
160
Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs
Normal file
160
Assets/VRM10_Samples/ClothSample/SphereTriangle/LineSegment.cs
Normal file
@@ -0,0 +1,160 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public readonly struct LineSegment
|
||||
{
|
||||
public readonly Vector3 Start;
|
||||
public readonly Vector3 End;
|
||||
|
||||
public Vector3 Vector => End - Start;
|
||||
public Ray Ray => new Ray(Start, Vector);
|
||||
public float Length => Vector.magnitude;
|
||||
public float SqLength => Vector.sqrMagnitude;
|
||||
|
||||
public LineSegment(in Vector3 start, in Vector3 end) => (Start, End) = (start, end);
|
||||
|
||||
public Vector3 GetPoint(float t)
|
||||
{
|
||||
return Start + Vector * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 球との衝突時の移動ベクトル
|
||||
/// Start が球の中心。End 衝突点。
|
||||
/// </summary>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns></returns>
|
||||
public Vector3 GetDelta(float radius)
|
||||
{
|
||||
return Vector.normalized * (radius - Length);
|
||||
}
|
||||
|
||||
public float Project(in Vector3 p)
|
||||
{
|
||||
var dir = p - Start;
|
||||
return Vector3.Dot(Vector.normalized, dir) / dir.magnitude;
|
||||
}
|
||||
|
||||
// P(s)
|
||||
// Q(t)
|
||||
public static (float s, float t) CalcClosest(in LineSegment p, in LineSegment q)
|
||||
{
|
||||
var d_p2 = Vector3.Dot(p.Vector, p.Vector);
|
||||
var d_q2 = Vector3.Dot(q.Vector, q.Vector);
|
||||
var d_pq = Vector3.Dot(p.Vector, q.Vector);
|
||||
var d_q_p_p = Vector3.Dot(q.Start - p.Start, p.Vector);
|
||||
var d_p_q_q = Vector3.Dot(p.Start - q.Start, q.Vector);
|
||||
var denom = d_p2 * d_q2 - d_pq * d_pq;
|
||||
if (denom < 1e-4)
|
||||
{
|
||||
var pq = q.Start - p.Start;
|
||||
var d = Vector3.Dot(p.Vector, pq);
|
||||
var s = d / p.Length;
|
||||
return (s, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var f = 1 / denom;
|
||||
var s = d_q2 * d_q_p_p + d_pq * d_p_q_q;
|
||||
var t = d_pq * d_q_p_p + d_p2 * d_p_q_q;
|
||||
return (f * s, f * t);
|
||||
}
|
||||
}
|
||||
|
||||
// public static bool Intersect(in LineSegment p, in LineSegment q, out float s, out float t)
|
||||
// {
|
||||
// (s, t) = CalcClosest(p, q);
|
||||
// if (s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0)
|
||||
// {
|
||||
// if (Vector3.Distance(p.GetPoint(s), q.GetPoint(t)) < 1e4f)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
public bool TryClampPlaneDistance(in Plane p, float distance, out LineSegment clamped, out Vector3 o)
|
||||
{
|
||||
var ray = Ray;
|
||||
var hit = p.Raycast(ray, out var t);
|
||||
if (!hit && t == 0)
|
||||
{
|
||||
// 平行
|
||||
clamped = default;
|
||||
o = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
o = ray.GetPoint(t);
|
||||
var vs = Start - o;
|
||||
var ve = End - o;
|
||||
|
||||
var ds = p.GetDistanceToPoint(Start);
|
||||
var de = p.GetDistanceToPoint(End);
|
||||
var ts = 1.0f;
|
||||
var te = 1.0f;
|
||||
if (ds > 0)
|
||||
{
|
||||
if (de > 0)
|
||||
{
|
||||
//+s
|
||||
//+e
|
||||
//==
|
||||
if (ds > distance && de > distance)
|
||||
{
|
||||
clamped = default;
|
||||
return false;
|
||||
}
|
||||
ts = Mathf.Min(distance, ds) / ds;
|
||||
te = Mathf.Min(distance, de) / de;
|
||||
}
|
||||
else
|
||||
{
|
||||
//+s
|
||||
//==
|
||||
//-e
|
||||
ts = Mathf.Min(distance, ds) / ds;
|
||||
te = Mathf.Max(-distance, de) / de;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (de < 0)
|
||||
{
|
||||
//==
|
||||
//-s
|
||||
//-e
|
||||
if (ds < -distance && de < -distance)
|
||||
{
|
||||
clamped = default;
|
||||
return false;
|
||||
}
|
||||
ts = Mathf.Max(-distance, ds) / ds;
|
||||
te = Mathf.Max(-distance, de) / de;
|
||||
}
|
||||
else
|
||||
{
|
||||
//+e
|
||||
//==
|
||||
//-s
|
||||
ts = Mathf.Max(-distance, ds) / ds;
|
||||
te = Mathf.Min(distance, de) / de;
|
||||
}
|
||||
}
|
||||
|
||||
var cs = new Ray(o, vs).GetPoint(vs.magnitude * ts);
|
||||
var ce = new Ray(o, ve).GetPoint(ve.magnitude * te);
|
||||
clamped = new(cs, ce);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DrawGizmos(float radius = 0.01f)
|
||||
{
|
||||
Gizmos.DrawLine(Start, End);
|
||||
Gizmos.DrawWireSphere(Start, radius);
|
||||
Gizmos.DrawWireSphere(End, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: add9de55aee3f344cab64a1c99c7c6aa
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
103
Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs
Normal file
103
Assets/VRM10_Samples/ClothSample/SphereTriangle/PositionList.cs
Normal file
@@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 衝突した。移動を蓄積
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="pos"></param>
|
||||
public void CollisionMove(int index, in LineSegment l, float radius, float factor = 1.0f)
|
||||
{
|
||||
using var profile = new ProfileSample("CollisionMove");
|
||||
if (Mass[index] > 0)
|
||||
{
|
||||
Delta[index] += l.GetDelta(radius) * factor;
|
||||
++CollisionCount[index];
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Vector3> Resolve()
|
||||
{
|
||||
for (int i = 0; i < Positions.Length; ++i)
|
||||
{
|
||||
if (CollisionCount[i] > 0)
|
||||
{
|
||||
Result[i] = Positions[i] + Delta[i] / CollisionCount[i];
|
||||
}
|
||||
else
|
||||
{
|
||||
Result[i] = Positions[i];
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 衝突前後の状態を描画
|
||||
/// </summary>
|
||||
public void DrawGizmos()
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
for (int i = 0; i < Positions.Length; ++i)
|
||||
{
|
||||
if (CollisionCount[i] > 0)
|
||||
{
|
||||
Gizmos.DrawLine(Positions[i], Result[i]);
|
||||
// Gizmos.DrawWireSphere(Positions[i], 0.01f);
|
||||
// Gizmos.DrawSphere(Result[i], 0.01f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 836b32456872256409962a684cbee1b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8375e9fc2aee4f448e349401c0bf068
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide sphere a and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="from"></param>
|
||||
/// <param name="ra"></param>
|
||||
/// <param name="to"></param>
|
||||
/// <param name="ba"></param>
|
||||
/// <param name="resolved"></param>
|
||||
/// <returns></returns>
|
||||
static bool TryCollideSphereAndSphere(
|
||||
in Vector3 from, float ra,
|
||||
in Vector3 to, float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var d = Vector3.Distance(from, to);
|
||||
if (d > (ra + rb))
|
||||
{
|
||||
resolved = default;
|
||||
return false;
|
||||
}
|
||||
Vector3 normal = (to - from).normalized;
|
||||
resolved = new(from, from + normal * (d - rb));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collide capsule and sphere b.
|
||||
/// move sphere b to resolved if collide.
|
||||
/// </summary>
|
||||
/// <param name="capsuleHead"></param>
|
||||
/// <param name="capsuleTail"></param>
|
||||
/// <param name="capsuleRadius"></param>
|
||||
/// <param name="b"></param>
|
||||
/// <param name="rb"></param>
|
||||
static bool TryCollideCapsuleAndSphere(
|
||||
in Vector3 capsuleHead,
|
||||
in Vector3 capsuleTail,
|
||||
float capsuleRadius,
|
||||
in Vector3 b,
|
||||
float rb,
|
||||
out LineSegment resolved
|
||||
)
|
||||
{
|
||||
var P = (capsuleTail - capsuleHead).normalized;
|
||||
var Q = b - capsuleHead;
|
||||
var dot = Vector3.Dot(P, Q);
|
||||
if (dot <= 0)
|
||||
{
|
||||
// head側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
var t = dot / P.magnitude;
|
||||
if (t >= 1.0f)
|
||||
{
|
||||
// tail側半球の球判定
|
||||
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
// head-tail上の m_transform.position との最近点
|
||||
var p = capsuleHead + P * t;
|
||||
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// collision for strand
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="resolved"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryCollide(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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d8072930ebf6d34ea30547c75dfb378
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "SphereTriangle",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0138a2c7639266f45b948ea162125de5
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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<Transform> Roots = new List<Transform>();
|
||||
|
||||
[SerializeField]
|
||||
[Range(0.001f, 0.5f)]
|
||||
public float DefaultStrandRaius = 0.05f;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efab592ca762fea46adb3c38b41e65d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
public class TransformSort : IComparer<Transform>
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcbd60313a3b8e34fbe465e9bcbcab98
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
285
Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs
Normal file
285
Assets/VRM10_Samples/ClothSample/SphereTriangle/Triangle.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace SphereTriangle
|
||||
{
|
||||
/// <summary>
|
||||
/// 三角形との交差が 2 => 交点 と 交点
|
||||
/// 三角形との交差が 1 => 交点 と 三角形内の線分端点
|
||||
/// 三角形との交差が 0 => 線分の端点が両方とも三角形内の場合。端点 と 端点
|
||||
/// </summary>
|
||||
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 };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// p がすべての辺の同じ側(内積的な意味で)にある => 三角形の内側にある
|
||||
/// </summary>
|
||||
/// <param name="p"></param>
|
||||
/// <returns></returns>
|
||||
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;
|
||||
}
|
||||
|
||||
// /// <summary>
|
||||
// /// p から lerp の係数を計算
|
||||
// /// </summary>
|
||||
// /// <param name="p0"></param>
|
||||
// /// <param name="p1"></param>
|
||||
// /// <param name="p"></param>
|
||||
// /// <returns></returns>
|
||||
// static float getT(in Vector3 p0, in Vector3 p1, in Vector3 p)
|
||||
// {
|
||||
// return (p - p0).magnitude / (p1 - p0).magnitude;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 三角形と線分の交差を判定する。
|
||||
// /// </summary>
|
||||
// /// <param name="p0">線分始点(t=0)。三角形 abc と同一平面を期待</param>
|
||||
// /// <param name="p1">線分終点(t=1)。三角形 abc と同一平面を期待</param>
|
||||
// /// <returns></returns>
|
||||
// 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;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 線分と線分の交差を判定する
|
||||
// /// a-b d
|
||||
// /// /
|
||||
// /// c
|
||||
// /// https://qiita.com/zu_rin/items/09876d2c7ec12974bc0f
|
||||
// /// </summary>
|
||||
// /// <param name="a"></param>
|
||||
// /// <param name="b"></param>
|
||||
// /// <param name="c"></param>
|
||||
// /// <param name="d"></param>
|
||||
// /// <returns></returns>
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2558c27234db0c741a6821682205da57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// カプセルStartの投影点が三角形内部
|
||||
/// </summary>
|
||||
public LineSegment? StartInTriangle;
|
||||
/// <summary>
|
||||
/// カプセルEndの投影点が三角形内部
|
||||
/// </summary>
|
||||
public LineSegment? EndInTriangle;
|
||||
|
||||
#if USE_VERTEX_DISTANCE
|
||||
/// <summary>
|
||||
/// カプセル線分と頂点Aの距離が半径以内
|
||||
/// </summary>
|
||||
public LineSegment? VertexADistance;
|
||||
/// <summary>
|
||||
/// カプセル線分と頂点Bの距離が半径以内
|
||||
/// </summary>
|
||||
public LineSegment? VertexBDistance;
|
||||
/// <summary>
|
||||
/// カプセル線分と頂点Cの距離が半径以内
|
||||
/// </summary>
|
||||
public LineSegment? VertexCDistance;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// カプセル線分と辺ABの距離が半径以内
|
||||
/// </summary>
|
||||
public LineSegment? EdgeABDistance;
|
||||
/// <summary>
|
||||
/// カプセル線分と辺BCの距離が半径以内
|
||||
/// </summary>
|
||||
public LineSegment? EdgeBCDistance;
|
||||
/// <summary>
|
||||
/// カプセル線分と辺CAの距離が半径以内
|
||||
/// </summary>
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Capsule clamped by radius distance from plane
|
||||
/// </summary>
|
||||
public LineSegment Clamped;
|
||||
|
||||
/// <summary>
|
||||
/// capsule line intersect plane
|
||||
/// </summary>
|
||||
public Vector3 O;
|
||||
|
||||
/// <summary>
|
||||
/// capsule Start project plane
|
||||
/// </summary>
|
||||
public Vector3 PS;
|
||||
/// <summary>
|
||||
/// capsule End project plane
|
||||
/// </summary>
|
||||
public Vector3 PE;
|
||||
|
||||
public int CollisionCount = 0;
|
||||
|
||||
public Result Result;
|
||||
}
|
||||
// 複数コライダーのデバッグ表示のため
|
||||
public Dictionary<SphereCapsuleCollider, Status> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4efecee94290be43b3affbcc6bd191a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c201feca8fd8c644b987521e89706c4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user