├── requirements.txt ├── data └── direct_vs_cg.gif ├── LICENSE ├── .gitignore ├── README.md └── implicit_mass_spring.py /requirements.txt: -------------------------------------------------------------------------------- 1 | taichi 2 | -------------------------------------------------------------------------------- /data/direct_vs_cg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taichiCourse01/taichi_course_final_project/HEAD/data/direct_vs_cg.gif -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 TaichiCourse 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Pycharm 2 | .idea 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | *.py,cover 53 | .hypothesis/ 54 | .pytest_cache/ 55 | cover/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | .pybuilder/ 79 | target/ 80 | 81 | # Jupyter Notebook 82 | .ipynb_checkpoints 83 | 84 | # IPython 85 | profile_default/ 86 | ipython_config.py 87 | 88 | # pyenv 89 | # For a library or package, you might want to ignore these files since the code is 90 | # intended to run in multiple environments; otherwise, check them in: 91 | # .python-version 92 | 93 | # pipenv 94 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 95 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 96 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 97 | # install all needed dependencies. 98 | #Pipfile.lock 99 | 100 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 101 | __pypackages__/ 102 | 103 | # Celery stuff 104 | celerybeat-schedule 105 | celerybeat.pid 106 | 107 | # SageMath parsed files 108 | *.sage.py 109 | 110 | # Environments 111 | .env 112 | .venv 113 | env/ 114 | venv/ 115 | ENV/ 116 | env.bak/ 117 | venv.bak/ 118 | 119 | # Spyder project settings 120 | .spyderproject 121 | .spyproject 122 | 123 | # Rope project settings 124 | .ropeproject 125 | 126 | # mkdocs documentation 127 | /site 128 | 129 | # mypy 130 | .mypy_cache/ 131 | .dmypy.json 132 | dmypy.json 133 | 134 | # Pyre type checker 135 | .pyre/ 136 | 137 | # pytype static type analyzer 138 | .pytype/ 139 | 140 | # Cython debug symbols 141 | cython_debug/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 太极图形课S1-大作业 2 | 3 | ## 作业来源 4 | > 请你介绍大作业的灵感来源,可以是自己原创的想法,也可以是复现一篇论文。如果有参考论文、文章或者参考代码,请**务必**给出来源。 5 | 6 | ## 运行方式 7 | > 展示项目运行方式,让更多的人在自己的机器上运行你的代码查看运行效果。 8 | > 为了复现运行效果,请给出你的运行环境。 9 | > 如果项目有依赖,请给出项目的依赖。 10 | 11 | #### 运行环境: 12 | > 请列出`Taichi`程序运行环境,可以在命令行中输入命令:`$ ti`,然后复制输出的第一行。 13 | 14 | #### 运行: 15 | > 请列出哪些代码可以运行以及如何运行,如果有命令行参数可以列出参数以及参数对应的含义。 16 | 17 | ## 效果展示 18 | > 这里可以展示这份作业运行起来后的可视化效果,可以让其他人更直观感受到你的工作。可以是**图片**(比如渲染效果)、**动图**或**视频**。 19 | 20 | ## 整体结构 21 | > 脉络清晰的结构能完整展示你的设计思想,以及实现方式,方便读者快速代入。Python的代码,可以使用命令:`yapf -i xx.py`来格式化。可以在repo的目录中包含如下内容: 22 | ``` 23 | -LICENSE 24 | -|data 25 | -README.MD 26 | -xx.py 27 | ``` 28 | 29 | ## 实现细节: 30 | > 请给出代码的具体实现流程和细节,让感兴趣的人能够更深入的了解你的代码。 31 | 32 | # 示例 33 | 下面给出一个模版供大家参考,在你提交大作业的时候可以删除示例部分。按上面的要求填写自己大作业的内容。 34 | 35 | ## 作业来源 36 | 布料仿真是计算机图形学中一个重要的仿真场景,在电影和游戏中都有很广泛的应用。一个很经典的布料仿真方法是 [*Large Steps in Cloth Simulation.*](https://www.cs.cmu.edu/~baraff/papers/sig98.pdf) David Baraff, Andrew Witkin. Siggraph 1998. 这也是皮克斯 (Pixar) 动画仿真器 **Fizt** 使用的布料仿真方法。在SCA 2020中有有一篇[论文](https://www.tkim.graphics/FEMBW/)描述了该论文中布料仿真模型对应的FEM形式。 37 | 38 | 本次大作业就是对Baraff-Witkin布料仿真的实现。在实现过程中,可以了解能量、力和刚度矩阵的计算。除此之外,还可以了解Taichi语言中稀疏矩阵 (Sparse Matrix) 的使用,共轭梯度法 (Conjugate Gradient, CG) 的实现。 39 | 40 | ## 运行方式 41 | #### 运行环境: 42 | `[Taichi] version 0.8.7, llvm 10.0.0, commit 1c3c705a, osx, python 3.8.8` 43 | 44 | #### 运行: 45 | 在运行 `implicit_mass_spring_system.py`时,可以通过命令行参数 `-cg` 来控制是否使用 **CG** solver。 46 | 47 | - 使用 Direct solver: 48 | `python implicit_mass_spring_system.py` 49 | 50 | - 使用 CG solver: 51 | `python implicit_mass_spring_system.py -cg` 52 | 53 | 54 | ## 效果展示 55 | 左侧:Direct solver;右侧: Conjugate Gradient (CG) solver。 56 | ![mass spring demo](./data/direct_vs_cg.gif) 57 | 58 | ## 整体结构 59 | ``` 60 | -LICENSE 61 | -|data 62 | -README.MD 63 | -implicit_mass_spring_system.py 64 | ``` 65 | 66 | ## 实现细节: 67 | `implicit_mass_spring_system.py`是项目源代码,其中包含一个Cloth类和一个`main`函数。具体公式推导,可以看: [here](https://github.com/taichiCourse01/taichiCourse01/blob/main/material/09_implicit_integration.pdf) (P45-46)。 68 | 69 | ### 整体流程 70 | 1. 布料的初始化 71 | 2. 创建一个GUI来显示布料 72 | 3. 根据命令行选项更新布料,默认使用 direct solver 来更新布料。可以命令行参数 `-cg` 来使用 CG solver。 73 | 4. 在GUI中显示布料 74 | 75 | ### 布料类 76 | 1. [初始化](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L13) 77 | - 设置field,确定数据结构 78 | - 初始化field,设置位置,速度,质量和每个弹簧两个端点索引 79 | 80 | 2. 布料更新 Direct Solver: [`update_direct(h)`](https://github.com/FantasyVR/taichi_course_final_project/blob/8f79e0026237e75ec3abe7d09b39be0a2fadc994/implicit_mass_spring.py#L156) 81 | 82 | **计算力**: [`compute_force()`](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L98) 83 | - 重力 84 | - 弹簧力 85 | - 约束力: 固定布料的两个顶点,使用一个刚度极大的弹簧来模拟。 86 | 87 | **计算力的导数**:[`compute_force_Jacobians`](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L120) 88 | - 弹簧力的导数 89 | - 约束力的导数 90 | 91 | **组装刚度矩阵**: [`assemble_K`](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L138) 92 | - 遍历每个弹簧,组建度矩阵 93 | 94 | **组装系统矩阵**:[here](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L162) 95 | - 系统矩阵 96 | 97 | **计算系统矩阵右侧b**: [here](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L169) 98 | - 99 | 100 | **求解线性系统**: [here](https://github.com/FantasyVR/taichi_course_final_project/blob/304a32dfa686862adcb54f737ed6970e21fe8d5b/implicit_mass_spring.py#L171) 101 | - 102 | 103 | **更新速度和位置**: [`directUpdatePosVel`](https://github.com/FantasyVR/taichi_course_final_project/blob/8f79e0026237e75ec3abe7d09b39be0a2fadc994/implicit_mass_spring.py#L151) 104 | - 105 | 106 | 3. 布料更新 CG solver: [`update_cg(h)`](https://github.com/FantasyVR/taichi_course_final_project/blob/8f79e0026237e75ec3abe7d09b39be0a2fadc994/implicit_mass_spring.py#L249) 107 | 108 | 在使用CG进行布料更新的时候,同样需要计算力和计算力的导数。但我们使用CG的时候,不需要显式的构建出刚度矩阵,只需要计算矩阵-向量乘就可以。具体算法请参考:[here](https://github.com/taichiCourse01/taichiCourse01/blob/main/material/09_implicit_integration.pdf) (P105-110)。 109 | 110 | -------------------------------------------------------------------------------- /implicit_mass_spring.py: -------------------------------------------------------------------------------- 1 | # https://www.cs.cmu.edu/~baraff/papers/sig98.pdf 2 | import argparse 3 | 4 | import numpy as np 5 | 6 | import taichi as ti 7 | 8 | import time 9 | 10 | 11 | @ti.data_oriented 12 | class Cloth: 13 | def __init__(self, N): 14 | self.N = N 15 | self.NF = 2 * N**2 # number of faces 16 | self.NV = (N + 1)**2 # number of vertices 17 | self.NE = 2 * N * (N + 1) + 2 * N * N # numbser of edges 18 | self.initPos = ti.Vector.field(2, ti.f32, self.NV) 19 | self.pos = ti.Vector.field(2, ti.f32, self.NV) 20 | self.vel = ti.Vector.field(2, ti.f32, self.NV) 21 | self.force = ti.Vector.field(2, ti.f32, self.NV) 22 | self.mass = ti.field(ti.f32, self.NV) 23 | self.spring = ti.Vector.field(2, ti.i32, self.NE) 24 | self.rest_len = ti.field(ti.f32, self.NE) 25 | self.ks = 1000.0 # spring stiffness 26 | self.kf = 1.0e5 # Attachment point stiffness 27 | self.Jx = ti.Matrix.field(2, 2, ti.f32, self.NE) # Force Jacobian 28 | self.Jf = ti.Matrix.field(2, 2, ti.f32, 2) # Attachment Jacobian 29 | 30 | self.init_pos() 31 | self.init_edges() 32 | 33 | # For sparse matrix solver, PPT: P45 34 | max_num_triplets = 10000 35 | self.MBuilder = ti.linalg.SparseMatrixBuilder(2 * self.NV, 2 * self.NV, 36 | max_num_triplets) 37 | self.init_mass_sp(self.MBuilder) 38 | self.M = self.MBuilder.build() 39 | self.KBuilder = ti.linalg.SparseMatrixBuilder(2 * self.NV, 2 * self.NV, 40 | max_num_triplets) 41 | 42 | # For conjugate gradient method, PPT: P106 43 | self.x = ti.Vector.field(2, ti.f32, self.NV) 44 | self.Ax = ti.Vector.field(2, ti.f32, self.NV) 45 | self.b = ti.Vector.field(2, ti.f32, self.NV) 46 | self.r = ti.Vector.field(2, ti.f32, self.NV) 47 | self.d = ti.Vector.field(2, ti.f32, self.NV) 48 | self.Ad = ti.Vector.field(2, ti.f32, self.NV) 49 | 50 | @ti.kernel 51 | def init_pos(self): 52 | for i, j in ti.ndrange(self.N + 1, self.N + 1): 53 | k = i * (self.N + 1) + j 54 | self.initPos[k] = ti.Vector([i, j]) / self.N * 0.5 + ti.Vector( 55 | [0.25, 0.25]) 56 | self.pos[k] = self.initPos[k] 57 | self.vel[k] = ti.Vector([0, 0]) 58 | self.mass[k] = 1.0 59 | 60 | @ti.kernel 61 | def init_edges(self): 62 | pos, spring, N, rest_len = ti.static(self.pos, self.spring, self.N, 63 | self.rest_len) 64 | for i, j in ti.ndrange(N + 1, N): 65 | idx, idx1 = i * N + j, i * (N + 1) + j 66 | spring[idx] = ti.Vector([idx1, idx1 + 1]) 67 | start = N * (N + 1) 68 | for i, j in ti.ndrange(N, N + 1): 69 | idx, idx1, idx2 = start + i + j * N, i * (N + 1) + j, i * ( 70 | N + 1) + j + N + 1 71 | spring[idx] = ti.Vector([idx1, idx2]) 72 | start = 2 * N * (N + 1) 73 | for i, j in ti.ndrange(N, N): 74 | idx, idx1, idx2 = start + i * N + j, i * (N + 1) + j, (i + 1) * ( 75 | N + 1) + j + 1 76 | spring[idx] = ti.Vector([idx1, idx2]) 77 | start = 2 * N * (N + 1) + N * N 78 | for i, j in ti.ndrange(N, N): 79 | idx, idx1, idx2 = start + i * N + j, i * (N + 1) + j + 1, ( 80 | i + 1) * (N + 1) + j 81 | spring[idx] = ti.Vector([idx1, idx2]) 82 | for i in range(self.NE): 83 | idx1, idx2 = spring[i] 84 | rest_len[i] = (pos[idx1] - pos[idx2]).norm() 85 | 86 | @ti.kernel 87 | def init_mass_sp(self, M: ti.linalg.sparse_matrix_builder()): 88 | for i in range(self.NV): 89 | M[2 * i + 0, 2 * i + 0] += self.mass[i] 90 | M[2 * i + 1, 2 * i + 1] += self.mass[i] 91 | 92 | @ti.func 93 | def clear_force(self): 94 | for i in self.force: 95 | self.force[i] = ti.Vector([0.0, 0.0]) 96 | 97 | @ti.kernel 98 | def compute_force(self): 99 | self.clear_force() 100 | gravity = ti.Vector([0.0, -2.0]) 101 | for i in self.force: 102 | self.force[i] += gravity * self.mass[i] 103 | 104 | for i in self.spring: 105 | idx1, idx2 = self.spring[i][0], self.spring[i][1] 106 | pos1, pos2 = self.pos[idx1], self.pos[idx2] 107 | dis = pos1 - pos2 108 | # Hook's law 109 | force = self.ks * (dis.norm() - 110 | self.rest_len[i]) * dis.normalized() 111 | self.force[idx1] -= force 112 | self.force[idx2] += force 113 | # Attachment constraint force 114 | self.force[self.N] += self.kf * (self.initPos[self.N] - 115 | self.pos[self.N]) 116 | self.force[self.NV - 1] += self.kf * (self.initPos[self.NV - 1] - 117 | self.pos[self.NV - 1]) 118 | 119 | @ti.kernel 120 | def compute_force_Jacobians(self): 121 | for i in self.spring: 122 | idx1, idx2 = self.spring[i][0], self.spring[i][1] 123 | pos1, pos2 = self.pos[idx1], self.pos[idx2] 124 | dx = pos1 - pos2 125 | I = ti.Matrix([[1.0, 0.0], [0.0, 1.0]]) 126 | dxtdx = ti.Matrix([[dx[0] * dx[0], dx[0] * dx[1]], 127 | [dx[1] * dx[0], dx[1] * dx[1]]]) 128 | l = dx.norm() 129 | if l != 0.0: 130 | l = 1.0 / l 131 | self.Jx[i] = (I - self.rest_len[i] * l * 132 | (I - dxtdx * l**2)) * self.ks 133 | # Attachment constraint force Jacobian 134 | self.Jf[0] = ti.Matrix([[-self.kf, 0], [0, -self.kf]]) 135 | self.Jf[1] = ti.Matrix([[-self.kf, 0], [0, -self.kf]]) 136 | 137 | @ti.kernel 138 | def assemble_K(self, K: ti.linalg.sparse_matrix_builder()): 139 | for i in self.spring: 140 | idx1, idx2 = self.spring[i][0], self.spring[i][1] 141 | for m, n in ti.static(ti.ndrange(2, 2)): 142 | K[2 * idx1 + m, 2 * idx1 + n] -= self.Jx[i][m, n] 143 | K[2 * idx1 + m, 2 * idx2 + n] += self.Jx[i][m, n] 144 | K[2 * idx2 + m, 2 * idx1 + n] += self.Jx[i][m, n] 145 | K[2 * idx2 + m, 2 * idx2 + n] -= self.Jx[i][m, n] 146 | for m, n in ti.static(ti.ndrange(2, 2)): 147 | K[2 * self.N + m, 2 * self.N + n] += self.Jf[0][m, n] 148 | K[2 * (self.NV - 1) + m, 2 * (self.NV - 1) + n] += self.Jf[1][m, n] 149 | 150 | @ti.kernel 151 | def directUpdatePosVel(self, h: ti.f32, v_next: ti.ext_arr()): 152 | for i in self.pos: 153 | self.vel[i] = ti.Vector([v_next[2 * i], v_next[2 * i + 1]]) 154 | self.pos[i] += h * self.vel[i] 155 | 156 | def update_direct(self, h): 157 | self.compute_force() 158 | self.compute_force_Jacobians() 159 | # Assemble global system 160 | self.assemble_K(self.KBuilder) 161 | K = self.KBuilder.build() 162 | A = self.M - h**2 * K 163 | solver = ti.linalg.SparseSolver(solver_type="LLT") 164 | solver.analyze_pattern(A) 165 | solver.factorize(A) 166 | 167 | vel = self.vel.to_numpy().reshape(2 * self.NV) 168 | force = self.force.to_numpy().reshape(2 * self.NV) 169 | b = h * force + self.M @ vel 170 | 171 | v_next = solver.solve(b) 172 | # flag = solver.info() 173 | # print("solver flag: ", flag) 174 | self.directUpdatePosVel(h, v_next) 175 | 176 | @ti.kernel 177 | def cgUpdatePosVel(self, h: ti.f32): 178 | for i in self.pos: 179 | self.vel[i] = self.x[i] 180 | self.pos[i] += h * self.vel[i] 181 | 182 | @ti.kernel 183 | def compute_RHS(self, h: ti.f32): 184 | #rhs = b = h * force + M @ v 185 | for i in range(self.NV): 186 | self.b[i] = h * self.force[i] + self.mass[i] * self.vel[i] 187 | 188 | @ti.func 189 | def dot(self, v1, v2): 190 | result = 0.0 191 | for i in range(self.NV): 192 | result += v1[i][0] * v2[i][0] 193 | result += v1[i][1] * v2[i][1] 194 | return result 195 | 196 | @ti.func 197 | def A_mult_x(self, h, dst, src): 198 | coeff = -h**2 199 | for i in range(self.NV): 200 | dst[i] = self.mass[i] * src[i] 201 | for i in range(self.NE): 202 | idx1, idx2 = self.spring[i][0], self.spring[i][1] 203 | temp = self.Jx[i] @ (src[idx1] - src[idx2]) 204 | dst[idx1] -= coeff * temp 205 | dst[idx2] += coeff * temp 206 | # Attachment constraint 207 | Attachment1, Attachment2 = self.N, self.NV - 1 208 | dst[Attachment1] -= coeff * self.kf * src[Attachment1] 209 | dst[Attachment2] -= coeff * self.kf * src[Attachment2] 210 | 211 | # conjugate gradient solving 212 | # https://www.cs.cmu.edu/~quake-papers/painless-conjugate-gradient.pdf 213 | 214 | @ti.kernel 215 | def before_ite(self) -> ti.f32: 216 | for i in range(self.NV): 217 | self.x[i] = ti.Vector([0.0, 0.0]) 218 | self.A_mult_x(h, self.Ax, self.x) # Ax = A @ x 219 | for i in range(self.NV): # r = b - A @ x 220 | self.r[i] = self.b[i] - self.Ax[i] 221 | for i in range(self.NV): # d = r 222 | self.d[i] = self.r[i] 223 | delta_new = self.dot(self.r, self.r) 224 | return delta_new 225 | 226 | @ti.kernel 227 | def run_iteration(self, delta_new: ti.f32) -> ti.f32: 228 | self.A_mult_x(h, self.Ad, self.d) # Ad = A @ d 229 | alpha = delta_new / self.dot(self.d, 230 | self.Ad) # alpha = (r^T * r) / dot(d, Ad) 231 | for i in range(self.NV): 232 | self.x[i] += alpha * self.d[i] # x^{i+1} = x^{i} + alpha * d 233 | self.r[i] -= alpha * self.Ad[i] # r^{i+1} = r^{i} + alpha * Ad 234 | delta_old = delta_new 235 | delta_new = self.dot(self.r, self.r) 236 | beta = delta_new / delta_old 237 | for i in range(self.NV): 238 | self.d[i] = self.r[i] + beta * self.d[ 239 | i] #p^{i+1} = r^{i+1} + beta * p^{i} 240 | return delta_new 241 | 242 | def cg(self, h: ti.f32): 243 | delta_new = self.before_ite() 244 | ite, iteMax = 0, 2 * self.NV 245 | while ite < iteMax and delta_new > 1.0e-6: 246 | delta_new = self.run_iteration(delta_new) 247 | ite += 1 248 | 249 | def update_cg(self, h): 250 | self.compute_force() 251 | self.compute_force_Jacobians() 252 | self.compute_RHS(h) 253 | self.cg(h) 254 | self.cgUpdatePosVel(h) 255 | 256 | def display(self, gui, radius=5, color=0xffffff): 257 | springs, pos = self.spring.to_numpy(), self.pos.to_numpy() 258 | line_Begin = np.zeros(shape=(springs.shape[0], 2)) 259 | line_End = np.zeros(shape=(springs.shape[0], 2)) 260 | for i in range(springs.shape[0]): 261 | idx1, idx2 = springs[i][0], springs[i][1] 262 | line_Begin[i], line_End[i] = pos[idx1], pos[idx2] 263 | gui.lines(line_Begin, line_End, radius=2, color=0x0000ff) 264 | gui.circles(self.pos.to_numpy(), radius, color) 265 | 266 | 267 | if __name__ == "__main__": 268 | ti.init(arch=ti.cpu) 269 | cloth = Cloth(N=5) 270 | parser = argparse.ArgumentParser() 271 | parser.add_argument('-cg', 272 | '--use_cg', 273 | action='store_true', 274 | help='Solve Ax=b with conjugate gradient method (CG).') 275 | args, unknowns = parser.parse_known_args() 276 | use_cg = args.use_cg 277 | 278 | gui = ti.GUI('Implicit Mass Spring System', res=(500, 500)) 279 | pause = False 280 | h, max_step = 0.01, 3 281 | while gui.running: 282 | for e in gui.get_events(): 283 | if e.key == gui.ESCAPE: 284 | gui.running = False 285 | elif e.key == gui.SPACE: 286 | pause = not pause 287 | if not pause: 288 | for i in range(max_step): 289 | if use_cg: 290 | cloth.update_cg(h) 291 | else: 292 | cloth.update_direct(h) 293 | cloth.display(gui) 294 | gui.show() --------------------------------------------------------------------------------