├── img ├── mpm.png └── marching_cube.jpg ├── mls_mpm.blend ├── LICENSE ├── mls_mpm.py ├── README.md └── mls_mpm_blender.py /img/mpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CzzzzH/MLS-MPM/HEAD/img/mpm.png -------------------------------------------------------------------------------- /mls_mpm.blend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CzzzzH/MLS-MPM/HEAD/mls_mpm.blend -------------------------------------------------------------------------------- /img/marching_cube.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CzzzzH/MLS-MPM/HEAD/img/marching_cube.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 CzzzzH 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 | -------------------------------------------------------------------------------- /mls_mpm.py: -------------------------------------------------------------------------------- 1 | import taichi as ti 2 | import numpy as np 3 | 4 | ti.init(arch=ti.cuda) 5 | 6 | dim = 3 7 | neighbour = (3, ) * dim 8 | bound = 3 9 | 10 | n_grid = 128 11 | dx = 1. / n_grid 12 | inv_dx = float(n_grid) 13 | dt = 1e-4 14 | substeps = int(1 / 120 // dt) 15 | 16 | p_rho = 1 17 | p_vol = (dx * 0.5) ** dim 18 | p_mass = p_vol * p_rho 19 | g = 9.8 20 | 21 | E = 5e3 22 | nu = 0.2 23 | mu_0 = E / (2. * (1. + nu)) 24 | lam_0 = E * nu / ((1. + nu) * (1. - 2. * nu)) 25 | Ef = 400 26 | 27 | bunny_vertices = [] 28 | bunny_scale = 1.5 29 | bunny_pos = np.array([0.5, 0.2, 0.5]) 30 | 31 | spot_vertices = [] 32 | spot_scale = 0.1 33 | spot_pos = np.array([0.3, 0.5, 0.5]) 34 | 35 | armadillo_vertices = [] 36 | armadillo_scale = 0.2 37 | armadillo_pos = np.array([0.7, 0.5, 0.5]) 38 | 39 | with open("bunny.obj", "r") as f: 40 | for line in f: 41 | if line[0] != "v": 42 | break 43 | tokens = line[:-1].split(" ") 44 | bunny_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 45 | 46 | with open("spot.obj", "r") as f: 47 | for line in f: 48 | if line[0] != "v": 49 | continue 50 | tokens = line[:-1].split(" ") 51 | spot_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 52 | 53 | with open("armadillo.obj", "r") as f: 54 | for line in f: 55 | if line[0] != "v": 56 | continue 57 | tokens = line[:-1].split(" ") 58 | armadillo_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 59 | 60 | n_bunny = len(bunny_vertices) 61 | n_spot = len(spot_vertices) 62 | n_armadillo = len(armadillo_vertices) 63 | n_particles = n_bunny + n_spot + n_armadillo 64 | 65 | print(f"Bunny Particles Num: {n_bunny}") 66 | print(f"Spot Particles Num: {n_spot}") 67 | print(f"Armadillo Particles Num: {n_armadillo}") 68 | print(f"Total Particles Num: {n_particles}") 69 | 70 | x = ti.Vector.field(dim, dtype=float, shape=(n_particles,)) 71 | v = ti.Vector.field(dim, dtype=float, shape=(n_particles,)) 72 | C = ti.Matrix.field(dim, dim, dtype=float, shape=(n_particles,)) 73 | F = ti.Matrix.field(dim, dim, dtype=float, shape=(n_particles,)) 74 | mat_idx = ti.field(dtype=int, shape=(n_particles,)) 75 | mat_color = ti.Vector.field(3, dtype=float, shape=(n_particles,)) 76 | 77 | Jp = ti.field(dtype=float, shape=(n_particles,)) 78 | Jf = ti.field(dtype=float, shape=(n_particles,)) 79 | grid_v = ti.Vector.field(dim, dtype=float, shape=(n_grid,) * dim) 80 | grid_m = ti.field(dtype=float, shape=(n_grid, ) * dim) 81 | 82 | if dim == 3: 83 | for i in range(n_particles): 84 | 85 | if i < n_bunny: 86 | x[i] = bunny_vertices[i] * bunny_scale + bunny_pos 87 | mat_idx[i] = 0 88 | mat_color[i] = [0.0, 0.6, 1.0] 89 | elif i < n_bunny + n_spot: 90 | x[i] = spot_vertices[i - n_bunny] * spot_scale + spot_pos 91 | mat_idx[i] = 1 92 | mat_color[i] = [1.0, 0.6, 0.0] 93 | else: 94 | x[i] = armadillo_vertices[i - n_bunny - n_spot] * armadillo_scale + armadillo_pos 95 | mat_idx[i] = 2 96 | mat_color[i] = [0.0, 1.0, 0.0] 97 | 98 | F[i] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] 99 | C[i] = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 100 | v[i] = [0, 0, 0] 101 | Jp[i] = 1 102 | Jf[i] = 1 103 | else: 104 | for i in range(n_particles): 105 | 106 | if i < n_bunny: 107 | x[i] = bunny_vertices[i] * bunny_scale + bunny_pos 108 | mat_idx[i] = 0 109 | mat_color[i] = [0.0, 0.6, 1.0] 110 | elif i < n_bunny + n_spot: 111 | x[i] = spot_vertices[i - n_bunny] * spot_scale + spot_pos 112 | mat_idx[i] = 1 113 | mat_color[i] = [1.0, 0.6, 0.0] 114 | else: 115 | x[i] = armadillo_vertices[i - n_bunny - n_spot] * armadillo_scale + armadillo_pos 116 | mat_idx[i] = 2 117 | mat_color[i] = [0.0, 1.0, 0.0] 118 | 119 | F[i] = [[1, 0], [0, 1]] 120 | C[i] = [[0, 0], [0, 0]] 121 | v[i] = [0, 0] 122 | Jp[i] = 1 123 | Jf[i] = 1 124 | 125 | @ti.kernel 126 | def substep(): 127 | 128 | for I in ti.grouped(grid_m): 129 | grid_v[I] = grid_v[I] * 0 130 | grid_m[I] = 0 131 | 132 | # P2G 133 | for p in x: 134 | 135 | base = (x[p] * inv_dx - 0.5).cast(int) 136 | fx = x[p] * inv_dx - base.cast(float) 137 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 138 | F[p] = (ti.Matrix.identity(float, dim) + dt * C[p]) @ F[p] 139 | 140 | h = ti.exp(10 * (1.0 - Jp[p])) 141 | if mat_idx[p] == 1: # Elastic 142 | h = 1 143 | mu, lam = mu_0 * h, lam_0 * h 144 | 145 | U, sig, V = ti.svd(F[p]) 146 | J = 1.0 147 | for d in ti.static(range(dim)): 148 | new_sig = sig[d, d] 149 | if mat_idx[p] == 2: # Snow 150 | new_sig = ti.min(ti.max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity 151 | Jp[p] *= sig[d, d] / new_sig 152 | sig[d, d] = new_sig 153 | J *= new_sig 154 | 155 | affine = ti.Matrix.identity(float, dim) 156 | if mat_idx[p] == 0: # weakly compressible fluid 157 | stress = -dt * 4 * Ef * p_vol * (Jf[p] - 1) * inv_dx * inv_dx 158 | affine = ti.Matrix.identity(float, dim) * stress + p_mass * C[p] 159 | else: 160 | if mat_idx[p] == 2: 161 | F[p] = U @ sig @ V.transpose() 162 | stress = 2 * mu * (F[p] - U @ V.transpose()) @ F[p].transpose() + ti.Matrix.identity(float, dim) * lam * J * (J - 1) 163 | stress = (-dt * 4 * p_vol * inv_dx * inv_dx) * stress 164 | affine = stress + p_mass * C[p] 165 | 166 | for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): 167 | dpos = (offset.cast(float) - fx) * dx 168 | weight = 1.0 169 | for i in ti.static(range(dim)): 170 | weight *= w[offset[i]][i] 171 | grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) 172 | grid_m[base + offset] += weight * p_mass 173 | 174 | # Boundary 175 | for I in ti.grouped(grid_m): 176 | if grid_m[I] > 0: 177 | grid_v[I] /= grid_m[I] 178 | grid_v[I][1] -= dt * g 179 | for i in ti.static(range(dim)): 180 | if I[i] < bound and grid_v[I][i] < 0: 181 | grid_v[I][i] = 0 182 | if I[i] > n_grid - bound and grid_v[I][i] > 0: 183 | grid_v[I][i] = 0 184 | 185 | # G2P 186 | for p in x: 187 | 188 | base = (x[p] * inv_dx - 0.5).cast(int) 189 | fx = x[p] * inv_dx - base.cast(float) 190 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 191 | new_v = ti.Vector.zero(float, dim) 192 | new_C = ti.Matrix.zero(float, dim, dim) 193 | 194 | for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): 195 | dpos = offset.cast(float) - fx 196 | g_v = grid_v[base + offset] 197 | weight = 1.0 198 | for i in ti.static(range(dim)): 199 | weight *= w[offset[i]][i] 200 | new_v += weight * g_v 201 | new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) 202 | 203 | v[p], C[p] = new_v, new_C 204 | Jf[p] *= 1 + dt * new_C.trace() 205 | x[p] += dt * v[p] 206 | 207 | if __name__ == '__main__': 208 | 209 | animate_name = "mls_mpm" 210 | window = ti.ui.Window(animate_name, (1024, 1024), vsync=True) 211 | 212 | canvas = window.get_canvas() 213 | canvas.set_background_color((1, 1, 1)) 214 | scene = ti.ui.Scene() 215 | camera = ti.ui.Camera() 216 | 217 | output_flag = True 218 | frame = 0 219 | 220 | while window.running: 221 | 222 | current_t = 0.0 223 | for i in range(substeps): 224 | substep() 225 | current_t += dt 226 | 227 | camera.position(0.5, 0.5, -0.8) 228 | camera.lookat(0.5, 0.5, 0) 229 | scene.set_camera(camera) 230 | 231 | scene.point_light(pos=(0, 10, 0), color=(1, 1, 1)) 232 | scene.ambient_light([0, 0, 0]) 233 | 234 | scene.particles(centers=x, radius=0.003, per_vertex_color=mat_color) 235 | canvas.scene(scene) 236 | 237 | # if output_flag == True: 238 | # window.save_image(f'output_{animate_name}/{animate_name}_{frame}.png') 239 | # frame += 1 240 | 241 | window.show() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # **Simulation of 3D fluid system** 2 | 3 | mpm 4 | 5 | This repository implements a MLS-MPM based 3D fluid system simulation with [Taichi](https://www.taichi-lang.org/) 6 | 7 | This is a course project of [CSE 291 Physics Simulation](https://cseweb.ucsd.edu/~alchern/teaching/cse291_sp23/) 8 | 9 | 10 | 11 | ### 1. Introduction 12 | 13 | In this project, we use [Taichi](https://www.taichi-lang.org/) to write a program that simulates a 3D fluid system. This system is implemented with [MLS-MPM](https://dl.acm.org/doi/pdf/10.1145/3197517.3201293) (moving least squares material point method). In this designed system, three cute animals are falling in a room. We assign these cute animals different materials: the bunny is in fluid material, the spot is in elastic material and the armadillo is in the snow material (a special elastic material). The MLS-MPM can easily combine these materials together in the same system and we can see the interaction of objects with different materials. 14 | 15 | The MLS-MPM is a method of hybrid of lagrangian and eulerian and it is also similar to [APIC](https://dl.acm.org/doi/pdf/10.1145/2766996) (Affine Particle-In-Cell method). The substep can be divided into three parts: particle to grid (P2G), grid operations and grid to particle (G2P). The initial particles are vertices of input meshes with some translation. We also have $\mathbf{v}_p^{(0)}=0$ for each particle $p$. 16 | 17 | 18 | 19 | ### 2. Implementation 20 | 21 | #### 2.1 Particle to Grid 22 | 23 | Firstly, we need to compute the deformation gredient $\mathbf{F}$ of each particle $p$. We have 24 | 25 | $$ 26 | \mathbf{F}_p^{(n+1)}=(\mathbf{I}+\Delta t \nabla \mathbf{v}_p^{(n)}) \mathbf{F}_p^{(n)} \quad \quad \mathbf{F}_p^{(0)}=\mathbf{I} 27 | $$ 28 | 29 | We use the matrix $\mathbf{C}$ in APIC to approximate $\mathbf{v}_p$ for efficiency here. So we have 30 | 31 | $$ 32 | \mathbf{F}_p^{(n+1)}=(\mathbf{I}+\Delta t \mathbf{C}_p^{(n)}) \mathbf{F}_p^{(n)} 33 | $$ 34 | 35 | Then we update the grid momentum (assuming symplectic Euler) for each grid $i$: 36 | 37 | $$ 38 | (m\mathbf{v})\_i^{(n+1)}=\sum\_{p \in N_i} w\_{ip}m\_p(\mathbf{v}\_p^{(n)}+\mathbf{C}\_p^{(n)}(\mathbf{x}\_i-\mathbf{x}\_p^{(n)})) 39 | +\Delta t(\sum\_{p \in N_i} w\_{ip}m_pg+\mathbf{f}_i^{(n+1)}) 40 | $$ 41 | 42 | There are two terms in this equation. The first term is the previous grid momentum gathered from the particles' momentum (weighted by a quadratic kernel function). The second term is the impulse resulted from gravity and the nodal force $\mathbf{f}_i^{(n+1)}$. $\mathbf{f}_i^{(n+1)}$ is derived from potential energy gradients and have the following final form: 43 | 44 | $$ 45 | \mathbf{f}\_i^{(n+1)}=-\frac{\partial U}{\partial \mathbf{x}\_i}=\frac{4}{\Delta x^2} \sum\_{p \in N_i} w\_{ip}V\_p^0 \mathbf{P}(\mathbf{F}\_p^{(n+1)})(\mathbf{F}\_p^{(n+1)})^\top(\mathbf{x}\_i-\mathbf{x}\_p^{(n)}) 46 | $$ 47 | 48 | in which $w_{ip}$ is also the weight value from the quadratic kernel function, $V_p^0$ is the initial volume of particle $p$, and $\mathbf{P}$ is the 1st Piola stress of deformation gradient $\mathbf{F}$. 49 | 50 | For particles' scattering quantities (grids' gathering quantities), we only consider the $3 \times 3 \times 3$ neighbor grids for each particle $p$. 51 | 52 | For the 1st Piola stress term, we use the Corotated model 53 | 54 | $$ 55 | \mathbf{P}(\mathbf{F})=\frac{\partial \psi}{\partial \mathbf{F}}=2\mu(\mathbf{F}-\mathbf{U}\mathbf{V}^\top)+\lambda(J-1)J \mathbf{F^{-\top}} 56 | $$ 57 | 58 | in which $\psi(\mathbf{F})=\mu \sum_i(\sigma_i-1)^2+ \frac{\lambda}{2}(J-1)^2$ is the elastic energy density, $\sigma_i$ are singular values of $\mathbf{F}$, and $J_p^{(n)}=\frac{V_p^{(n)}}{V_p^{(0)}}=\det(\mathbf{F}_p^{(n)})$ is the volume ratio. $\mathbf{U}$ and $\mathbf{V}$ are orthonormal matrices obtained from the singular value decomposition (SVD) of $\mathbf{F}$. 59 | 60 | It is worth noting that we only simulate the elastic and the snow material with this Corotaed model, and the handling details are different from different materials. 61 | 62 | (1) For the fluid material, we use the model of weakly compressible fluids. We apply this simplification so we do not need to do projection in the Grid Operation step. In this model, we use Cauchy stress $\sigma=-p \mathbf{I}=K(J-1)\mathbf{I}$ to replace the 1st Piola stress $\mathbf{P}(\mathbf{F})\mathbf{F}^\top$. $E$ is the bulk modulus and we use $K=400$ in our system. $J$ is the volume ratio mentioned above. We directly update $J$ in the fluid model in order to avoid catastrophic cancellation problem: 63 | 64 | $$ 65 | \mathbf{F}_p^{(n+1)}=(\mathbf{I}+\Delta t \mathbf{C}_p^{(n)}) \mathbf{F}_p^{(n)} 66 | $$ 67 | 68 | $$ 69 | \det(\mathbf{F}_p^{(n+1)})=\det(\mathbf{I}+\Delta t \mathbf{C}_p^{(n)})\det(\mathbf{F}_p^{(n)}) 70 | $$ 71 | 72 | $$ 73 | J_p^{(n+1)}=(1+\Delta t\mathbf{tr}(\mathbf{C}_p^{(n)}))J_p^{(n)} 74 | $$ 75 | 76 | (2) For the elastic material, $\mu$ and $\lambda$ are obtained from Young’s modulus $E$ and Poisson ratio $\nu$. The relationship is 77 | 78 | $$ 79 | \mu=\frac{E}{2(1+\nu)} \quad \quad \lambda=\frac{E \nu}{(1+\nu)(1-2\nu)} \\ 80 | $$ 81 | 82 | in which we use $E=5000$ and $\nu=0.2$ 83 | 84 | (3) For the snow material, we also use $\mu$ and $\lambda$ from Young’s modulus $E$ and Poisson ratio $\nu$. However, we additionally add a hardening coefficient $h=e^{10(1-J)}$ that scale each particle's $\mu$ and $\lambda$ to simulate the properties that the snow would harden when compressed. Then we have $\mu'=h\mu$ and $\lambda'=h\lambda$. We also clamp the $\sigma_i$ to $[0.975, 1.0045]$ to simulate snow's elastic deformation property. 85 | 86 | #### 2.2 Grid Operation 87 | 88 | Since we do not need to do projection in the Grid Operation. There are only two things we need to do: 89 | 90 | (1) We calculate the velocity of each grid $i$: 91 | 92 | $$ 93 | \hat{\mathbf{v}}_i^{(n+1)}=\frac{(m\mathbf{v})_i^{(n+1)}}{m_i^{(n+1)}} 94 | $$ 95 | 96 | (2) We enforce the boundary conditions (BC) with the grid velocity: 97 | 98 | $$ 99 | \mathbf{v}_i^{(n+1)}=\hat{\mathbf{v}}_i^{(n+1)}-\mathbf{n} \cdot \min(\mathbf{n}^\top \mathbf{v}_i^{(n+1)},0) 100 | $$ 101 | 102 | Actually we also add the gravity in this stage instead of adding it in the P2G part for convenience. 103 | 104 | #### 2.3 Grid to Particle 105 | 106 | In this last part, we update the velocity and the position of each particle $p$ from grids with symplectic Euler. We also update the matrix $\mathbf{C}$ . Then we have 107 | 108 | $$ 109 | \mathbf{v}\_p^{(n+1)}=\sum\_{i \in N_p} w\_{ip}\mathbf{v}\_i^{(n+1)} 110 | $$ 111 | 112 | $$ 113 | \mathbf{C}\_p^{(n+1)}=\frac{4}{\Delta x^2} \sum\_{i \in N_p} w\_{ip}\mathbf{v}\_i^{(n+1)}(\mathbf{x}\_i-\mathbf{x}\_p^{(n)})^\top 114 | $$ 115 | 116 | $$ 117 | \mathbf{x}\_p^{(n+1)}=\mathbf{x}\_p^{(n)}+\Delta t \mathbf{v}\_p^{(n+1)} 118 | $$ 119 | 120 | 121 | 122 | ### 3. Run 123 | 124 | #### 3.1 Render in the Blender 125 | 126 | For better visualization, we reconstruct mesh surfaces from the particles in the system and render the scene with the software [Blender](https://www.blender.org/) and the one its plugin [Taichi-Blend](https://github.com/taichi-dev/taichi_blend). 127 | 128 | To reconstruct the surfaces, we need to voxelize the particles at first. We calculate the density of each grid $i$ with the quadratic kernel function 129 | 130 | $$ 131 | \rho_i=\sum_{p \in N_i} w_{ip} 132 | $$ 133 | 134 | And then we choose $\rho_{base}=0.125$ as a contour to reconstruct surface from the density value. We use the marching cube algorithm to reconstruct from each grid. The following figure show the cube configurations (the colors of a cube's 8 vertices are assigned according to whether its density value is larger than $\rho_{base}$ or not) 135 | 136 | ![marching_cube](./img/marching_cube.jpg) 137 | 138 | We would not describe the complex marching cube algorithm in detail since they are not parts of simulation (and the codes of these parts are mostly borrowed from Taichi-Blend). 139 | 140 | Finally we render the whole scene with path-tracing algorithm after obtaining the meshes from the particles. 141 | 142 | #### 3.2 Run the code 143 | 144 | We provide multiple files in this project. **mls_mpm.py** is the code only do the simulation and present the particles with the Taichi GUI. **mls_mpm.blend** is the Blender project file. We produce this project on **Blender 2.93** with the Taichi-Blend plugin (Release [v0.07](https://github.com/taichi-dev/taichi_blend/releases/tag/v0.0.7)). **mls_mpm_blender.py** is the script in the Blender project which can run directly and simulate the system in Blender. 145 | 146 | **Note:** we do a little modification in the source code of Taichi-Blend plugin. If you would like to run the code, please add this function in the path *'Taichi-Blend/bundle-packages/numblend/anim.py'* 147 | 148 | ```python 149 | def objects_meshes_update(objects, meshes): 150 | def callback(): 151 | for i in range(len(objects)): 152 | objects[i].data = meshes[i] 153 | return AnimUpdate(callback) 154 | ``` 155 | 156 | -------------------------------------------------------------------------------- /mls_mpm_blender.py: -------------------------------------------------------------------------------- 1 | import numblend as nb 2 | import taichi as ti 3 | import numpy as np 4 | import bpy 5 | 6 | ti.init(arch=ti.cuda, device_memory_fraction=0.8) 7 | nb.init() 8 | 9 | @ti.data_oriented 10 | class MCISO: 11 | et2 = np.array([ 12 | [[-1, -1], [-1, -1]], # 13 | [[0, 1], [-1, -1]], #a 14 | [[0, 2], [-1, -1]], #b 15 | [[1, 2], [-1, -1]], #ab 16 | [[1, 3], [-1, -1]], #c 17 | [[0, 3], [-1, -1]], #ca 18 | [[2, 3], [0, 1]], #cb 19 | [[2, 3], [-1, -1]], #cab 20 | [[2, 3], [-1, -1]], #d 21 | [[2, 3], [0, 1]], #da 22 | [[0, 3], [-1, -1]], #db 23 | [[1, 3], [-1, -1]], #dab 24 | [[1, 2], [-1, -1]], #dc 25 | [[0, 2], [-1, -1]], #dca 26 | [[0, 1], [-1, -1]], #dcb 27 | [[-1, -1], [-1, -1]], #dcab 28 | ], np.int32) 29 | et3 = np.array([ 30 | [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 31 | [0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 32 | [0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 33 | [1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 34 | [1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 35 | [0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 36 | [9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 37 | [2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1], 38 | [3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 39 | [0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 40 | [1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 41 | [1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1], 42 | [3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 43 | [0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1], 44 | [3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1], 45 | [9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 46 | [4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 47 | [4, 3, 0, 7, 3, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 48 | [0, 1, 9, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 49 | [4, 1, 9, 4, 7, 1, 7, 3, 1, -1, -1, -1, -1, -1, -1, -1], 50 | [1, 2, 10, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 51 | [3, 4, 7, 3, 0, 4, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1], 52 | [9, 2, 10, 9, 0, 2, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1], 53 | [2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, -1, -1, -1, -1], 54 | [8, 4, 7, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 55 | [11, 4, 7, 11, 2, 4, 2, 0, 4, -1, -1, -1, -1, -1, -1, -1], 56 | [9, 0, 1, 8, 4, 7, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1], 57 | [4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, -1, -1, -1, -1], 58 | [3, 10, 1, 3, 11, 10, 7, 8, 4, -1, -1, -1, -1, -1, -1, -1], 59 | [1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, -1, -1, -1, -1], 60 | [4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, -1, -1, -1, -1], 61 | [4, 7, 11, 4, 11, 9, 9, 11, 10, -1, -1, -1, -1, -1, -1, -1], 62 | [9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 63 | [9, 5, 4, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 64 | [0, 5, 4, 1, 5, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 65 | [8, 5, 4, 8, 3, 5, 3, 1, 5, -1, -1, -1, -1, -1, -1, -1], 66 | [1, 2, 10, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 67 | [3, 0, 8, 1, 2, 10, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1], 68 | [5, 2, 10, 5, 4, 2, 4, 0, 2, -1, -1, -1, -1, -1, -1, -1], 69 | [2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, -1, -1, -1, -1], 70 | [9, 5, 4, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 71 | [0, 11, 2, 0, 8, 11, 4, 9, 5, -1, -1, -1, -1, -1, -1, -1], 72 | [0, 5, 4, 0, 1, 5, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1], 73 | [2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, -1, -1, -1, -1], 74 | [10, 3, 11, 10, 1, 3, 9, 5, 4, -1, -1, -1, -1, -1, -1, -1], 75 | [4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, -1, -1, -1, -1], 76 | [5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, -1, -1, -1, -1], 77 | [5, 4, 8, 5, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1], 78 | [9, 7, 8, 5, 7, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 79 | [9, 3, 0, 9, 5, 3, 5, 7, 3, -1, -1, -1, -1, -1, -1, -1], 80 | [0, 7, 8, 0, 1, 7, 1, 5, 7, -1, -1, -1, -1, -1, -1, -1], 81 | [1, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 82 | [9, 7, 8, 9, 5, 7, 10, 1, 2, -1, -1, -1, -1, -1, -1, -1], 83 | [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, -1, -1, -1, -1], 84 | [8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, -1, -1, -1, -1], 85 | [2, 10, 5, 2, 5, 3, 3, 5, 7, -1, -1, -1, -1, -1, -1, -1], 86 | [7, 9, 5, 7, 8, 9, 3, 11, 2, -1, -1, -1, -1, -1, -1, -1], 87 | [9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, -1, -1, -1, -1], 88 | [2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, -1, -1, -1, -1], 89 | [11, 2, 1, 11, 1, 7, 7, 1, 5, -1, -1, -1, -1, -1, -1, -1], 90 | [9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, -1, -1, -1, -1], 91 | [5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, -1], 92 | [11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, -1], 93 | [11, 10, 5, 7, 11, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 94 | [10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 95 | [0, 8, 3, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 96 | [9, 0, 1, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 97 | [1, 8, 3, 1, 9, 8, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1], 98 | [1, 6, 5, 2, 6, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 99 | [1, 6, 5, 1, 2, 6, 3, 0, 8, -1, -1, -1, -1, -1, -1, -1], 100 | [9, 6, 5, 9, 0, 6, 0, 2, 6, -1, -1, -1, -1, -1, -1, -1], 101 | [5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, -1, -1, -1, -1], 102 | [2, 3, 11, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 103 | [11, 0, 8, 11, 2, 0, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1], 104 | [0, 1, 9, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1, -1, -1, -1], 105 | [5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, -1, -1, -1, -1], 106 | [6, 3, 11, 6, 5, 3, 5, 1, 3, -1, -1, -1, -1, -1, -1, -1], 107 | [0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, -1, -1, -1, -1], 108 | [3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, -1, -1, -1, -1], 109 | [6, 5, 9, 6, 9, 11, 11, 9, 8, -1, -1, -1, -1, -1, -1, -1], 110 | [5, 10, 6, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 111 | [4, 3, 0, 4, 7, 3, 6, 5, 10, -1, -1, -1, -1, -1, -1, -1], 112 | [1, 9, 0, 5, 10, 6, 8, 4, 7, -1, -1, -1, -1, -1, -1, -1], 113 | [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, -1, -1, -1, -1], 114 | [6, 1, 2, 6, 5, 1, 4, 7, 8, -1, -1, -1, -1, -1, -1, -1], 115 | [1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, -1, -1, -1, -1], 116 | [8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, -1, -1, -1, -1], 117 | [7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, -1], 118 | [3, 11, 2, 7, 8, 4, 10, 6, 5, -1, -1, -1, -1, -1, -1, -1], 119 | [5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, -1, -1, -1, -1], 120 | [0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, -1, -1, -1, -1], 121 | [9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, -1], 122 | [8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, -1, -1, -1, -1], 123 | [5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, -1], 124 | [0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, -1], 125 | [6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, -1, -1, -1, -1], 126 | [10, 4, 9, 6, 4, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 127 | [4, 10, 6, 4, 9, 10, 0, 8, 3, -1, -1, -1, -1, -1, -1, -1], 128 | [10, 0, 1, 10, 6, 0, 6, 4, 0, -1, -1, -1, -1, -1, -1, -1], 129 | [8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, -1, -1, -1, -1], 130 | [1, 4, 9, 1, 2, 4, 2, 6, 4, -1, -1, -1, -1, -1, -1, -1], 131 | [3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, -1, -1, -1, -1], 132 | [0, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 133 | [8, 3, 2, 8, 2, 4, 4, 2, 6, -1, -1, -1, -1, -1, -1, -1], 134 | [10, 4, 9, 10, 6, 4, 11, 2, 3, -1, -1, -1, -1, -1, -1, -1], 135 | [0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, -1, -1, -1, -1], 136 | [3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, -1, -1, -1, -1], 137 | [6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, -1], 138 | [9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, -1, -1, -1, -1], 139 | [8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, -1], 140 | [3, 11, 6, 3, 6, 0, 0, 6, 4, -1, -1, -1, -1, -1, -1, -1], 141 | [6, 4, 8, 11, 6, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 142 | [7, 10, 6, 7, 8, 10, 8, 9, 10, -1, -1, -1, -1, -1, -1, -1], 143 | [0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, -1, -1, -1, -1], 144 | [10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, -1, -1, -1, -1], 145 | [10, 6, 7, 10, 7, 1, 1, 7, 3, -1, -1, -1, -1, -1, -1, -1], 146 | [1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, -1, -1, -1, -1], 147 | [2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, -1], 148 | [7, 8, 0, 7, 0, 6, 6, 0, 2, -1, -1, -1, -1, -1, -1, -1], 149 | [7, 3, 2, 6, 7, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 150 | [2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, -1, -1, -1, -1], 151 | [2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, -1], 152 | [1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, -1], 153 | [11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, -1, -1, -1, -1], 154 | [8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, -1], 155 | [0, 9, 1, 11, 6, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 156 | [7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, -1, -1, -1, -1], 157 | [7, 11, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 158 | [7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 159 | [3, 0, 8, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 160 | [0, 1, 9, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 161 | [8, 1, 9, 8, 3, 1, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1], 162 | [10, 1, 2, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 163 | [1, 2, 10, 3, 0, 8, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1], 164 | [2, 9, 0, 2, 10, 9, 6, 11, 7, -1, -1, -1, -1, -1, -1, -1], 165 | [6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, -1, -1, -1, -1], 166 | [7, 2, 3, 6, 2, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 167 | [7, 0, 8, 7, 6, 0, 6, 2, 0, -1, -1, -1, -1, -1, -1, -1], 168 | [2, 7, 6, 2, 3, 7, 0, 1, 9, -1, -1, -1, -1, -1, -1, -1], 169 | [1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, -1, -1, -1, -1], 170 | [10, 7, 6, 10, 1, 7, 1, 3, 7, -1, -1, -1, -1, -1, -1, -1], 171 | [10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, -1, -1, -1, -1], 172 | [0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, -1, -1, -1, -1], 173 | [7, 6, 10, 7, 10, 8, 8, 10, 9, -1, -1, -1, -1, -1, -1, -1], 174 | [6, 8, 4, 11, 8, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 175 | [3, 6, 11, 3, 0, 6, 0, 4, 6, -1, -1, -1, -1, -1, -1, -1], 176 | [8, 6, 11, 8, 4, 6, 9, 0, 1, -1, -1, -1, -1, -1, -1, -1], 177 | [9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, -1, -1, -1, -1], 178 | [6, 8, 4, 6, 11, 8, 2, 10, 1, -1, -1, -1, -1, -1, -1, -1], 179 | [1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, -1, -1, -1, -1], 180 | [4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, -1, -1, -1, -1], 181 | [10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, -1], 182 | [8, 2, 3, 8, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1], 183 | [0, 4, 2, 4, 6, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 184 | [1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, -1, -1, -1, -1], 185 | [1, 9, 4, 1, 4, 2, 2, 4, 6, -1, -1, -1, -1, -1, -1, -1], 186 | [8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, -1, -1, -1, -1], 187 | [10, 1, 0, 10, 0, 6, 6, 0, 4, -1, -1, -1, -1, -1, -1, -1], 188 | [4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, -1], 189 | [10, 9, 4, 6, 10, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 190 | [4, 9, 5, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 191 | [0, 8, 3, 4, 9, 5, 11, 7, 6, -1, -1, -1, -1, -1, -1, -1], 192 | [5, 0, 1, 5, 4, 0, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1], 193 | [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, -1, -1, -1, -1], 194 | [9, 5, 4, 10, 1, 2, 7, 6, 11, -1, -1, -1, -1, -1, -1, -1], 195 | [6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, -1, -1, -1, -1], 196 | [7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, -1, -1, -1, -1], 197 | [3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, -1], 198 | [7, 2, 3, 7, 6, 2, 5, 4, 9, -1, -1, -1, -1, -1, -1, -1], 199 | [9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, -1, -1, -1, -1], 200 | [3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, -1, -1, -1, -1], 201 | [6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, -1], 202 | [9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, -1, -1, -1, -1], 203 | [1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, -1], 204 | [4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, -1], 205 | [7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, -1, -1, -1, -1], 206 | [6, 9, 5, 6, 11, 9, 11, 8, 9, -1, -1, -1, -1, -1, -1, -1], 207 | [3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, -1, -1, -1, -1], 208 | [0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, -1, -1, -1, -1], 209 | [6, 11, 3, 6, 3, 5, 5, 3, 1, -1, -1, -1, -1, -1, -1, -1], 210 | [1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, -1, -1, -1, -1], 211 | [0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, -1], 212 | [11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, -1], 213 | [6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, -1, -1, -1, -1], 214 | [5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, -1, -1, -1, -1], 215 | [9, 5, 6, 9, 6, 0, 0, 6, 2, -1, -1, -1, -1, -1, -1, -1], 216 | [1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, -1], 217 | [1, 5, 6, 2, 1, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 218 | [1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, -1], 219 | [10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, -1, -1, -1, -1], 220 | [0, 3, 8, 5, 6, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 221 | [10, 5, 6, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 222 | [11, 5, 10, 7, 5, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 223 | [11, 5, 10, 11, 7, 5, 8, 3, 0, -1, -1, -1, -1, -1, -1, -1], 224 | [5, 11, 7, 5, 10, 11, 1, 9, 0, -1, -1, -1, -1, -1, -1, -1], 225 | [10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, -1, -1, -1, -1], 226 | [11, 1, 2, 11, 7, 1, 7, 5, 1, -1, -1, -1, -1, -1, -1, -1], 227 | [0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, -1, -1, -1, -1], 228 | [9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, -1, -1, -1, -1], 229 | [7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, -1], 230 | [2, 5, 10, 2, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1], 231 | [8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, -1, -1, -1, -1], 232 | [9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, -1, -1, -1, -1], 233 | [9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, -1], 234 | [1, 3, 5, 3, 7, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 235 | [0, 8, 7, 0, 7, 1, 1, 7, 5, -1, -1, -1, -1, -1, -1, -1], 236 | [9, 0, 3, 9, 3, 5, 5, 3, 7, -1, -1, -1, -1, -1, -1, -1], 237 | [9, 8, 7, 5, 9, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 238 | [5, 8, 4, 5, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1], 239 | [5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, -1, -1, -1, -1], 240 | [0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, -1, -1, -1, -1], 241 | [10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, -1], 242 | [2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, -1, -1, -1, -1], 243 | [0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, -1], 244 | [0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, -1], 245 | [9, 4, 5, 2, 11, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 246 | [2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, -1, -1, -1, -1], 247 | [5, 10, 2, 5, 2, 4, 4, 2, 0, -1, -1, -1, -1, -1, -1, -1], 248 | [3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, -1], 249 | [5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, -1, -1, -1, -1], 250 | [8, 4, 5, 8, 5, 3, 3, 5, 1, -1, -1, -1, -1, -1, -1, -1], 251 | [0, 4, 5, 1, 0, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 252 | [8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, -1, -1, -1, -1], 253 | [9, 4, 5, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 254 | [4, 11, 7, 4, 9, 11, 9, 10, 11, -1, -1, -1, -1, -1, -1, -1], 255 | [0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, -1, -1, -1, -1], 256 | [1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, -1, -1, -1, -1], 257 | [3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, -1], 258 | [4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, -1, -1, -1, -1], 259 | [9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, -1], 260 | [11, 7, 4, 11, 4, 2, 2, 4, 0, -1, -1, -1, -1, -1, -1, -1], 261 | [11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, -1, -1, -1, -1], 262 | [2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, -1, -1, -1, -1], 263 | [9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, -1], 264 | [3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, -1], 265 | [1, 10, 2, 8, 7, 4, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 266 | [4, 9, 1, 4, 1, 7, 7, 1, 3, -1, -1, -1, -1, -1, -1, -1], 267 | [4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, -1, -1, -1, -1], 268 | [4, 0, 3, 7, 4, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 269 | [4, 8, 7, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 270 | [9, 10, 8, 10, 11, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 271 | [3, 0, 9, 3, 9, 11, 11, 9, 10, -1, -1, -1, -1, -1, -1, -1], 272 | [0, 1, 10, 0, 10, 8, 8, 10, 11, -1, -1, -1, -1, -1, -1, -1], 273 | [3, 1, 10, 11, 3, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 274 | [1, 2, 11, 1, 11, 9, 9, 11, 8, -1, -1, -1, -1, -1, -1, -1], 275 | [3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, -1, -1, -1, -1], 276 | [0, 2, 11, 8, 0, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 277 | [3, 2, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 278 | [2, 3, 8, 2, 8, 10, 10, 8, 9, -1, -1, -1, -1, -1, -1, -1], 279 | [9, 10, 2, 0, 9, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 280 | [2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, -1, -1, -1, -1], 281 | [1, 10, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 282 | [1, 3, 8, 9, 1, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 283 | [0, 9, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 284 | [0, 3, 8, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 285 | [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 286 | ], np.int32)[:, :15].reshape(256, 5, 3) 287 | 288 | def __init__(self, N=128, N_res=None, dim=3, use_sparse=True): 289 | self.N = N 290 | self.dim = dim 291 | self.N_res = N_res or N**self.dim 292 | self.use_sparse = use_sparse 293 | 294 | et = [self.et2, self.et3][dim - 2] 295 | self.et = ti.Vector.field(dim, int, et.shape[:2]) 296 | @ti.materialize_callback 297 | def init_tables(): 298 | self.et.from_numpy(et) 299 | 300 | self.m = ti.field(float) 301 | self.g = ti.Vector.field(self.dim, float) 302 | self.Jtab = ti.field(int) 303 | indices = [ti.ij, ti.ijk][dim - 2] 304 | ti.root.dense(indices, self.N).place(self.g) 305 | if self.use_sparse: 306 | ti.root.pointer(indices, self.N // 16).dense(indices, 16).place(self.m) 307 | ti.root.dense(indices, 1).dense(ti.l, self.dim).pointer(indices, self.N // 8).bitmasked(indices, 8).place(self.Jtab) 308 | else: 309 | ti.root.dense(indices, 1).dense(ti.l, self.dim).dense(indices, self.N).place(self.Jtab) 310 | ti.root.dense(indices, self.N).place(self.m) 311 | 312 | self.Js = ti.Vector.field(self.dim + 1, int, (self.N_res, self.dim)) 313 | self.Jts = ti.Vector.field(self.dim, int, self.N_res) 314 | self.vs = ti.Vector.field(self.dim, float, self.N_res) 315 | self.Js_n = ti.field(int, ()) 316 | self.vs_n = ti.field(int, ()) 317 | 318 | @ti.kernel 319 | def march(self): 320 | self.Js_n[None] = 0 321 | self.vs_n[None] = 0 322 | 323 | for I in ti.grouped(self.m): 324 | id = self.get_cubeid(I) 325 | for m in range(self.et.shape[1]): 326 | et = self.et[id, m] 327 | if et[0] == -1: 328 | break 329 | 330 | Js_n = ti.atomic_add(self.Js_n[None], 1) 331 | for l in ti.static(range(self.dim)): 332 | e = et[l] 333 | J = ti.Vector(I.entries + [0]) 334 | if ti.static(self.dim == 2): 335 | if e == 1 or e == 2: J.z = 1 336 | if e == 2: J.x += 1 337 | if e == 3: J.y += 1 338 | else: 339 | if e == 1 or e == 3 or e == 5 or e == 7: J.w = 1 340 | elif e == 8 or e == 9 or e == 10 or e == 11: J.w = 2 341 | if e == 1 or e == 5 or e == 9 or e == 10: J.x += 1 342 | if e == 2 or e == 6 or e == 10 or e == 11: J.y += 1 343 | if e == 4 or e == 5 or e == 6 or e == 7: J.z += 1 344 | self.Js[Js_n, l] = J 345 | self.Jtab[J] = 1 346 | 347 | for J in ti.grouped(self.Jtab): 348 | if not ti.static(self.use_sparse): 349 | if self.Jtab[J] == 0: 350 | continue 351 | vs_n = ti.atomic_add(self.vs_n[None], 1) 352 | I = ti.Vector(ti.static(J.entries[:-1])) 353 | vs = I * 1.0 354 | p1 = self.m[I] 355 | for t in ti.static(range(self.dim)): 356 | if J.entries[-1] == t: 357 | p2 = self.m[I + ti.Vector.unit(self.dim, t, int)] 358 | p = (1 - p1) / (p2 - p1) 359 | vs[t] += max(0, min(1, p)) 360 | self.vs[vs_n] = vs 361 | self.Jtab[J] = vs_n 362 | 363 | for i in range(self.Js_n[None]): 364 | for l in ti.static(range(self.dim)): 365 | self.Jts[i][l] = self.Jtab[self.Js[i, l]] 366 | 367 | def clear(self): 368 | if self.use_sparse: 369 | ti.root.deactivate_all() 370 | else: 371 | self.m.fill(0) 372 | self.Jtab.fill(0) 373 | 374 | @ti.kernel 375 | def compute_grad(self): 376 | for I in ti.grouped(self.g): 377 | r = ti.Vector.zero(float, self.dim) 378 | for i in ti.static(range(self.dim)): 379 | d = ti.Vector.unit(self.dim, i, int) 380 | r[i] = self.m[I + d] - self.m[I - d] 381 | self.g[I] = r#.normalized(1e-4) 382 | 383 | @ti.func 384 | def get_cubeid(self, I): 385 | id = 0 386 | if ti.static(self.dim == 2): 387 | i, j = I 388 | if self.m[i, j] > 1: id |= 1 389 | if self.m[i + 1, j] > 1: id |= 2 390 | if self.m[i, j + 1] > 1: id |= 4 391 | if self.m[i + 1, j + 1] > 1: id |= 8 392 | else: 393 | i, j, k = I 394 | if self.m[i, j, k] > 1: id |= 1 395 | if self.m[i + 1, j, k] > 1: id |= 2 396 | if self.m[i + 1, j + 1, k] > 1: id |= 4 397 | if self.m[i, j + 1, k] > 1: id |= 8 398 | if self.m[i, j, k + 1] > 1: id |= 16 399 | if self.m[i + 1, j, k + 1] > 1: id |= 32 400 | if self.m[i + 1, j + 1, k + 1] > 1: id |= 64 401 | if self.m[i, j + 1, k + 1] > 1: id |= 128 402 | return id 403 | 404 | def get_mesh(self): 405 | Jts = self.Jts.to_numpy()[:self.Js_n[None]] 406 | vs = (self.vs.to_numpy()[:self.vs_n[None]] + 0.5) / self.N 407 | return vs, Jts 408 | 409 | @ti.data_oriented 410 | class Voxelizer: 411 | def __init__(self, N, pmin=0, pmax=1, bound=3): 412 | self.N = N 413 | self.pmin = ti.Vector([pmin for i in range(3)]) 414 | self.pmax = ti.Vector([pmax for i in range(3)]) 415 | self.bound = 3 416 | 417 | @ti.kernel 418 | def voxelize(self, out: ti.template(), pos: ti.template(), w0: ti.template(), st: ti.i32, ed: ti.i32): 419 | for i in pos: 420 | if i < st or i >= ed: 421 | continue 422 | p = (pos[i] - self.pmin) / (self.pmax - self.pmin) 423 | Xp = p * self.N 424 | if not all(self.bound <= Xp < self.N - self.bound): 425 | continue 426 | base = int(Xp - 0.5) 427 | fx = Xp - base 428 | w = [0.5 * (1.5 - fx)**2, 0.75 - (fx - 1)**2, 0.5 * (fx - 0.5)**2] 429 | for offset in ti.static(ti.grouped(ti.ndrange(3, 3, 3))): 430 | dpos = (offset - fx) / self.N 431 | weight = float(w0) 432 | for t in ti.static(range(3)): 433 | weight *= w[offset[t]][t] 434 | out[base + offset] += weight 435 | 436 | @ti.kernel 437 | def substep(): 438 | 439 | for I in ti.grouped(grid_m): 440 | grid_v[I] = grid_v[I] * 0 441 | grid_m[I] = 0 442 | 443 | # P2G 444 | for p in x: 445 | 446 | base = (x[p] * inv_dx - 0.5).cast(int) 447 | fx = x[p] * inv_dx - base.cast(float) 448 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 449 | F[p] = (ti.Matrix.identity(float, dim) + dt * C[p]) @ F[p] 450 | 451 | h = ti.exp(10 * (1.0 - Jp[p])) 452 | if mat_idx[p] == 1: # Elastic 453 | h = 1 454 | mu, lam = mu_0 * h, lam_0 * h 455 | 456 | U, sig, V = ti.svd(F[p]) 457 | J = 1.0 458 | for d in ti.static(range(dim)): 459 | new_sig = sig[d, d] 460 | if mat_idx[p] == 2: # Snow 461 | new_sig = ti.min(ti.max(sig[d, d], 1 - 2.5e-2), 1 + 4.5e-3) # Plasticity 462 | Jp[p] *= sig[d, d] / new_sig 463 | sig[d, d] = new_sig 464 | J *= new_sig 465 | 466 | affine = ti.Matrix.identity(float, dim) 467 | if mat_idx[p] == 0: # weakly compressible fluid 468 | stress = -dt * 4 * Ef * p_vol * (Jf[p] - 1) * inv_dx * inv_dx 469 | affine = ti.Matrix.identity(float, dim) * stress + p_mass * C[p] 470 | else: 471 | if mat_idx[p] == 2: 472 | F[p] = U @ sig @ V.transpose() 473 | stress = 2 * mu * (F[p] - U @ V.transpose()) @ F[p].transpose() + ti.Matrix.identity(float, dim) * lam * J * (J - 1) 474 | stress = (-dt * 4 * p_vol * inv_dx * inv_dx) * stress 475 | affine = stress + p_mass * C[p] 476 | 477 | for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): 478 | dpos = (offset.cast(float) - fx) * dx 479 | weight = 1.0 480 | for i in ti.static(range(dim)): 481 | weight *= w[offset[i]][i] 482 | grid_v[base + offset] += weight * (p_mass * v[p] + affine @ dpos) 483 | grid_m[base + offset] += weight * p_mass 484 | 485 | # Boundary 486 | for I in ti.grouped(grid_m): 487 | if grid_m[I] > 0: 488 | grid_v[I] /= grid_m[I] 489 | grid_v[I][1] -= dt * g 490 | for i in ti.static(range(dim)): 491 | if I[i] < bound and grid_v[I][i] < 0: 492 | grid_v[I][i] = 0 493 | if I[i] > n_grid - bound and grid_v[I][i] > 0: 494 | grid_v[I][i] = 0 495 | 496 | # G2P 497 | for p in x: 498 | 499 | base = (x[p] * inv_dx - 0.5).cast(int) 500 | fx = x[p] * inv_dx - base.cast(float) 501 | w = [0.5 * (1.5 - fx) ** 2, 0.75 - (fx - 1) ** 2, 0.5 * (fx - 0.5) ** 2] 502 | new_v = ti.Vector.zero(float, dim) 503 | new_C = ti.Matrix.zero(float, dim, dim) 504 | 505 | for offset in ti.static(ti.grouped(ti.ndrange(*neighbour))): 506 | dpos = offset.cast(float) - fx 507 | g_v = grid_v[base + offset] 508 | weight = 1.0 509 | for i in ti.static(range(dim)): 510 | weight *= w[offset[i]][i] 511 | new_v += weight * g_v 512 | new_C += 4 * inv_dx * weight * g_v.outer_product(dpos) 513 | 514 | v[p], C[p] = new_v, new_C 515 | Jf[p] *= 1 + dt * new_C.trace() 516 | x[p] += dt * v[p] 517 | 518 | if __name__ == '__main__': 519 | 520 | bpy.context.scene.frame_current = 0 521 | 522 | N = 176 523 | mciso_bunny = MCISO(N) 524 | mciso_spot = MCISO(N) 525 | mciso_armadillo = MCISO(N) 526 | voxel_bunny = Voxelizer(N) 527 | voxel_spot = Voxelizer(N) 528 | voxel_armadillo = Voxelizer(N) 529 | 530 | dim = 3 531 | neighbour = (3, ) * dim 532 | bound = 3 533 | 534 | n_grid = 128 535 | dx = 1. / n_grid 536 | inv_dx = float(n_grid) 537 | dt = 1e-4 538 | substeps = int(1 / 120 // dt) 539 | 540 | p_rho = 1 541 | p_vol = (dx * 0.5) ** dim 542 | p_mass = p_vol * p_rho 543 | g = 9.8 544 | 545 | E = 5e3 546 | nu = 0.2 547 | mu_0 = E / (2. * (1. + nu)) 548 | lam_0 = E * nu / ((1. + nu) * (1. - 2. * nu)) 549 | Ef = 400 550 | 551 | bunny_vertices = [] 552 | bunny_scale = 1.5 553 | bunny_pos = np.array([0.5, 0.2, 0.5]) 554 | 555 | spot_vertices = [] 556 | spot_scale = 0.1 557 | spot_pos = np.array([0.3, 0.5, 0.5]) 558 | 559 | armadillo_vertices = [] 560 | armadillo_scale = 0.2 561 | armadillo_pos = np.array([0.7, 0.5, 0.5]) 562 | 563 | with open("../bunny.obj", "r") as f: 564 | for line in f: 565 | if line[0] != "v": 566 | break 567 | tokens = line[:-1].split(" ") 568 | bunny_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 569 | 570 | with open("../spot.obj", "r") as f: 571 | for line in f: 572 | if line[0] != "v": 573 | continue 574 | tokens = line[:-1].split(" ") 575 | spot_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 576 | 577 | with open("../armadillo.obj", "r") as f: 578 | for line in f: 579 | if line[0] != "v": 580 | continue 581 | tokens = line[:-1].split(" ") 582 | armadillo_vertices.append(np.array([float(tokens[1]), float(tokens[2]), float(tokens[3])])) 583 | 584 | n_bunny = len(bunny_vertices) 585 | n_spot = len(spot_vertices) 586 | n_armadillo = len(armadillo_vertices) 587 | n_particles = n_bunny + n_spot + n_armadillo 588 | 589 | print(f"Bunny Particles Num: {n_bunny}") 590 | print(f"Spot Particles Num: {n_spot}") 591 | print(f"Armadillo Particles Num: {n_armadillo}") 592 | print(f"Total Particles Num: {n_particles}") 593 | 594 | x = ti.Vector.field(dim, dtype=float, shape=(n_particles,)) 595 | v = ti.Vector.field(dim, dtype=float, shape=(n_particles,)) 596 | C = ti.Matrix.field(dim, dim, dtype=float, shape=(n_particles,)) 597 | F = ti.Matrix.field(dim, dim, dtype=float, shape=(n_particles,)) 598 | mat_idx = ti.field(dtype=int, shape=(n_particles,)) 599 | mat_color = ti.Vector.field(3, dtype=float, shape=(n_particles,)) 600 | 601 | Jp = ti.field(dtype=float, shape=(n_particles,)) 602 | Jf = ti.field(dtype=float, shape=(n_particles,)) 603 | grid_v = ti.Vector.field(dim, dtype=float, shape=(n_grid,) * dim) 604 | grid_m = ti.field(dtype=float, shape=(n_grid, ) * dim) 605 | 606 | for i in range(n_particles): 607 | 608 | if i < n_bunny: 609 | x[i] = bunny_vertices[i] * bunny_scale + bunny_pos 610 | mat_idx[i] = 0 611 | mat_color[i] = [0.0, 0.6, 1.0] 612 | elif i < n_bunny + n_spot: 613 | x[i] = spot_vertices[i - n_bunny] * spot_scale + spot_pos 614 | mat_idx[i] = 1 615 | mat_color[i] = [1.0, 0.6, 0.0] 616 | else: 617 | x[i] = armadillo_vertices[i - n_bunny - n_spot] * armadillo_scale + armadillo_pos 618 | mat_idx[i] = 2 619 | mat_color[i] = [0.0, 1.0, 0.0] 620 | 621 | F[i] = [[1, 0, 0], [0, 1, 0], [0, 0, 1]] 622 | C[i] = [[0, 0, 0], [0, 0, 0], [0, 0, 0]] 623 | v[i] = [0, 0, 0] 624 | Jp[i] = 1 625 | Jf[i] = 1 626 | 627 | nb.delete_object('bunny') 628 | nb.delete_object('spot') 629 | nb.delete_object('armadillo') 630 | nb.delete_mesh('bunny') 631 | nb.delete_mesh('spot') 632 | nb.delete_mesh('armadillo') 633 | 634 | mat_fluid = bpy.data.materials.get("mat_fluid") 635 | mat_elastic = bpy.data.materials.get("mat_elastic") 636 | mat_snow = bpy.data.materials.get("mat_snow") 637 | 638 | obj_bunny = nb.new_object('bunny', nb.new_mesh('bunny')) 639 | obj_spot = nb.new_object('spot', nb.new_mesh('spot')) 640 | obj_armadillo = nb.new_object('armadillo', nb.new_mesh('armadillo')) 641 | 642 | @nb.add_animation 643 | def main(): 644 | 645 | for frame in range(300): 646 | 647 | for i in range(substeps): 648 | substep() 649 | 650 | mciso_bunny.clear() 651 | mciso_spot.clear() 652 | mciso_armadillo.clear() 653 | voxel_bunny.voxelize(mciso_bunny.m, x, 8, 0, n_bunny) 654 | voxel_spot.voxelize(mciso_spot.m, x, 8, n_bunny, n_bunny + n_spot) 655 | voxel_armadillo.voxelize(mciso_armadillo.m, x, 8, n_bunny + n_spot, n_particles) 656 | mciso_bunny.march() 657 | mciso_spot.march() 658 | mciso_armadillo.march() 659 | vert_bunny, face_bunny = mciso_bunny.get_mesh() 660 | vert_spot, face_spot = mciso_spot.get_mesh() 661 | vert_armadillo, face_armadillo = mciso_armadillo.get_mesh() 662 | 663 | nb.delete_object(f'bunny_{frame}') 664 | nb.delete_object(f'spot_{frame}') 665 | nb.delete_object(f'armadillo_{frame }') 666 | mesh_bunny = nb.new_mesh(f'bunny_{frame}', vert_bunny, [], face_bunny) 667 | mesh_spot = nb.new_mesh(f'spot_{frame}', vert_spot, [], face_spot) 668 | mesh_armadillo = nb.new_mesh(f'armadillo_{frame}', vert_armadillo, [], face_armadillo) 669 | 670 | mesh_bunny.materials.append(mat_fluid) 671 | mesh_spot.materials.append(mat_elastic) 672 | mesh_armadillo.materials.append(mat_snow) 673 | 674 | yield nb.objects_meshes_update([obj_bunny, obj_spot, obj_armadillo], [mesh_bunny, mesh_spot, mesh_armadillo]) --------------------------------------------------------------------------------