任务
1.更新位置、姿态与速度
2.碰撞检测
3.碰撞反馈
实现
更新位置、姿态与速度
对于速度的更新,采用显式的方法,对于位置的更新,采用隐式的方法。就是103中讲的两只青蛙的例子。
需要同时更新线速度和角速度。线速度受到重力的影响,因此每次更新都要加上重力造成的速度影响,同时还需要乘上速度的衰减量。角速度的更新,也是乘上衰减量。
位置和姿态的更新。对于位置的更新较简单。
姿态的更新,涉及到四元数的运算。更新四元数时,和矩阵相乘叠加旋转效果一样,左乘一个四元数。而四元数的求解是通过下面的式子求得。因为计算三角函数性能开销会变大,这里的角度非常小,因此可以通过近似的方法求得近似的q = [1 , Δt/2*w]
叠加两次旋转,为 [1 , Δt/2*w] *q ,将[1 , Δt/2*w]分解成 1 + [0 , Δt/2*w],再将q乘进来就得到了103给的式子。这里代码就直接按103的式子实现了
v += dt * gravity;
v *= linear_decay;
w *= angular_decay;
//Update linear status
Vector3 x = transform.position + dt * v;
//Update angular status
Vector3 dw = w * dt / 2.0f;
Quaternion qw = new Quaternion(dw.x,dw.y,dw.z,0.0f);
Quaternion q = transform.rotation;
Quaternion qw_q = qw *q;
q = new Quaternion(q.x + qw_q.x , q.y + qw_q.y , q.z + qw_q.z , q.w + qw_q.w);
// Part IV: Assign to the object
transform.position = x;
transform.rotation = q;
碰撞检测
先判断点是否已经进入了物体内部,如果在物体内部,再考虑是否还有往内部运动的趋势,说明需要进行碰撞反弹处理。这里较简单。
当有多个点满足需要碰撞反弹处理时,需要对这些点求一个平均位置,再用平均的位置来进行之后的计算。
MeshFilter meshFilter = GetComponent<MeshFilter>();
Mesh mesh = meshFilter.mesh;
Vector3[] vertices = mesh.vertices;
Vector3 sum = new Vector3(0,0,0);
int cnt = 0;
Matrix4x4 R = Matrix4x4.Rotate(transform.rotation);
Vector3 T = transform.position;
for(int i = 0;i<vertices.Length;i++){
Vector3 ri = vertices[i];
Vector3 Rri = R.MultiplyVector(ri);
Vector3 xi = T + Rri;
Vector3 dir = xi - P;
float d = Vector3.Dot(dir,N);
if(d < 0.0f){
Vector3 vi = v + Vector3.Cross(w,Rri);
float viN = Vector3.Dot(vi,N);
if( viN < 0.0f){
sum += ri;
cnt++;
}
}
}
碰撞反馈
因为不能只修改某个点的速度。因此需要通过冲量,连接点的速度和整体的速度。
碰撞后速度的修改:根据弹性系数和摩擦系数来确定反弹后的速度。注意这里的速度是已经加上了角速度了。对
对于垂直与平面方向的速度,直接取反并乘以弹性系数就好了。
对于平行于平面方向的速度,根据库伦摩擦定律 F=μN,水平方向速度的改变量是由摩擦力F造成的。水平方向的摩擦系数为μT,因此有
ΔvT = dt * F / m
将摩擦定律代入
ΔvT = dt * μT * N / m
N是竖直方向施加的力的大小,而这里 dt * N / m 刚好对应了竖直方向的速度变化量,因此就能得到第一条式子
ΔvT = μT * ΔvN
第二条是根据Δv的定义得到的。avT是下一个状态的水平速度,速度差自然是(1-a)|vT|。右边因为竖直方向速度都取反了,因此变化量就是(1+μN)|vN|。最后得到a的表示。
得到速度变化后,接着就是计算冲量大小了。
先求出物体于某个姿态时的惯性张量,而下面的推导告诉了我们,可以预计算初始姿态的惯性张量,想要求某个姿态的惯性张量时,再使用R乘入即可。
假设一个冲量j,作用于该点,那么该点的线速度和角速度会发生如下变化。
向量叉乘时,可以将向量写成矩阵形式
因此将v都移到左边,右边的将j提取出来,就能得到一个K和j。此时有三个量,分别是Δv,j,K,而我们已知Δv和K,因此能求出冲量j。
得到冲量j后,将其作用于物体的整理,便能更新物体新的速度。
具体反馈的流程如下
Vector3 r_i = sum / cnt;
Vector3 Rr_i = R.MultiplyVector(r_i);
Vector3 x_i = T + Rr_i;
Vector3 v_i = v + Vector3.Cross(w,Rr_i);
Vector3 v_N_i = Vector3.Dot(v_i,N) * N;
Vector3 v_T_i = v_i - v_N_i;
float a = Math.Max(1.0f - u_t * ( 1.0f + u_n) * v_N_i.magnitude / v_T_i.magnitude , 0.0f);
Vector3 v_N_i_new = -u_n * v_N_i;
Vector3 v_T_i_new = a * v_T_i;
Vector3 v_i_new = v_N_i_new + v_T_i_new;
Matrix4x4 Rr_i_matrix = Get_Cross_Matrix(Rr_i);
Matrix4x4 I = R * I_ref * Matrix4x4.Transpose(R);
Matrix4x4 I_inv = Matrix4x4.Inverse(I);
Matrix4x4 mass_inv = Matrix4x4.identity;
for(int ii=0;ii<4;ii++){
for(int jj = 0;jj<4;jj++){
mass_inv[ii,jj] *= 1.0f / mass;
}
}
Matrix4x4 RIR = Rr_i_matrix * I_inv * Rr_i_matrix;
Matrix4x4 K = Matrix4x4.identity;
for(int ii=0;ii<4;ii++){
for(int jj=0;jj<4;jj++){
K[ii,jj] = mass_inv[ii,jj] - RIR[ii,jj];
}
}
Vector3 j = Matrix4x4.Inverse(K) * ( v_i_new - v_i );
v += 1.0f/mass * j;
w += I_inv.MultiplyVector(Vector3.Cross(Rr_i,j));
全部代码
using UnityEngine;
using System.Collections;
using System;
public class Rigid_Bunny : MonoBehaviour
{
bool launched = false;
float dt = 0.015f;
Vector3 v = new Vector3(0, 0, 0); // velocity
Vector3 w = new Vector3(0, 0, 0); // angular velocity
float mass; // mass
Matrix4x4 I_ref; // reference inertia
float linear_decay = 0.999f; // for velocity decay
float angular_decay = 0.98f;
float restitution = 0.5f; // for collision
Vector3 gravity = new Vector3(0.0f, -9.8f, 0.0f);
float u_t = 0.2f; //摩擦系数
float u_n = 0.5f; //弹性系数
// Use this for initialization
void Start ()
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
float m=1;
mass=0;
for (int i=0; i<vertices.Length; i++)
{
mass += m;
float diag=m*vertices[i].sqrMagnitude;
I_ref[0, 0]+=diag;
I_ref[1, 1]+=diag;
I_ref[2, 2]+=diag;
I_ref[0, 0]-=m*vertices[i][0]*vertices[i][0];
I_ref[0, 1]-=m*vertices[i][0]*vertices[i][1];
I_ref[0, 2]-=m*vertices[i][0]*vertices[i][2];
I_ref[1, 0]-=m*vertices[i][1]*vertices[i][0];
I_ref[1, 1]-=m*vertices[i][1]*vertices[i][1];
I_ref[1, 2]-=m*vertices[i][1]*vertices[i][2];
I_ref[2, 0]-=m*vertices[i][2]*vertices[i][0];
I_ref[2, 1]-=m*vertices[i][2]*vertices[i][1];
I_ref[2, 2]-=m*vertices[i][2]*vertices[i][2];
}
I_ref [3, 3] = 1;
}
Matrix4x4 Get_Cross_Matrix(Vector3 a)
{
//Get the cross product matrix of vector a
Matrix4x4 A = Matrix4x4.zero;
A [0, 0] = 0;
A [0, 1] = -a [2];
A [0, 2] = a [1];
A [1, 0] = a [2];
A [1, 1] = 0;
A [1, 2] = -a [0];
A [2, 0] = -a [1];
A [2, 1] = a [0];
A [2, 2] = 0;
A [3, 3] = 1;
return A;
}
// In this function, update v and w by the impulse due to the collision with
//a plane <P, N>
void Collision_Impulse(Vector3 P, Vector3 N)
{
MeshFilter meshFilter = GetComponent<MeshFilter>();
Mesh mesh = meshFilter.mesh;
Vector3[] vertices = mesh.vertices;
Vector3 sum = new Vector3(0,0,0);
int cnt = 0;
Matrix4x4 R = Matrix4x4.Rotate(transform.rotation);
Vector3 T = transform.position;
for(int i = 0;i<vertices.Length;i++){
Vector3 ri = vertices[i];
Vector3 Rri = R.MultiplyVector(ri);
Vector3 xi = T + Rri;
Vector3 dir = xi - P;
float d = Vector3.Dot(dir,N);
if(d < 0.0f){
Vector3 vi = v + Vector3.Cross(w,Rri);
float viN = Vector3.Dot(vi,N);
if( viN < 0.0f){
sum += ri;
cnt++;
}
}
}
if(cnt ==0)return;
Vector3 r_i = sum / cnt;
Vector3 Rr_i = R.MultiplyVector(r_i);
Vector3 x_i = T + Rr_i;
Vector3 v_i = v + Vector3.Cross(w,Rr_i);
Vector3 v_N_i = Vector3.Dot(v_i,N) * N;
Vector3 v_T_i = v_i - v_N_i;
float a = Math.Max(1.0f - u_t * ( 1.0f + u_n) * v_N_i.magnitude / v_T_i.magnitude , 0.0f);
Vector3 v_N_i_new = -u_n * v_N_i;
Vector3 v_T_i_new = a * v_T_i;
Vector3 v_i_new = v_N_i_new + v_T_i_new;
Matrix4x4 Rr_i_matrix = Get_Cross_Matrix(Rr_i);
Matrix4x4 I = R * I_ref * Matrix4x4.Transpose(R);
Matrix4x4 I_inv = Matrix4x4.Inverse(I);
Matrix4x4 mass_inv = Matrix4x4.identity;
for(int ii=0;ii<4;ii++){
for(int jj = 0;jj<4;jj++){
mass_inv[ii,jj] *= 1.0f / mass;
}
}
Matrix4x4 RIR = Rr_i_matrix * I_inv * Rr_i_matrix;
Matrix4x4 K = Matrix4x4.identity;
for(int ii=0;ii<4;ii++){
for(int jj=0;jj<4;jj++){
K[ii,jj] = mass_inv[ii,jj] - RIR[ii,jj];
}
}
Vector3 j = Matrix4x4.Inverse(K) * ( v_i_new - v_i );
v += 1.0f/mass * j;
w += I_inv.MultiplyVector(Vector3.Cross(Rr_i,j));
}
// Update is called once per frame
void Update ()
{
//Game Control
if(Input.GetKey("r"))
{
transform.position = new Vector3 (0, 0.6f, 0);
restitution = 0.5f;
launched=false;
}
if(Input.GetKey("l"))
{
v = new Vector3 (5, 2, 0);
launched=true;
}
if(launched){
// Part I: Update velocities
v += dt * gravity;
v *= linear_decay;
w *= angular_decay;
// Part II: Collision Impulse
Collision_Impulse(new Vector3(0, 0.01f, 0), new Vector3(0, 1, 0));
Collision_Impulse(new Vector3(2, 0, 0), new Vector3(-1, 0, 0));
// Part III: Update position & orientation
//Update linear status
Vector3 x = transform.position + dt * v;
//Update angular status
Vector3 dw = w * dt / 2.0f;
Quaternion qw = new Quaternion(dw.x,dw.y,dw.z,0.0f);
Quaternion q = transform.rotation;
Quaternion qw_q = qw *q;
q = new Quaternion(q.x + qw_q.x , q.y + qw_q.y , q.z + qw_q.z , q.w + qw_q.w);
// Part IV: Assign to the object
transform.position = x;
transform.rotation = q;
}
}
}
结果