using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Rigid_Bunny_by_Shape_Matching : MonoBehaviour
{
public bool launched = false;
Vector3[] X; // 每个点的位置
Vector3[] Q; // 每个点的旋转
Vector3[] V; // 每个点的速度
Vector3[] Y;
Matrix4x4 QQt = Matrix4x4.zero;
Vector3 G = new Vector3(0.0f, -9.8f, 0.0f);
float linear_decay = 0.999f;
Vector3 ground = new Vector3(0, 0.01f, 0);
Vector3 groundNormal = new Vector3(0, 1, 0);
Vector3 wall = new Vector3(2.01f, 0, 0);
Vector3 wallNormal = new Vector3(-1, 0, 0);
float mu_T = 0.5f; // μ_T may be coefficient of air resistance
float mu_N = 5.0f; // μ_N may be Coefficient of Restitution
// Start is called before the first frame update
void Start()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
V = new Vector3[mesh.vertices.Length];
X = mesh.vertices;
Y = X;
Q = mesh.vertices;
//Centerizing Q.
Vector3 c = Vector3.zero;
for (int i = 0; i < Q.Length; i++)
c += Q[i];
c /= Q.Length;
for (int i = 0; i < Q.Length; i++)
Q[i] -= c;
//Get QQ^t ready.
for (int i = 0; i < Q.Length; i++)
{
QQt[0, 0] += Q[i][0] * Q[i][0];
QQt[0, 1] += Q[i][0] * Q[i][1];
QQt[0, 2] += Q[i][0] * Q[i][2];
QQt[1, 0] += Q[i][1] * Q[i][0];
QQt[1, 1] += Q[i][1] * Q[i][1];
QQt[1, 2] += Q[i][1] * Q[i][2];
QQt[2, 0] += Q[i][2] * Q[i][0];
QQt[2, 1] += Q[i][2] * Q[i][1];
QQt[2, 2] += Q[i][2] * Q[i][2];
}
QQt[3, 3] = 1;
for (int i = 0; i < X.Length; i++)
V[i][0] = 4.0f;
Update_Mesh(transform.position, Matrix4x4.Rotate(transform.rotation), 0);
transform.position = Vector3.zero;
transform.rotation = Quaternion.identity;
}
// Polar Decomposition that returns the rotation from F.
Matrix4x4 Get_Rotation(Matrix4x4 F)
{
Matrix4x4 C = Matrix4x4.zero;
for (int ii = 0; ii < 3; ii++)
for (int jj = 0; jj < 3; jj++)
for (int kk = 0; kk < 3; kk++)
C[ii, jj] += F[kk, ii] * F[kk, jj];
Matrix4x4 C2 = Matrix4x4.zero;
for (int ii = 0; ii < 3; ii++)
for (int jj = 0; jj < 3; jj++)
for (int kk = 0; kk < 3; kk++)
C2[ii, jj] += C[ii, kk] * C[jj, kk];
float det = F[0, 0] * F[1, 1] * F[2, 2] +
F[0, 1] * F[1, 2] * F[2, 0] +
F[1, 0] * F[2, 1] * F[0, 2] -
F[0, 2] * F[1, 1] * F[2, 0] -
F[0, 1] * F[1, 0] * F[2, 2] -
F[0, 0] * F[1, 2] * F[2, 1];
float I_c = C[0, 0] + C[1, 1] + C[2, 2];
float I_c2 = I_c * I_c;
float II_c = 0.5f * (I_c2 - C2[0, 0] - C2[1, 1] - C2[2, 2]);
float III_c = det * det;
float k = I_c2 - 3 * II_c;
Matrix4x4 inv_U = Matrix4x4.zero;
if (k < 1e-10f)
{
float inv_lambda = 1 / Mathf.Sqrt(I_c / 3);
inv_U[0, 0] = inv_lambda;
inv_U[1, 1] = inv_lambda;
inv_U[2, 2] = inv_lambda;
}
else
{
float l = I_c * (I_c * I_c - 4.5f * II_c) + 13.5f * III_c;
float k_root = Mathf.Sqrt(k);
float value = l / (k * k_root);
if (value < -1.0f) value = -1.0f;
if (value > 1.0f) value = 1.0f;
float phi = Mathf.Acos(value);
float lambda2 = (I_c + 2 * k_root * Mathf.Cos(phi / 3)) / 3.0f;
float lambda = Mathf.Sqrt(lambda2);
float III_u = Mathf.Sqrt(III_c);
if (det < 0) III_u = -III_u;
float I_u = lambda + Mathf.Sqrt(-lambda2 + I_c + 2 * III_u / lambda);
float II_u = (I_u * I_u - I_c) * 0.5f;
float inv_rate, factor;
inv_rate = 1 / (I_u * II_u - III_u);
factor = I_u * III_u * inv_rate;
Matrix4x4 U = Matrix4x4.zero;
U[0, 0] = factor;
U[1, 1] = factor;
U[2, 2] = factor;
factor = (I_u * I_u - II_u) * inv_rate;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
U[i, j] += factor * C[i, j] - inv_rate * C2[i, j];
inv_rate = 1 / III_u;
factor = II_u * inv_rate;
inv_U[0, 0] = factor;
inv_U[1, 1] = factor;
inv_U[2, 2] = factor;
factor = -I_u * inv_rate;
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
inv_U[i, j] += factor * U[i, j] + inv_rate * C[i, j];
}
Matrix4x4 R = Matrix4x4.zero;
for (int ii = 0; ii < 3; ii++)
for (int jj = 0; jj < 3; jj++)
for (int kk = 0; kk < 3; kk++)
R[ii, jj] += F[ii, kk] * inv_U[kk, jj];
R[3, 3] = 1;
return R;
}
// Update the mesh vertices according to translation c and rotation R.
// It also updates the velocity.
void Update_Mesh(Vector3 c, Matrix4x4 R, float inv_dt)
{
for (int i = 0; i < Q.Length; i++)
{
Vector3 x = (Vector3)(R * Q[i]) + c;
V[i] += (x - X[i]) * inv_dt;
X[i] = x;
}
Mesh mesh = GetComponent<MeshFilter>().mesh;
mesh.vertices = X;
}
void Collision(float inv_dt)
{
for (int i = 0; i < Q.Length; i++)
{
bool isCol = false;
isCol = Collision_Impulse(ground, groundNormal, i);
if (isCol == false)
{
Collision_Impulse(wall, wallNormal, i);
}
}
}
bool Collision_Impulse(Vector3 P, Vector3 N, int i)
{
if (Vector3.Dot(X[i] - P, N) < 0)
{
if (Vector3.Dot(V[i], N) < 0)
{
Vector3 v_ni = Vector3.Dot(V[i], N) * N;
Vector3 v_ti = V[i] - v_ni;
float a = Mathf.Max(1 - mu_T * (1 + mu_N) * v_ni.magnitude / v_ti.magnitude, 0);
v_ni = -mu_N * v_ni;
v_ti = a * v_ti;
V[i] = v_ni + v_ti;
return true;
}
}
return false;
}
// Update is called once per frame
void Update()
{
if (Input.GetKey("l"))
{
launched = true;
for (int i = 0; i < V.Length; i++)
{
V[i] = new Vector3(5.0f, 2.0f, 0.0f);
}
}
if (Input.GetKey("r"))
{
launched = false;
for (int i = 0; i < V.Length; i++)
{
V[i] = new Vector3(4.0f, 0.0f, 0.0f);
}
Update_Mesh(new Vector3(0, 0.6f, 0), Matrix4x4.Rotate(transform.rotation), 0);
}
float dt = 0.015f;
if (launched == false)
{
return;
}
//Step 1: run a simple particle system.
for (int i = 0; i < V.Length; i++)
{
V[i] = V[i] + G * dt;
V[i] *= linear_decay;
}
//Step 2: Perform simple particle collision.
Collision(1 / dt);
for (int i = 0; i < V.Length; i++)
{
Y[i] = X[i] + V[i] * dt;
}
// Step 3: Use shape matching to get new translation c and
// new rotation R. Update the mesh by c and R.
//Shape Matching (translation)
Vector3 c = ShapeMatching_translation();
//Shape Matching (rotation)
Matrix4x4 R = ShapeMatching_rotation(c);
Update_Mesh(c, R, 1 / dt);
}
Vector3 ShapeMatching_translation()
{
Vector3 res = Vector3.zero;
for (int i = 0; i < Y.Length; i++)
{
res = res + Y[i];
}
res = res / Y.Length;
return res;
}
Matrix4x4 ShapeMatching_rotation(Vector3 c)
{
// calc A
Matrix4x4 A = Matrix4x4.zero;
A[3, 3] = 1.0f;
for (int i = 0; i < V.Length; i++)
{
Matrix4x4 o = vector3x1dotvector1x3(Y[i] - c, Q[i]);
A[0, 0] += o[0, 0];
A[0, 1] += o[0, 1];
A[0, 2] += o[0, 2];
A[1, 0] += o[1, 0];
A[1, 1] += o[1, 1];
A[1, 2] += o[1, 2];
A[2, 0] += o[2, 0];
A[2, 1] += o[2, 1];
A[2, 2] += o[2, 2];
}
A = A * QQt.inverse;
//Shape Matching (rotation)
// calc R
Matrix4x4 R = Matrix4x4.zero;
R = Get_Rotation(A);
return R;
}
Matrix4x4 vector3x1dotvector1x3(Vector3 A, Vector3 B)
{
Matrix4x4 rlt = Matrix4x4.zero;
rlt[3, 3] = 1.0f;
rlt[0, 0] = A[0] * B[0];
rlt[0, 1] = A[0] * B[1];
rlt[0, 2] = A[0] * B[2];
rlt[1, 0] = A[1] * B[0];
rlt[1, 1] = A[1] * B[1];
rlt[1, 2] = A[1] * B[2];
rlt[2, 0] = A[2] * B[0];
rlt[2, 1] = A[2] * B[1];
rlt[2, 2] = A[2] * B[2];
return rlt;
}
}