// Magica Cloth.
// Copyright (c) MagicaSoft, 2020-2022.
// https://magicasoft.jp
using Unity.Mathematics;
using UnityEngine;
namespace MagicaCloth
{
///
/// カプセルコライダー
///
[HelpURL("https://magicasoft.jp/magica-cloth-capsule-collider/")]
[AddComponentMenu("MagicaCloth/MagicaCapsuleCollider")]
public class MagicaCapsuleCollider : ColliderComponent
{
// 軸
public enum Axis
{
X,
Y,
Z,
}
[SerializeField]
private Axis axis = Axis.X;
[SerializeField]
[Range(0, 1)]
private float length = 0.2f;
[SerializeField]
[Range(0.0f, 0.5f)]
private float startRadius = 0.1f;
[SerializeField]
[Range(0.0f, 0.5f)]
private float endRadius = 0.1f;
//=========================================================================================
public override ComponentType GetComponentType()
{
return ComponentType.CapsuleCollider;
}
private void OnValidate()
{
if (Application.isPlaying)
DataUpdate();
}
///
/// パーティクルのデータ更新処理
///
internal override void DataUpdate()
{
base.DataUpdate();
foreach (var c in particleDict.Values)
{
for (int i = 0; i < c.dataLength; i++)
{
int pindex = c.startIndex + i;
// カプセルデータ
float3 radius = new float3(length, startRadius, endRadius);
MagicaPhysicsManager.Instance.Particle.SetRadius(pindex, radius);
// localPos
MagicaPhysicsManager.Instance.Particle.SetLocalPos(pindex, Center);
// カプセルフラグ再設定
var flag = MagicaPhysicsManager.Instance.Particle.flagList[pindex];
flag.SetFlag(PhysicsManagerParticleData.Flag_CapsuleX, false);
flag.SetFlag(PhysicsManagerParticleData.Flag_CapsuleY, false);
flag.SetFlag(PhysicsManagerParticleData.Flag_CapsuleZ, false);
flag.SetFlag(GetCapsuleFlag(), true);
MagicaPhysicsManager.Instance.Particle.flagList[pindex] = flag;
}
}
}
///
/// データハッシュ計算
///
///
public override int GetDataHash()
{
int hash = base.GetDataHash();
hash += axis.GetDataHash();
hash += length.GetDataHash();
hash += startRadius.GetDataHash();
hash += endRadius.GetDataHash();
return hash;
}
//=========================================================================================
public Axis AxisMode
{
get
{
return axis;
}
set
{
axis = value;
ReserveDataUpdate();
}
}
public float Length
{
get
{
return length;
}
set
{
length = value;
ReserveDataUpdate();
}
}
public float StartRadius
{
get
{
return startRadius;
}
set
{
startRadius = value;
ReserveDataUpdate();
}
}
public float EndRadius
{
get
{
return endRadius;
}
set
{
endRadius = value;
ReserveDataUpdate();
}
}
protected override ChunkData CreateColliderParticleReal(int teamId)
{
uint flag = 0;
flag |= PhysicsManagerParticleData.Flag_Kinematic;
flag |= PhysicsManagerParticleData.Flag_Collider;
flag |= GetCapsuleFlag();
flag |= PhysicsManagerParticleData.Flag_Transform_Read_Base;
flag |= PhysicsManagerParticleData.Flag_Step_Update;
flag |= PhysicsManagerParticleData.Flag_Reset_Position;
flag |= PhysicsManagerParticleData.Flag_Transform_Read_Local;
//flag |= PhysicsManagerParticleData.Flag_Transform_Read_Scl; // 現在スケールは見ていない
// radiusにカプセルのデータを入れる
float3 radius = new float3(length, startRadius, endRadius);
var c = CreateParticle(
flag,
teamId, // team
0.0f, // depth
radius,
Center
);
if (c.IsValid())
MagicaPhysicsManager.Instance.Team.AddCollider(teamId, c.startIndex);
return c;
}
///
/// 設定軸に対応するカプセルフラグを返す
///
///
uint GetCapsuleFlag()
{
if (axis == Axis.X)
return PhysicsManagerParticleData.Flag_CapsuleX;
else if (axis == Axis.Y)
return PhysicsManagerParticleData.Flag_CapsuleY;
else
return PhysicsManagerParticleData.Flag_CapsuleZ;
}
///
/// カプセルのローカル方向を返す
///
///
public Vector3 GetLocalDir()
{
if (axis == Axis.X)
return Vector3.right;
else if (axis == Axis.Y)
return Vector3.up;
else
return Vector3.forward;
}
///
/// カプセルのローカル上方向を返す
///
///
public Vector3 GetLocalUp()
{
if (axis == Axis.X)
return Vector3.up;
else if (axis == Axis.Y)
return Vector3.forward;
else
return Vector3.up;
}
///
/// カプセルのスケール値を取得
/// 方向軸のスケール値を採用する
///
///
public float GetScale()
{
var scl = transform.lossyScale;
if (axis == Axis.X)
return scl.x;
else if (axis == Axis.Y)
return scl.y;
else
return scl.z;
}
///
/// 指定座標に最も近い衝突点pと、中心軸からのpへの方向dirを返す。
/// ※エディタ計算用
///
///
///
///
public override bool CalcNearPoint(Vector3 pos, out Vector3 p, out Vector3 dir, out Vector3 d, bool skinning)
{
dir = Vector3.zero;
var ldir = GetLocalDir();
var l = ldir * Length;
//var tpos = transform.position;
var tpos = transform.TransformPoint(Center);
var trot = transform.rotation;
float scl = GetScale();
l *= scl;
var spos = trot * -l + tpos;
var epos = trot * l + tpos;
#if true
// 半径分長さ拡張
if (skinning == false)
{
const float ratio = 0.5f;
spos = trot * (-l - ldir * StartRadius * scl * ratio) + tpos;
epos = trot * (l + ldir * EndRadius * scl * ratio) + tpos;
}
#endif
float t = MathUtility.ClosestPtPointSegmentRatio(pos, spos, epos);
#if true
// 蓋の部分は無効とする
if (skinning == false)
{
if (t < 0.0001f || t > 0.9999f)
{
p = Vector3.zero;
d = Vector3.zero;
return false;
}
}
#endif
float cr = Mathf.Lerp(StartRadius * scl, EndRadius * scl, t);
d = spos + (epos - spos) * t; // 中心軸位置
var v = pos - d;
float vlen = v.magnitude;
if (vlen < cr)
{
// 衝突している
p = pos;
if (vlen > 0.0f)
dir = v.normalized;
}
else
{
dir = v.normalized;
p = d + dir * cr;
}
return true;
}
}
}