SphereCapsuleCollider to VRM10SpringBoneCollider

This commit is contained in:
ousttrue 2024-11-16 04:03:47 +09:00
parent a249fc7f37
commit 0732779721
15 changed files with 256 additions and 250 deletions

View File

@ -28,12 +28,36 @@ namespace UniVRM10
public Vector3 Normal = Vector3.up;
public Vector3 HeadWorldPosition => transform.TransformPoint(Offset);
public Vector3 TailWorldPosition => transform.TransformPoint(TailOrNormal);
public Vector3 TailOrNormal => ColliderType == VRM10SpringBoneColliderTypes.Plane ? Normal : Tail;
public static int SelectedGuid;
public bool IsSelected => GetInstanceID() == SelectedGuid;
public Bounds GetBounds()
{
switch (ColliderType)
{
case VRM10SpringBoneColliderTypes.Capsule:
{
var h = HeadWorldPosition;
var t = TailWorldPosition;
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;
}
case VRM10SpringBoneColliderTypes.Sphere:
return new Bounds(HeadWorldPosition, new Vector3(Radius, Radius, Radius));
default:
throw new NotImplementedException();
}
}
void OnDrawGizmosSelected()
{
DrawGizmos();

View File

@ -552,7 +552,11 @@ namespace UniVRM10.Cloth.Viewer
void OnInit(RotateParticle.RotateParticleSystem system, Vrm10Instance vrm)
{
var animator = vrm.GetComponent<Animator>();
HumanoidCollider.AddColliders(system._colliderGroups, animator);
if (vrm.SpringBone.ColliderGroups.Count == 0)
{
HumanoidCollider.AddColliders(animator);
}
try
{
ClothGuess.Guess(animator);

View File

@ -23,51 +23,31 @@ namespace UniVRM10.Cloth.Viewer
("Arm", HumanBodyBones.RightHand, HumanBodyBones.RightMiddleProximal, 0.02f),
};
public static void AddColliders(List<ColliderGroup> _colliderGroups, Animator animator)
public static void AddColliders(Animator animator)
{
foreach (var (group, head, tail, radius) in Capsules)
{
AddColliderIfNotExists(_colliderGroups, group,
animator.GetBoneTransform(head),
animator.GetBoneTransform(tail),
radius);
}
}
Dictionary<string, VRM10SpringBoneColliderGroup> map = new();
static void AddColliderIfNotExists(List<ColliderGroup> _colliderGroups, string groupName,
Transform head, Transform tail, float radius)
{
ColliderGroup group = default;
foreach (var g in _colliderGroups)
foreach (var (group, _head, _tail, radius) in Capsules)
{
if (g.Name == groupName)
if (!map.ContainsKey(group))
{
group = g;
break;
var g = animator.gameObject.AddComponent<VRM10SpringBoneColliderGroup>();
map.Add(group, g);
}
var head = animator.GetBoneTransform(_head);
var vrmCollider = head.gameObject.AddComponent<VRM10SpringBoneCollider>();
if (vrmCollider != null)
{
vrmCollider.Radius = radius;
vrmCollider.ColliderType = VRM10SpringBoneColliderTypes.Capsule;
var tail = animator.GetBoneTransform(_tail);
vrmCollider.Tail = head.worldToLocalMatrix.MultiplyPoint(tail.position);
map[group].Colliders.Add(vrmCollider);
}
}
if (group == null)
{
group = new ColliderGroup { Name = groupName };
_colliderGroups.Add(group);
}
foreach (var collider in group.Colliders)
{
if (collider._vrm.transform == head)
{
return;
}
}
var vrmCollider = head.gameObject.AddComponent<VRM10SpringBoneCollider>();
vrmCollider.Radius = radius;
vrmCollider.ColliderType = VRM10SpringBoneColliderTypes.Capsule;
vrmCollider.Tail = head.worldToLocalMatrix.MultiplyPoint(tail.position);
var c = new SphereCapsuleCollider(vrmCollider);
// c.GizmoColor = GetGizmoColor(group);
group.Colliders.Add(c);
}
static T GetOrAddComponent<T>(GameObject o) where T : Component

View File

@ -15,9 +15,11 @@ namespace RotateParticle.Components
[SerializeField]
public List<RectCloth> Cloths = new();
IVrm10SpringBoneRuntime m_runtime;
public IVrm10SpringBoneRuntime CreateSpringBoneRuntime()
{
return new RotateParticleSpringboneRuntime();
m_runtime = new RotateParticleSpringboneRuntime();
return m_runtime;
}
public void Reset()
@ -25,5 +27,14 @@ namespace RotateParticle.Components
Warps = GetComponentsInChildren<Warp>().ToList();
Cloths = GetComponentsInChildren<RectCloth>().ToList();
}
void OnDrawGizmos()
{
if (m_runtime == null)
{
return;
}
m_runtime.DrawGizmos();
}
}
}

View File

@ -58,6 +58,14 @@ namespace RotateParticle
_system._cloths.Add(cloth);
}
foreach (var g in instance.SpringBone.ColliderGroups)
{
foreach (var vrmCollider in g.Colliders)
{
_system.AddColliderIfNotExists(g.name, vrmCollider);
}
}
await awaitCaller.NextFrame();
_system.Initialize();
}

View File

@ -4,6 +4,7 @@ using System.Linq;
using RotateParticle.Components;
using SphereTriangle;
using UnityEngine;
using UniVRM10;
namespace RotateParticle
@ -254,7 +255,7 @@ namespace RotateParticle
foreach (var c in g.Colliders)
{
// strand
if (c != null && c.TryCollide(p, particle.Init.Radius, out var resolved))
if (c != null && TryCollide(c, p, particle.Init.Radius, out var resolved))
{
_newPos.CollisionMove(particle.Init.Index, resolved, c.Radius);
}
@ -286,6 +287,123 @@ namespace RotateParticle
}
}
public ColliderGroup GetOrAddColliderGroup(string groupName)
{
foreach (var g in _colliderGroups)
{
if (g.Name == groupName)
{
return g;
}
}
var group = new ColliderGroup { Name = groupName };
_colliderGroups.Add(group);
return group;
}
public void AddColliderIfNotExists(string groupName,
VRM10SpringBoneCollider c)
{
var group = GetOrAddColliderGroup(groupName);
foreach (var collider in group.Colliders)
{
if (collider == c)
{
return;
}
}
// c.GizmoColor = GetGizmoColor(group);
group.Colliders.Add(c);
}
/// <summary>
/// collide sphere a and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="from"></param>
/// <param name="ra"></param>
/// <param name="to"></param>
/// <param name="ba"></param>
/// <param name="resolved"></param>
/// <returns></returns>
static bool TryCollideSphereAndSphere(
in Vector3 from, float ra,
in Vector3 to, float rb,
out LineSegment resolved
)
{
var d = Vector3.Distance(from, to);
if (d > (ra + rb))
{
resolved = default;
return false;
}
Vector3 normal = (to - from).normalized;
resolved = new(from, from + normal * (d - rb));
return true;
}
/// <summary>
/// collide capsule and sphere b.
/// move sphere b to resolved if collide.
/// </summary>
/// <param name="capsuleHead"></param>
/// <param name="capsuleTail"></param>
/// <param name="capsuleRadius"></param>
/// <param name="b"></param>
/// <param name="rb"></param>
static bool TryCollideCapsuleAndSphere(
in Vector3 capsuleHead,
in Vector3 capsuleTail,
float capsuleRadius,
in Vector3 b,
float rb,
out LineSegment resolved
)
{
var P = (capsuleTail - capsuleHead).normalized;
var Q = b - capsuleHead;
var dot = Vector3.Dot(P, Q);
if (dot <= 0)
{
// head側半球の球判定
return TryCollideSphereAndSphere(capsuleHead, capsuleRadius, b, rb, out resolved);
}
var t = dot / P.magnitude;
if (t >= 1.0f)
{
// tail側半球の球判定
return TryCollideSphereAndSphere(capsuleTail, capsuleRadius, b, rb, out resolved);
}
// head-tail上の m_transform.position との最近点
var p = capsuleHead + P * t;
return TryCollideSphereAndSphere(p, capsuleRadius, b, rb, out resolved);
}
/// <summary>
/// collision for strand
/// </summary>
/// <param name="p"></param>
/// <param name="radius"></param>
/// <param name="resolved"></param>
/// <returns></returns>
public bool TryCollide(UniVRM10.VRM10SpringBoneCollider c, in Vector3 p, float radius, out LineSegment resolved)
{
if (c.ColliderType == UniVRM10.VRM10SpringBoneColliderTypes.Capsule)
{
return TryCollideCapsuleAndSphere(c.HeadWorldPosition, c.TailWorldPosition, c.Radius, p, radius, out resolved);
}
else
{
return TryCollideSphereAndSphere(c.HeadWorldPosition, c.Radius, p, radius, out resolved);
}
}
public void DrawGizmos()
{
_list.DrawGizmos();

View File

@ -1,10 +1,11 @@
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
struct CapsuleInfo
{
public SphereCapsuleCollider Collider;
public VRM10SpringBoneCollider Collider;
public Triangle Triangle;
public Vector3 MinOnPlane;
@ -28,7 +29,7 @@ namespace SphereTriangle
public bool Intersected;
public CapsuleInfo(in Triangle t, SphereCapsuleCollider collider)
public CapsuleInfo(in Triangle t, VRM10SpringBoneCollider collider)
{
Collider = collider;
Triangle = t;

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
@ -23,7 +24,7 @@ namespace SphereTriangle
TriangleCapsuleCollisionSolver _s1 = new();
// 各コライダーが初期姿勢で三角形ABCの法線の正か負のどちらにあるのかを記録する
Dictionary<SphereCapsuleCollider, float> _initialColliderNormalSide = new();
Dictionary<VRM10SpringBoneCollider, float> _initialColliderNormalSide = new();
/// <summary>
/// two triangles
@ -81,7 +82,7 @@ namespace SphereTriangle
return GetBoundsFrom4(list.Get(_a), list.Get(_b), list.Get(_c), list.Get(_d));
}
public void Collide(PositionList list, IReadOnlyCollection<SphereCapsuleCollider> colliders)
public void Collide(PositionList list, IReadOnlyCollection<VRM10SpringBoneCollider> colliders)
{
using (new ProfileSample("Rect: Prepare"))
{
@ -160,9 +161,9 @@ namespace SphereTriangle
/// <param name="t"></param>
/// <param name="l"></param>
/// <returns></returns>
static bool TryCollide(TriangleCapsuleCollisionSolver solver, SphereCapsuleCollider collider, in Triangle t, out LineSegment l)
static bool TryCollide(TriangleCapsuleCollisionSolver solver, VRM10SpringBoneCollider collider, in Triangle t, out LineSegment l)
{
if (collider.IsCapsule)
if (collider.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
{
// capsule
TriangleCapsuleCollisionSolver.Result result = default;
@ -207,15 +208,9 @@ namespace SphereTriangle
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();
var closestPoint = triangle.GetClosest(collider);
l = new LineSegment(collider, closestPoint);
return true;
}
public void DrawGizmos()

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
@ -11,6 +12,6 @@ namespace SphereTriangle
public string Name;
[SerializeField]
public List<SphereCapsuleCollider> Colliders = new List<SphereCapsuleCollider>();
public List<VRM10SpringBoneCollider> Colliders = new();
}
}

View File

@ -1,23 +1,24 @@
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
public class LineDistanceGizmo : MonoBehaviour
{
public SphereCapsuleCollider LineA;
public SphereCapsuleCollider LineB;
public VRM10SpringBoneCollider LineA;
public VRM10SpringBoneCollider LineB;
void Reset()
{
if (LineA == null)
{
LineA = GetComponent<SphereCapsuleCollider>();
LineA = GetComponent<VRM10SpringBoneCollider>();
}
}
public void OnDrawGizmos()
{
if (LineA.IsCapsule && LineB.IsCapsule)
if (LineA.ColliderType == VRM10SpringBoneColliderTypes.Capsule && LineB.ColliderType == VRM10SpringBoneColliderTypes.Capsule)
{
var a = new LineSegment(LineA.HeadWorldPosition, LineA.TailWorldPosition);
var b = new LineSegment(LineB.HeadWorldPosition, LineB.TailWorldPosition);

View File

@ -1,173 +0,0 @@
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
public class SphereCapsuleCollider
{
public readonly VRM10SpringBoneCollider _vrm;
public Color GizmoColor = Color.yellow;
public bool SolidGizmo = false;
public float Radius => _vrm.Radius;
public bool IsCapsule => _vrm.ColliderType == VRM10SpringBoneColliderTypes.Capsule;
public Vector3 HeadWorldPosition => _vrm.transform.TransformPoint(_vrm.Offset);
public Vector3 TailWorldPosition => _vrm.transform.TransformPoint(_vrm.TailOrNormal);
public Ray? HeadTailRay => IsCapsule ? new Ray { origin = HeadWorldPosition, direction = TailWorldPosition - HeadWorldPosition } : null;
public float CapsuleLength => !IsCapsule ? 0 : Vector3.Distance(TailWorldPosition, HeadWorldPosition);
public SphereCapsuleCollider(VRM10SpringBoneCollider vrmCollider)
{
_vrm = vrmCollider;
}
public Bounds GetBounds()
{
if (IsCapsule)
{
var h = HeadWorldPosition;
var t = TailWorldPosition;
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(_vrm.Radius * 2);
return aabb;
}
else
{
return new Bounds(HeadWorldPosition, new Vector3(_vrm.Radius, _vrm.Radius, _vrm.Radius));
}
}
/// <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 (IsCapsule)
{
return TryCollideCapsuleAndSphere(HeadWorldPosition, TailWorldPosition, this.Radius, p, radius, out resolved);
}
else
{
return TryCollideSphereAndSphere(HeadWorldPosition, 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(HeadWorldPosition, Radius);
}
Gizmos.color = GizmoColor;
if (_vrm.transform.parent != null)
{
Gizmos.DrawLine(HeadWorldPosition, HeadWorldPosition);
}
if (IsCapsule)
{
DrawCapsuleGizmo(HeadWorldPosition, TailWorldPosition, Radius);
}
else
{
Gizmos.DrawWireSphere(HeadWorldPosition, Radius);
}
#if AABB_DEBUG
Gizmos.matrix = Matrix4x4.identity;
Gizmos.color = Color.magenta;
var aabb = GetBounds();
Gizmos.DrawWireCube(aabb.center, aabb.size);
#endif
}
}
}

View File

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

View File

@ -36,6 +36,10 @@ namespace SphereTriangle
public Vector3 b => Points[1];
public Vector3 c => Points[2];
public LineSegment AB => new LineSegment(a, b);
public LineSegment BC => new LineSegment(b, c);
public LineSegment CA => new LineSegment(c, a);
public Triangle(Vector3 a, Vector3 b, Vector3 c)
{
Plane = new Plane(a, b, c);
@ -269,6 +273,47 @@ namespace SphereTriangle
// };
// }
public Vector3 GetClosest(in Vector3 p)
{
var ab = AB;
var t_ab = ab.Project(p);
var p_ab = ab.GetPoint(t_ab);
var d_ab = Vector3.Distance(p, p_ab);
var bc = BC;
var t_bc = bc.Project(p);
var p_bc = bc.GetPoint(t_bc);
var d_bc = Vector3.Distance(p, p_bc);
var ca = CA;
var t_ca = ca.Project(p);
var p_ca = ca.GetPoint(t_ca);
var d_ca = Vector3.Distance(p, p_ca);
if (d_ab < d_bc)
{
if (d_ab < d_ca)
{
return p_ab;
}
else
{
return p_ca;
}
}
else
{
if (d_bc < d_ca)
{
return p_bc;
}
else
{
return p_ca;
}
}
}
public void DrawGizmos()
{
#if UNITY_2022_3_OR_NEWER

View File

@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
{
@ -129,14 +130,14 @@ namespace SphereTriangle
public Result Result;
}
// 複数コライダーのデバッグ表示のため
public Dictionary<SphereCapsuleCollider, Status> collider_status_map = new();
public Dictionary<VRM10SpringBoneCollider, 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)
public Result Collide(in Triangle t, VRM10SpringBoneCollider collider, in LineSegment capsule, float radius)
{
if (collider == null)
{

View File

@ -1,4 +1,5 @@
using UnityEngine;
using UniVRM10;
namespace SphereTriangle
@ -7,7 +8,7 @@ namespace SphereTriangle
{
public Transform B;
public Transform C;
public SphereCapsuleCollider Capsule;
public VRM10SpringBoneCollider Capsule;
void Reset()
{
@ -35,7 +36,7 @@ namespace SphereTriangle
if (C == null) return;
var t = new Triangle(transform.position, B.position, C.position);
if (!Capsule.IsCapsule) return;
if (Capsule.ColliderType != VRM10SpringBoneColliderTypes.Capsule) return;
var capsule = new LineSegment(Capsule.HeadWorldPosition, Capsule.TailWorldPosition);
_solver.BeginFrame();