//Compute finite-volume style face-weights for fluid from nodal signed distances void FluidSim::compute_weights() { //Compute face area fractions (using marching squares cases). for(int k = 0; k < nk; ++k) for(int j = 0; j < nj; ++j) for(int i = 0; i < ni+1; ++i) { u_weights(i,j,k) = 1 - fraction_inside(nodal_solid_phi(i,j, k), nodal_solid_phi(i,j+1,k), nodal_solid_phi(i,j, k+1), nodal_solid_phi(i,j+1,k+1)); u_weights(i,j,k) = clamp(u_weights(i,j,k),0.0f,1.0f); } for(int k = 0; k < nk; ++k) for(int j = 0; j < nj+1; ++j) for(int i = 0; i < ni; ++i) { v_weights(i,j,k) = 1 - fraction_inside(nodal_solid_phi(i, j,k), nodal_solid_phi(i, j,k+1), nodal_solid_phi(i+1,j,k), nodal_solid_phi(i+1,j,k+1)); v_weights(i,j,k) = clamp(v_weights(i,j,k),0.0f,1.0f); } for(int k = 0; k < nk+1; ++k) for(int j = 0; j < nj; ++j) for(int i = 0; i < ni; ++i) { w_weights(i,j,k) = 1 - fraction_inside(nodal_solid_phi(i, j, k), nodal_solid_phi(i, j+1,k), nodal_solid_phi(i+1,j, k), nodal_solid_phi(i+1,j+1,k)); w_weights(i,j,k) = clamp(w_weights(i,j,k),0.0f,1.0f); } }
//Compute finite-volume style face-weights for fluid from nodal signed distances void FluidSim::compute_weights() { for(int j = 0; j < u_weights.nj; ++j) for(int i = 0; i < u_weights.ni; ++i) { u_weights(i,j) = 1 - fraction_inside(nodal_solid_phi(i,j+1), nodal_solid_phi(i,j)); } for(int j = 0; j < v_weights.nj; ++j) for(int i = 0; i < v_weights.ni; ++i) { v_weights(i,j) = 1 - fraction_inside(nodal_solid_phi(i+1,j), nodal_solid_phi(i,j)); } }
//On a signed distance field, compute the fraction inside the (negative) isosurface //along the 1D line segment joining phi_left and phi_right sample points. //Except now there are two level sets, and we want the fraction that is the union //of their interiors float fraction_inside_either(float phi_left0, float phi_right0, float phi_left1, float phi_right1) { if(phi_left0 <= 0) { if(phi_right0 <= 0) { //entirely inside solid0 [-/-][?] return 1; } else { //phi_right0 > 0 if(phi_left1 <= 0) { if(phi_right1 <= 0) { //entirely inside solid1 -> [-/+][-/-] return 1; } else {//both left sides are inside, neither right side [-/+][-/+] return max( fraction_inside(phi_left0, phi_right0), fraction_inside(phi_left1, phi_right1) ); } } else { //phi_left1 > 0 if(phi_right1 <= 0) { //opposite sides are interior [-/+][+/-] float frac0 = fraction_inside(phi_left0, phi_right0); float frac1 = fraction_inside(phi_left1, phi_right1); float total = frac0+frac1; if(total <= 1) return total; else return 1; } else {//phi_right1 > 0 //phi1 plays no role, both outside [-/+][+/+] return fraction_inside(phi_left0, phi_right0); } } } } else { if(phi_right0 <= 0) { if(phi_left1 <= 0) { if(phi_right1 <= 0) { //entirely inside solid1[+/-][-/-] return 1; } else { //coming in from opposing sides [+/-][-/+] float frac0 = fraction_inside(phi_left0, phi_right0); float frac1 = fraction_inside(phi_left1, phi_right1); float total = frac0+frac1; if(total <= 1) return total; else return 1; } } else { //phi_left1 > 0 if(phi_right1 <= 0) { //coming from the same side, take the larger one [+/-][+/-] return max( fraction_inside(phi_left0, phi_right0), fraction_inside(phi_left1, phi_right1) ); } else { //phi_right > 0 //Only have to worry about phi_0 [+/-][+/+] return fraction_inside(phi_left0, phi_right0); } } } else { //Only have to worry about phi_1 [+/+][?] return fraction_inside(phi_left1, phi_right1); } } }
//An implementation of the variational pressure projection solve for static geometry void FluidSim::solve_pressure(float dt) { int ni = v.ni; int nj = u.nj; int nk = u.nk; int system_size = ni*nj*nk; if(rhs.size() != system_size) { rhs.resize(system_size); pressure.resize(system_size); matrix.resize(system_size); } matrix.zero(); rhs.assign(rhs.size(), 0); pressure.assign(pressure.size(), 0); //Build the linear system for pressure for(int k = 1; k < nk-1; ++k) { for(int j = 1; j < nj-1; ++j) { for(int i = 1; i < ni-1; ++i) { int index = i + ni*j + ni*nj*k; rhs[index] = 0; pressure[index] = 0; float centre_phi = liquid_phi(i,j,k); if(centre_phi < 0) { //right neighbour float term = u_weights(i+1,j,k) * dt / sqr(dx); float right_phi = liquid_phi(i+1,j,k); if(right_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + 1, -term); } else { float theta = fraction_inside(centre_phi, right_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= u_weights(i+1,j,k)*u(i+1,j,k) / dx; //left neighbour term = u_weights(i,j,k) * dt / sqr(dx); float left_phi = liquid_phi(i-1,j,k); if(left_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - 1, -term); } else { float theta = fraction_inside(centre_phi, left_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += u_weights(i,j,k)*u(i,j,k) / dx; //top neighbour term = v_weights(i,j+1,k) * dt / sqr(dx); float top_phi = liquid_phi(i,j+1,k); if(top_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + ni, -term); } else { float theta = fraction_inside(centre_phi, top_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= v_weights(i,j+1,k)*v(i,j+1,k) / dx; //bottom neighbour term = v_weights(i,j,k) * dt / sqr(dx); float bot_phi = liquid_phi(i,j-1,k); if(bot_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - ni, -term); } else { float theta = fraction_inside(centre_phi, bot_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += v_weights(i,j,k)*v(i,j,k) / dx; //far neighbour term = w_weights(i,j,k+1) * dt / sqr(dx); float far_phi = liquid_phi(i,j,k+1); if(far_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + ni*nj, -term); } else { float theta = fraction_inside(centre_phi, far_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= w_weights(i,j,k+1)*w(i,j,k+1) / dx; //near neighbour term = w_weights(i,j,k) * dt / sqr(dx); float near_phi = liquid_phi(i,j,k-1); if(near_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - ni*nj, -term); } else { float theta = fraction_inside(centre_phi, near_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += w_weights(i,j,k)*w(i,j,k) / dx; /* //far neighbour term = w_weights(i,j,k+1) * dt / sqr(dx); float far_phi = liquid_phi(i,j,k+1); if(far_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + ni*nj, -term); } else { float theta = fraction_inside(centre_phi, far_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= w_weights(i,j,k+1)*w(i,j,k+1) / dx; //near neighbour term = w_weights(i,j,k) * dt / sqr(dx); float near_phi = liquid_phi(i,j,k-1); if(near_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - ni*nj, -term); } else { float theta = fraction_inside(centre_phi, near_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += w_weights(i,j,k)*w(i,j,k) / dx; */ } } } } //Solve the system using Robert Bridson's incomplete Cholesky PCG solver double tolerance; int iterations; solver.set_solver_parameters(1e-18, 1000); bool success = solver.solve(matrix, rhs, pressure, tolerance, iterations); //printf("Solver took %d iterations and had residual %e\n", iterations, tolerance); if(!success) { printf("WARNING: Pressure solve failed!************************************************\n"); } //Apply the velocity update u_valid.assign(0); for(int k = 0; k < u.nk; ++k) for(int j = 0; j < u.nj; ++j) for(int i = 1; i < u.ni-1; ++i) { int index = i + j*ni + k*ni*nj; if(u_weights(i,j,k) > 0 && (liquid_phi(i,j,k) < 0 || liquid_phi(i-1,j,k) < 0)) { float theta = 1; if(liquid_phi(i,j,k) >= 0 || liquid_phi(i-1,j,k) >= 0) theta = fraction_inside(liquid_phi(i-1,j,k), liquid_phi(i,j,k)); if(theta < 0.01f) theta = 0.01f; u(i,j,k) -= dt * (float)(pressure[index] - pressure[index-1]) / dx / theta; u_valid(i,j,k) = 1; } } v_valid.assign(0); for(int k = 0; k < v.nk; ++k) for(int j = 1; j < v.nj-1; ++j) for(int i = 0; i < v.ni; ++i) { int index = i + j*ni + k*ni*nj; if(v_weights(i,j,k) > 0 && (liquid_phi(i,j,k) < 0 || liquid_phi(i,j-1,k) < 0)) { float theta = 1; if(liquid_phi(i,j,k) >= 0 || liquid_phi(i,j-1,k) >= 0) theta = fraction_inside(liquid_phi(i,j-1,k), liquid_phi(i,j,k)); if(theta < 0.01f) theta = 0.01f; v(i,j,k) -= dt * (float)(pressure[index] - pressure[index-ni]) / dx / theta; v_valid(i,j,k) = 1; } } w_valid.assign(0); for(int k = 1; k < w.nk-1; ++k) for(int j = 0; j < w.nj; ++j) for(int i = 0; i < w.ni; ++i) { int index = i + j*ni + k*ni*nj; if(w_weights(i,j,k) > 0 && (liquid_phi(i,j,k) < 0 || liquid_phi(i,j,k-1) < 0)) { float theta = 1; if(liquid_phi(i,j,k) >= 0 || liquid_phi(i,j,k-1) >= 0) theta = fraction_inside(liquid_phi(i,j,k-1), liquid_phi(i,j,k)); if(theta < 0.01f) theta = 0.01f; w(i,j,k) -= dt * (float)(pressure[index] - pressure[index-ni*nj]) / dx / theta; w_valid(i,j,k) = 1; } } for(unsigned int i = 0; i < u_valid.a.size(); ++i) if(u_valid.a[i] == 0) u.a[i] = 0; for(unsigned int i = 0; i < v_valid.a.size(); ++i) if(v_valid.a[i] == 0) v.a[i] = 0; for(unsigned int i = 0; i < w_valid.a.size(); ++i) if(w_valid.a[i] == 0) w.a[i] = 0; }
//An implementation of the variational pressure projection solve for static geometry void FluidSim::solve_pressure(float dt) { //This linear system could be simplified, but I've left it as is for clarity //and consistency with the standard naive discretization int ni = v.ni; int nj = u.nj; int system_size = ni*nj; if(rhs.size() != system_size) { rhs.resize(system_size); pressure.resize(system_size); matrix.resize(system_size); } matrix.zero(); //Build the linear system for pressure for(int j = 1; j < nj-1; ++j) { for(int i = 1; i < ni-1; ++i) { int index = i + ni*j; rhs[index] = 0; pressure[index] = 0; float centre_phi = liquid_phi(i,j); if(centre_phi < 0) { //right neighbour float term = u_weights(i+1,j) * dt / sqr(dx); float right_phi = liquid_phi(i+1,j); if(right_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + 1, -term); } else { float theta = fraction_inside(centre_phi, right_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= u_weights(i+1,j)*u(i+1,j) / dx; //left neighbour term = u_weights(i,j) * dt / sqr(dx); float left_phi = liquid_phi(i-1,j); if(left_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - 1, -term); } else { float theta = fraction_inside(centre_phi, left_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += u_weights(i,j)*u(i,j) / dx; //top neighbour term = v_weights(i,j+1) * dt / sqr(dx); float top_phi = liquid_phi(i,j+1); if(top_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index + ni, -term); } else { float theta = fraction_inside(centre_phi, top_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] -= v_weights(i,j+1)*v(i,j+1) / dx; //bottom neighbour term = v_weights(i,j) * dt / sqr(dx); float bot_phi = liquid_phi(i,j-1); if(bot_phi < 0) { matrix.add_to_element(index, index, term); matrix.add_to_element(index, index - ni, -term); } else { float theta = fraction_inside(centre_phi, bot_phi); if(theta < 0.01f) theta = 0.01f; matrix.add_to_element(index, index, term/theta); } rhs[index] += v_weights(i,j)*v(i,j) / dx; } } } //Solve the system using Robert Bridson's incomplete Cholesky PCG solver double tolerance; int iterations; bool success = solver.solve(matrix, rhs, pressure, tolerance, iterations); if(!success) { printf("WARNING: Pressure solve failed!************************************************\n"); } //Apply the velocity update u_valid.assign(0); for(int j = 0; j < u.nj; ++j) for(int i = 1; i < u.ni-1; ++i) { int index = i + j*ni; if(u_weights(i,j) > 0 && (liquid_phi(i,j) < 0 || liquid_phi(i-1,j) < 0)) { float theta = 1; if(liquid_phi(i,j) >= 0 || liquid_phi(i-1,j) >= 0) theta = fraction_inside(liquid_phi(i-1,j), liquid_phi(i,j)); if(theta < 0.01f) theta = 0.01f; u(i,j) -= dt * (float)(pressure[index] - pressure[index-1]) / dx / theta; u_valid(i,j) = 1; } else u(i,j) = 0; } v_valid.assign(0); for(int j = 1; j < v.nj-1; ++j) for(int i = 0; i < v.ni; ++i) { int index = i + j*ni; if(v_weights(i,j) > 0 && (liquid_phi(i,j) < 0 || liquid_phi(i,j-1) < 0)) { float theta = 1; if(liquid_phi(i,j) >= 0 || liquid_phi(i,j-1) >= 0) theta = fraction_inside(liquid_phi(i,j-1), liquid_phi(i,j)); if(theta < 0.01f) theta = 0.01f; v(i,j) -= dt * (float)(pressure[index] - pressure[index-ni]) / dx / theta; v_valid(i,j) = 1; } else v(i,j) = 0; } }