mark一下写的,果然,老师说得简单和代码量少对于我等人来说,不是一个维度的。
自己经验少,以为基本单元都是三角形的,这回第一次来了个四面体的,有点蒙圈,但处理过程中也还是把四面体拆成顶点和三角形来处理。确切的说,以往是以为mesh只是个三维空间中闭合的二维面,里面啥都没,就是个空心皮套一样。这回的mesh感觉里面也有三角形。虽说没看过(因为自己不会看模型里面…也有点懒不想找方法比如上色啥的)
这里有个稍微迷惑人的地方,就是初始的名为house的平面,其实它啥用都没…不要以为是神奇的代码把平面折叠成房子,你把FVM脚本附着在啥物体上其实都行。
在课程PPT里,一直是按着二维处理的,这里我们要变成三维,本质上大差不差。
下面记录代码:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
public class FVM : MonoBehaviour
{
float dt = 0.003f;
float mass = 1;
float stiffness_0 = 20000.0f; //lamda
float stiffness_1 = 5000.0f; //miu
float damp = 0.999f;
int[] Tet;
int tet_number; //The number of tetrahedra
Vector3[] Force;
Vector3[] V;
Vector3[] X;
int number; //The number of vertices
Matrix4x4[] inv_Dm;
//For Laplacian smoothing.
Vector3[] V_sum;
int[] V_num;
SVD svd = new SVD();
// Start is called before the first frame update
void Start()
{
// FILO IO: Read the house model from files.
// The model is from Jonathan Schewchuk's Stellar lib.
{
string fileContent = File.ReadAllText("Assets/house2.ele");
string[] Strings = fileContent.Split(new char[]{' ', '\t', '\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
tet_number=int.Parse(Strings[0]);
Tet = new int[tet_number*4];
for(int tet=0; tet<tet_number; tet++)
{
Tet[tet*4+0]=int.Parse(Strings[tet*5+4])-1;
Tet[tet*4+1]=int.Parse(Strings[tet*5+5])-1;
Tet[tet*4+2]=int.Parse(Strings[tet*5+6])-1;
Tet[tet*4+3]=int.Parse(Strings[tet*5+7])-1;
}
}
{
string fileContent = File.ReadAllText("Assets/house2.node");
string[] Strings = fileContent.Split(new char[]{' ', '\t', '\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
number = int.Parse(Strings[0]);
X = new Vector3[number];
for(int i=0; i<number; i++)
{
X[i].x=float.Parse(Strings[i*5+5])*0.4f;
X[i].y=float.Parse(Strings[i*5+6])*0.4f;
X[i].z=float.Parse(Strings[i*5+7])*0.4f;
}
//Centralize the model.
Vector3 center=Vector3.zero;
for(int i=0; i<number; i++) center+=X[i];
center=center/number;
for(int i=0; i<number; i++)
{
X[i]-=center;
float temp=X[i].y;
X[i].y=X[i].z;
X[i].z=temp;
}
}
/*
tet_number=1;
Tet = new int[tet_number*4];
Tet[0]=0;
Tet[1]=1;
Tet[2]=2;
Tet[3]=3;
number=4;
X = new Vector3[number];
V = new Vector3[number];
Force = new Vector3[number];
X[0]= new Vector3(0, 0, 0);
X[1]= new Vector3(1, 0, 0);
X[2]= new Vector3(0, 1, 0);
X[3]= new Vector3(0, 0, 1);
*/
//Create triangle mesh.
Vector3[] vertices = new Vector3[tet_number*12];
int vertex_number=0;
for(int tet=0; tet<tet_number; tet++)
{
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
}
int[] triangles = new int[tet_number*12];
for(int t=0; t<tet_number*4; t++)
{
triangles[t*3+0]=t*3+0;
triangles[t*3+1]=t*3+1;
triangles[t*3+2]=t*3+2;
}
Mesh mesh = GetComponent<MeshFilter> ().mesh;
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals ();
V = new Vector3[number];
Force = new Vector3[number];
V_sum = new Vector3[number];
V_num = new int[number];
//TODO: Need to allocate and assign inv_Dm
inv_Dm = new Matrix4x4[tet_number]; //初始的位置矩阵
for (int tet = 0; tet < tet_number; tet++)
inv_Dm[tet] = Build_Edge_Matrix(tet).inverse;
}
Matrix4x4 Build_Edge_Matrix(int tet)
{
Matrix4x4 ret=Matrix4x4.zero;
//TODO: Need to build edge matrix here.
ret[0, 0] = X[Tet[tet * 4 + 1]].x - X[Tet[tet * 4 + 0]].x;
ret[1, 0] = X[Tet[tet * 4 + 1]].y - X[Tet[tet * 4 + 0]].y;
ret[2, 0] = X[Tet[tet * 4 + 1]].z - X[Tet[tet * 4 + 0]].z;
ret[3, 0] = 0;
ret[0, 1] = X[Tet[tet * 4 + 2]].x - X[Tet[tet * 4 + 0]].x;
ret[1, 1] = X[Tet[tet * 4 + 2]].y - X[Tet[tet * 4 + 0]].y;
ret[2, 1] = X[Tet[tet * 4 + 2]].z - X[Tet[tet * 4 + 0]].z;
ret[3, 1] = 0;
ret[0, 2] = X[Tet[tet * 4 + 3]].x - X[Tet[tet * 4 + 0]].x;
ret[1, 2] = X[Tet[tet * 4 + 3]].y - X[Tet[tet * 4 + 0]].y;
ret[2, 2] = X[Tet[tet * 4 + 3]].z - X[Tet[tet * 4 + 0]].z;
ret[3, 2] = 0;
ret[0, 3] = 0;
ret[1, 3] = 0;
ret[2, 3] = 0;
ret[3, 3] = 1;
return ret;
}
void Smooth_V()
{
for (int i = 0; i < number; i++)
{
V_sum[i] = new Vector3(0, 0, 0);
V_num[i] = 0;
}
for (int tet = 0; tet < tet_number; tet++)
{
Vector3 sum = V[Tet[tet * 4 + 0]] + V[Tet[tet * 4 + 1]] + V[Tet[tet * 4 + 2]] + V[Tet[tet * 4 + 3]];
V_sum[Tet[tet * 4 + 0]] += sum;
V_sum[Tet[tet * 4 + 1]] += sum;
V_sum[Tet[tet * 4 + 2]] += sum;
V_sum[Tet[tet * 4 + 3]] += sum;
V_num[Tet[tet * 4 + 0]] += 4;
V_num[Tet[tet * 4 + 1]] += 4;
V_num[Tet[tet * 4 + 2]] += 4;
V_num[Tet[tet * 4 + 3]] += 4;
}
for (int i = 0; i < number; i++)
{
V[i] = 0.9f * V[i] + 0.1f * V_sum[i] / V_num[i];
}
}
void _Update()
{
// Jump up.
if(Input.GetKeyDown(KeyCode.Space))
{
for(int i=0; i<number; i++)
V[i].y+=0.2f;
}
for(int i=0 ;i<number; i++)
{
//TODO: Add gravity to Force.
Force[i] = new Vector3(0,- 9.8f * mass, 0);
}
for(int tet=0; tet<tet_number; tet++)
{
//TODO: Deformation Gradient
Matrix4x4 F = Build_Edge_Matrix(tet) * inv_Dm[tet];
//TODO: Green Strain
Matrix4x4 G = F.transpose * F;
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
{
if (i == j)
G[i, j] = G[i, j] - 1;
G[i, j] = 0.5f * G[i, j];
}
//TODO: Second PK Stress
Matrix4x4 S = Matrix4x4.zero;
float traceG = G[0, 0] + G[1, 1] + G[2, 2];
for(int i=0;i<4;i++)
for(int j=0;j<4;j++)
{
S[i, j] = 2 * stiffness_1 * G[i, j];
if (i == j)
S[i, j] += stiffness_0 * traceG;
}
//TODO: Elastic Force
Matrix4x4 force = F * S * inv_Dm[tet].transpose;
float volume = 1/(inv_Dm[tet].determinant * 6); //注意下这里体积公式 S=||A||/6 S=1/(6*||A^-1||)
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
force[i, j] = -1*volume * force[i, j];
Force[Tet[tet * 4 + 1]].x += force[0, 0];
Force[Tet[tet * 4 + 1]].y += force[1, 0];
Force[Tet[tet * 4 + 1]].z += force[2, 0];
Force[Tet[tet * 4 + 2]].x += force[0, 1];
Force[Tet[tet * 4 + 2]].y += force[1, 1];
Force[Tet[tet * 4 + 2]].z += force[2, 1];
Force[Tet[tet * 4 + 3]].x += force[0, 2];
Force[Tet[tet * 4 + 3]].y += force[1, 2];
Force[Tet[tet * 4 + 3]].z += force[2, 2];
Force[Tet[tet * 4 + 0]].x -= (force[0, 0] + force[0, 1] + force[0, 2]);
Force[Tet[tet * 4 + 0]].y -= (force[1, 0] + force[1, 1] + force[1, 2]);
Force[Tet[tet * 4 + 0]].z -= (force[2, 0] + force[2, 1] + force[2, 2]);
}
Smooth_V();
for (int i=0; i<number; i++)
{
//TODO: Update X and V here.
V[i] = (V[i] + dt * Force[i] / mass) * damp;
X[i] = X[i] + V[i] * dt;
//TODO: (Particle) collision with floor.
if(X[i].y<-3f) //这里仅对局部坐标进行相应判断,选取-3的原因是底部物体的世界坐标是-3
{
X[i].y = -3f;
if (V[i].y < 0)
V[i].y = -V[i].y;
}
}
}
// Update is called once per frame
void Update()
{
for(int l=0; l<10; l++)
_Update();
// Dump the vertex array for rendering.
Vector3[] vertices = new Vector3[tet_number*12];
int vertex_number=0;
for(int tet=0; tet<tet_number; tet++)
{
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+0]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
vertices[vertex_number++]=X[Tet[tet*4+1]];
vertices[vertex_number++]=X[Tet[tet*4+2]];
vertices[vertex_number++]=X[Tet[tet*4+3]];
}
Mesh mesh = GetComponent<MeshFilter> ().mesh;
mesh.vertices = vertices;
mesh.RecalculateNormals ();
}
}