Rigid Body Simulation
0 前言
声明:此篇博客仅用于个人学习记录之用,并非是分享代码。Hornor the Code
刚体动力学仿真的实现,方法来自Games103。
同时因为之前学习Jonh Crag
的《机器人学》,感觉自己的动力学部分差的很多,所以也有很多动力学的知识来自于Cambridge David Tong: Lectures on Dynamics and Relativity, 这个讲义的相对论那边没怎么看。
参考了《Physically Based Modeling by David Baraff Pixar Animation Studios》 只用到了前面一章的内容 Unconstrained Rigid Body Dynamics。这也是给出的课后阅读材料,大致70页左右。
现在David Baraff 是斯坦福 cs448b 的 Course Speakers https://graphics.stanford.edu/courses/cs448b-00-winter/papers/phys_model.pdf 又有一本全面的大概300页的物理仿真的Lecture。
有关姿态更新的地方,王华民老师的课件里给出的是不包含torque-free-motion
的角速度更新方程。torque-free-motion
的核心就是,因为角动量守恒,在不受力矩的情况下,具有初始的角速度,因此刚体的姿态会发生变化,导致角速度也得变化。
碰撞响应的部分,并没有同PBM
给出的回溯时间的方法,当有顶点侵入到物体内部,时间回溯到碰撞之时,重启ode-solver
。因为刚体碰撞,也就是说,两个物体之间是有相对速度的,碰撞的时候,速度会发生较大的改变,可以说在那瞬间,速度并不是连续的。
本课程给出的碰撞响应,是通过计算冲量的方式,用一个简化方式。\(J = F \Delta{t} = Ma \Delta{t} = M \Delta{v}\)。
当让能够称之为等号的是 \(dv = adt\), 上式其实只能算近似 \(\Delta{v} \approx a \Delta{t}\)。
这就导致了位置一直是连续的,所以哪怕是侵入了墙体之后,兔子也不可能瞬间出来,反而会在里面待一会儿。
1 核心技术
1.1 Semi-implicit Euler
\]
这个如何理解呢,关键就是对于每一帧的渲染。我们只有三个量
- \(\mathbf{v}^{[0]}\)
- \(\mathbf{x}^{[0]}\)
- \(\Delta{t}\)
也就是说,前一帧的位置和速度信息,以及一个时间差。
需要我们计算当前的位置与速度,并且渲染出来。
半隐式欧拉这玩意有1阶精度。但更稳定一点。
怎么说呢,就以课程代码给出的时间间隔t = 0.0015f
, 那么就算是2阶精度 t^2 = 0.00000225
。就以纯累加算,1秒60帧,一分钟3600帧,已经导致将近 0.0081
的误差(似乎也不能么算)。
事实上,我试过只有torque-free-motion
的仿真,5分钟直接爆炸,numerical instability
。
1.2 刚体模拟
图片来源: Games103 lecture3 Rigid Body Dynamics
Inertia_tensor 的从 body 到 world 的变换可以看 PBM。这个tensor会随着姿态的变化而变化, body frame 的坐标系原点是质心。
- 2.10 The Inertia Tensor Page.13
角速度的更新不考虑torque-free-motion
,这个可以看 PBM。
- C.2 Angular Accelerion Page.60 具体推导。
四元数的更新可以看 PBM。PBM中给出的四元数的乘法,说是为了与矩阵乘法类似,是左乘的过程。而Unity给出的是右乘。这是需要注意的点。
- 4 Quaternions vs. Rotation Matrices Page.20
- Appendix B Quaternion Derivations Page.58
ODE相关的知识可以去看 MIT的 Matrix Calculus 2023年10月23日 鸡汤来喽。里面关于导数的一些认识,特别有启发。
1.3 Collision
图片来源: Games103 lecture4 Rigid Body Contacts
should be minimized while meeting Coulomb’s law, 是决定切向速度的改变量。最小是0,也就是说,一碰,切向的速度就成了0,因为比较大的摩擦力的缘故。
另外冲量的计算是遍历所有的顶点,找出其中处于碰撞的点,然后取平均值,基于平均的速度和位置,计算冲量。
2 实现
using UnityEngine;
using System.Collections;
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 Mu_N
// Mine Var
Vector3 g = new Vector3(0, -9.8f, 0);
float Mu_T = 0.8f;
// 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)
{
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
Vector3 x = transform.position;
Matrix4x4 R = Matrix4x4.Rotate(transform.rotation);
Matrix4x4 Inertia_tensor = R * I_ref * R.transpose;
Vector3 avg_pos = new Vector3(0, 0, 0);
Vector3 avg_vol = new Vector3(0, 0, 0);
bool isCllison = false;
int count = 0;
for (int i = 0; i < vertices.Length; i++)
{
Vector3 Rv_i = R.MultiplyVector(vertices[i]);
Vector3 world_vertice = x + Rv_i;
float SDF = Vector3.Dot(world_vertice - P, N);
if (SDF < 0.0f)
{
Vector3 world_volocity = v + Vector3.Cross(w, Rv_i);
float vSDF = Vector3.Dot(world_volocity, N);
if (vSDF < 0.0f)
{
count++;
avg_pos += vertices[i];
avg_vol += world_volocity;
isCllison = true;
}
}
}
if (isCllison)
{
avg_pos /= count;
avg_vol /= count;
Vector3 Rv_i = R.MultiplyVector(avg_pos);
// Compute the wanted v_new^i
Vector3 v_N = Vector3.Dot(avg_vol, N) * N;
Vector3 v_T = avg_vol - v_N;
float a = Mathf.Max(1 - (Mu_T * (1 + restitution) * v_N.magnitude / v_T.magnitude), 0.0f);
v_N = -restitution * v_N;
v_T = a * v_T;
Vector3 v_new = v_N + v_T;
float massinv = (1.0f / mass);
Matrix4x4 massinv_Indentity = new Matrix4x4(massinv * Matrix4x4.identity.GetColumn(0),
massinv * Matrix4x4.identity.GetColumn(1),
massinv * Matrix4x4.identity.GetColumn(2),
Matrix4x4.identity.GetColumn(3));
Matrix4x4 K = M_minus_M(massinv_Indentity,
Get_Cross_Matrix(Rv_i) * Inertia_tensor.inverse * Get_Cross_Matrix(Rv_i));
Vector3 j = K.inverse.MultiplyVector(v_new - avg_vol);
Debug.Log(K);
Debug.Log(j);
// update volocity
v = v + massinv * j;
w = w + Inertia_tensor.inverse.MultiplyVector(Vector3.Cross(Rv_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;
}
// Part I: Update velocities
float m = 1;
Vector3 f_i = new Vector3(0, 0, 0);
Vector3 toruqe_i = new Vector3(0, 0, 0);
Vector3 F = new Vector3(0, 0, 0);
Vector3 Torque = new Vector3(0, 0, 0);
Matrix4x4 R = Matrix4x4.Rotate(transform.rotation);
Matrix4x4 Inertia_tensor = R * I_ref * R.transpose;
Mesh mesh = GetComponent<MeshFilter>().mesh;
Vector3[] vertices = mesh.vertices;
for (int i = 0; i < vertices.Length; i++)
{
// Linear
f_i = m * g;
F += f_i;
// Rotation
toruqe_i = Vector3.Cross(R * vertices[i], f_i);
Torque += toruqe_i;
}
// or v = v + F / mass * dt - (1.0f - linear_decay) * v
v = v + F / mass * dt;
Vector3 L = Inertia_tensor.MultiplyVector(w);
// + Inertia_tensor.inverse.MultiplyVector(Vector3.Cross(L, w))*dt torque free motion
w = w + Inertia_tensor.inverse.MultiplyVector(Torque) *dt;
v = linear_decay * v;
w = angular_decay * w;
// Part II: Collision Impulse
// floor
Collision_Impulse(new Vector3(0, 0.01f, 0), new Vector3(0, 1, 0));
// wall
Collision_Impulse(new Vector3(2, 0, 0), new Vector3(-1, 0, 0));
// Part III: Update position & orientation
//Update linear status
Vector3 x = transform.position;
x = x + v * dt;
//Update angular status
Quaternion q = transform.rotation;
Vector3 w_update = 0.5f * dt * w;
Quaternion q_dt = new Quaternion(w_update[0], w_update[1], w_update[2], 0);
// Rotating by the product lhs * rhs is the same as applying the two rotations in sequence: lhs first and then rhs
q = Q_plus(q, q * q_dt);
// Part IV: Assign to the object
transform.position = x;
transform.rotation = q;
}
private Quaternion Q_plus(Quaternion lhs, Quaternion rhs)
{
Quaternion q = new Quaternion(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w);
return q.normalized;
}
private Matrix4x4 M_minus_M(Matrix4x4 lhs, Matrix4x4 rhs)
{
Matrix4x4 A = Matrix4x4.zero;
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
A[i, j] = lhs[i, j] - rhs[i, j];
}
}
// Metion here, if A[3,3] == 0 then the matrix is sigular, which doesn't have a inverse,
// however, in the singular case, the inverse is Matrix.zero;
A[3, 3] = 1;
return A;
}
}
X Ref
- 2013 David Tong: Lectures on Dynamics and Relativity _ Cambridge
- 2021 王华民 GAMES103:基于物理的计算机动画入门
- 2023 MIT 18.S096 Matrix Calculus
- 2019 CSE 291D Physics Simulation Spring 2019
- 2019 SCE 291 Rigid Body Simulation
- 2014 CIS563, Spring 2014 (Physics-based Animation)
Rigid Body Simulation的更多相关文章
- VTK三维点集轮廓凸包提取
碰撞检测问题在虚拟现实.计算机辅助设计与制造.游戏及机器人等领域有着广泛的应用,甚至成为关键技术.而包围盒算法是进行碰撞干涉初步检测的重要方法之一.包围盒算法是一种求解离散点集最优包围空间的方法.基本 ...
- 基于CUDA的粒子系统的实现
基于CUDA的粒子系统的实现 用途: 这篇文章作为代码实现的先导手册,以全局的方式概览一下粒子系统的实现大纲. 科普: 对粒子进行模拟有两种基本方法: Eulerian(grid-based) met ...
- [wikipedia] List of free and open-source software packages
List of free and open-source software packages From Wikipedia, the free encyclopedia This articl ...
- Bullet Physics OpenGL 刚体应用程序模板 Rigid Simulation in Bullet
利用Bullet物理引擎实现刚体的自由落体模拟的模板 Bullet下载地址 Main.cpp #include <GLUT/glut.h> #include <cstdlib> ...
- 2016 Multi-University Training Contest 1 G. Rigid Frameworks
Rigid Frameworks Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) ...
- Rigid motion segmentation
In computer vision, rigid motion segmentation is the process of separating regions, features, or tra ...
- Gate level Simulation(门级仿真)
1 什么是后仿真? 后仿真也成为时序仿真,门级仿真,在芯片布局布线后将时序文件SDF反标到网标文件上,针对带有时序信息的网标仿真称为后仿真. 2 后仿真是用来干嘛的? 检查电路中的timing vio ...
- SSAS Cube 维度成员关系Rigid 和 Flexible
维度成员关系指示成员关系是否随时间而更改. 值为 Rigid 和 Flexible,前者表示成员之间的关系不随时间而更改,后者表示成员之间的关系随时间而更改. 默认值为 Flexible. 指定适 ...
- fdtd simulation, plotting with gnuplot, writting in perl
# 9月13日 于成都黄龙溪 1 #!/usr/bin/perl # Author : Leon Email: yangli0534@gmail.com # fdtd simulation , plo ...
- 【转载】PMC/PEC Boundary Conditions and Plane Wave Simulation
原文链接 PMC/PEC Boundary Conditions and Plane Wave Simulation (FDTD) OptiFDTD now has options to use Pe ...
随机推荐
- git 如何处理合并时存在的子模块冲突
如果另一个分支的子模块不同于当前分支,那么在拉取下来时,并不会更新本地子模块的版本,而会出现一个.diff文件,表示差异性.那么在合并代码时,可能会因为这个.dff文件冲突无法解决.产生这个问题的原因 ...
- 新晋 Committer!来自复旦大学的帅哥一枚
点亮Star️ · 支持我们 https://github.com/apache/dolphinscheduler 最近,社区星力量又迎来一位新晋 Committer,这次是来自复旦大学研究生在读的王 ...
- RabbitMq高级特性之消费端限流 通俗易懂 超详细 【内含案例】
RabbitMq高级特性之消费端限流 介绍 消息队列中囤积了大量的消息, 或者某些时刻生产的消息远远大于消费者处理能力的时候, 这个时候如果消费者一次取出大量的消息, 但是客户端又无法处理, 就会出现 ...
- ArcGIS创建渔网工具的使用方法
本文介绍在ArcMap软件中,通过"Create Fishnet"工具创建渔网,从而获得指定大小的矢量格网数据的方法. 首先,我们在创建渔网前,需要指定渔网覆盖的范围.这里 ...
- HttpWebResponse 四种accept-encoding解析(gzip, deflate, br,identity【转】
var hwrs = (HttpWebResponse)hwr.GetResponse() if (hwrs.ContentEncoding.ToLower().Contains("gzip ...
- ES7学习笔记(二)ES的集群原理
发现 发现是节点之间彼此发现,形成集群的一个过程.这个过程发生的场景有很多,比如:你启动了一个集群节点,或者一个节点确认主节点已经挂掉了,或者一个新的主节点被选举了. 咱们在配置集群的时候在配置文件中 ...
- rpm -Uvh *.rpm --nodeps --force
rpm -Uvh *.rpm --nodeps --force 含义:-U:升级软件,若未软件尚未安装,则安装软件.-v:表示显示详细信息.-h:以"#"号显示安装进度.--for ...
- c程序设计语言 by K&R(二)指针与数组
指针与数组 1. c语言只有值传递,没有引用传递 可通过指针交换 #include <stdio.h> #include <stdlib.h> void swap(int* a ...
- webpack高版本抽离css样式报错[已解决]
全局安装的webpack版本是5.51.1,webpack-cli是4.9的版本; 本来想用 extract-text-webpack-plugin 的,但是报错了,查了一下文档 发现,已经不支持新版 ...
- dotnet 学习 CPF 框架笔记 了解 X11 里如何获取触摸信息
本文记录我学习 CPF 框架的笔记,本文记录我阅读 CPF 框架,学习到了如何在 dotnet C# 里面获取到 X11 的触摸信息,获取到多指触摸以及触摸点的面积和触摸点压感等信息的方法 开始之前, ...