200 lines
9.4 KiB
C#

using System;
using Unity.Collections;
using Unity.Jobs;
using UnityEngine;
using UniVRM10.FastSpringBones.Blittables;
#if ENABLE_SPRINGBONE_BURST
using Unity.Burst;
#endif
namespace UniVRM10.FastSpringBones.System
{
#if ENABLE_SPRINGBONE_BURST
[BurstCompile]
#endif
public struct UpdateFastSpringBoneJob : IJobParallelFor
{
[ReadOnly] public NativeArray<BlittableSpring> Springs;
[ReadOnly] public NativeArray<BlittableJoint> Joints;
[ReadOnly] public NativeArray<BlittableCollider> Colliders;
[NativeDisableParallelForRestriction] public NativeArray<BlittableLogic> Logics;
[NativeDisableParallelForRestriction] public NativeArray<BlittableTransform> Transforms;
public float DeltaTime;
public unsafe void Execute(int index)
{
var spring = Springs[index];
var transformIndexOffset = spring.transformIndexOffset;
var colliderSpan = spring.colliderSpan;
var logicSpan = spring.logicSpan;
for (var logicIndex = logicSpan.startIndex; logicIndex < logicSpan.startIndex + logicSpan.count; ++logicIndex)
{
var logic = Logics[logicIndex];
var joint = Joints[logicIndex];
var headTransform = Transforms[logic.headTransformIndex + transformIndexOffset];
var parentTransform = logic.parentTransformIndex >= 0
? Transforms[logic.parentTransformIndex + transformIndexOffset]
: (BlittableTransform?)null;
var centerTransform = spring.centerTransformIndex >= 0
? Transforms[spring.centerTransformIndex + transformIndexOffset]
: (BlittableTransform?)null;
// 親があったら、親に依存するTransformを再計算
if (parentTransform.HasValue)
{
headTransform.position =
parentTransform.Value.localToWorldMatrix.MultiplyPoint3x4(headTransform.localPosition);
headTransform.rotation = parentTransform.Value.rotation * headTransform.localRotation;
}
var currentTail = centerTransform.HasValue
? centerTransform.Value.localToWorldMatrix.MultiplyPoint3x4(logic.currentTail)
: logic.currentTail;
var prevTail = centerTransform.HasValue
? centerTransform.Value.localToWorldMatrix.MultiplyPoint3x4(logic.prevTail)
: logic.prevTail;
var parentRotation = parentTransform?.rotation ?? Quaternion.identity;
// verlet積分で次の位置を計算
var external = (joint.gravityDir * joint.gravityPower + spring.ExternalData->ExternalForce) * DeltaTime;
var nextTail = currentTail
+ (currentTail - prevTail) * (1.0f - joint.dragForce) // 前フレームの移動を継続する(減衰もあるよ)
+ parentRotation * logic.localRotation * logic.boneAxis *
joint.stiffnessForce * DeltaTime // 親の回転による子ボーンの移動目標
+ external; // 外力による移動量
// 長さをboneLengthに強制
nextTail = headTransform.position + (nextTail - headTransform.position).normalized * logic.length;
// Collisionで移動
for (var colliderIndex = colliderSpan.startIndex; colliderIndex < colliderSpan.startIndex + colliderSpan.count; ++colliderIndex)
{
var collider = Colliders[colliderIndex];
var colliderTransform = Transforms[collider.transformIndex + transformIndexOffset];
var colliderScale = colliderTransform.localToWorldMatrix.lossyScale;
var maxColliderScale = Mathf.Max(Mathf.Max(Mathf.Abs(colliderScale.x), Mathf.Abs(colliderScale.y)), Mathf.Abs(colliderScale.z));
var worldPosition = colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.offset);
var worldTail = colliderTransform.localToWorldMatrix.MultiplyPoint3x4(collider.tail);
switch (collider.colliderType)
{
case BlittableColliderType.Sphere:
ResolveSphereCollision(joint, collider, worldPosition, headTransform, maxColliderScale, logic, ref nextTail);
break;
case BlittableColliderType.Capsule:
ResolveCapsuleCollision(worldTail, worldPosition, headTransform, joint, collider, maxColliderScale, logic, ref nextTail);
break;
}
}
logic.prevTail = centerTransform.HasValue
? centerTransform.Value.worldToLocalMatrix.MultiplyPoint3x4(currentTail)
: currentTail;
logic.currentTail = centerTransform.HasValue
? centerTransform.Value.worldToLocalMatrix.MultiplyPoint3x4(nextTail)
: nextTail;
//回転を適用
var rotation = parentRotation * logic.localRotation;
headTransform.rotation = Quaternion.FromToRotation(rotation * logic.boneAxis,
nextTail - headTransform.position) * rotation;
// Transformを更新
if (parentTransform.HasValue)
{
var parentLocalToWorldMatrix = parentTransform.Value.localToWorldMatrix;
headTransform.localRotation = Normalize(Quaternion.Inverse(parentTransform.Value.rotation) * headTransform.rotation);
headTransform.localToWorldMatrix =
parentLocalToWorldMatrix *
Matrix4x4.TRS(
headTransform.localPosition,
headTransform.localRotation,
headTransform.localScale
);
headTransform.worldToLocalMatrix = headTransform.localToWorldMatrix.inverse;
}
else
{
headTransform.localToWorldMatrix =
Matrix4x4.TRS(
headTransform.position,
headTransform.rotation,
headTransform.localScale
);
headTransform.worldToLocalMatrix = headTransform.localToWorldMatrix.inverse;
headTransform.localRotation = headTransform.rotation;
}
// 値をバッファに戻す
Transforms[logic.headTransformIndex + transformIndexOffset] = headTransform;
Logics[logicIndex] = logic;
}
}
/// <summary>
/// BurstではMathfがエラーを吐くため、内部でMathfを呼ばないNormalizeを自前実装
/// </summary>
private static Quaternion Normalize(Quaternion q)
{
var num = (float)Math.Sqrt(Quaternion.Dot(q, q));
return num < float.Epsilon ? Quaternion.identity : new Quaternion(q.x / num, q.y / num, q.z / num, q.w / num);
}
private static void ResolveCapsuleCollision(
Vector3 worldTail,
Vector3 worldPosition,
BlittableTransform headTransform,
BlittableJoint joint,
BlittableCollider collider,
float maxColliderScale,
BlittableLogic logic,
ref Vector3 nextTail)
{
var P = (worldTail - worldPosition).normalized;
var Q = headTransform.position - worldPosition;
var dot = Vector3.Dot(P, Q);
if (dot <= 0)
{
// head側半球の球判定
ResolveSphereCollision(joint, collider, worldPosition, headTransform, maxColliderScale, logic, ref nextTail);
}
var t = dot / P.magnitude;
if (t >= 1.0f)
{
// tail側半球の球判定
ResolveSphereCollision(joint, collider, worldTail, headTransform, maxColliderScale, logic, ref nextTail);
}
// head-tail上の m_transform.position との最近点
var p = worldPosition + P * t;
ResolveSphereCollision(joint, collider, p, headTransform, maxColliderScale, logic, ref nextTail);
}
private static void ResolveSphereCollision(
BlittableJoint joint,
BlittableCollider collider,
Vector3 worldPosition,
BlittableTransform headTransform,
float maxColliderScale,
BlittableLogic logic,
ref Vector3 nextTail)
{
var r = joint.radius + collider.radius * maxColliderScale;
if (Vector3.SqrMagnitude(nextTail - worldPosition) <= (r * r))
{
// ヒット。Colliderの半径方向に押し出す
var normal = (nextTail - worldPosition).normalized;
var posFromCollider = worldPosition + normal * r;
// 長さをboneLengthに強制
nextTail = headTransform.position + (posFromCollider - headTransform.position).normalized * logic.length;
}
}
}
}