static void computeLevelset(GModel *gm, cartesianBox<double> &box) { // tolerance for desambiguation const double tol = box.getLC() * 1.e-12; std::vector<SPoint3> nodes; std::vector<int> indices; for (cartesianBox<double>::valIter it = box.nodalValuesBegin(); it != box.nodalValuesEnd(); ++it){ nodes.push_back(box.getNodeCoordinates(it->first)); indices.push_back(it->first); } Msg::Info(" %d nodes in the grid at level %d", (int)nodes.size(), box.getLevel()); std::vector<double> dist, localdist; std::vector<SPoint3> dummy; for (GModel::fiter fit = gm->firstFace(); fit != gm->lastFace(); fit++){ for (int i = 0; i < (*fit)->stl_triangles.size(); i += 3){ int i1 = (*fit)->stl_triangles[i]; int i2 = (*fit)->stl_triangles[i + 1]; int i3 = (*fit)->stl_triangles[i + 2]; GPoint p1 = (*fit)->point((*fit)->stl_vertices[i1]); GPoint p2 = (*fit)->point((*fit)->stl_vertices[i2]); GPoint p3 = (*fit)->point((*fit)->stl_vertices[i3]); SPoint2 p = ((*fit)->stl_vertices[i1] + (*fit)->stl_vertices[i2] + (*fit)->stl_vertices[i3]) * 0.33333333; SVector3 N = (*fit)->normal(p); SPoint3 P1(p1.x(), p1.y(), p1.z()); SPoint3 P2(p2.x(), p2.y(), p2.z()); SPoint3 P3(p3.x(), p3.y(), p3.z()); SVector3 NN(crossprod(P2 - P1, P3 - P1)); if (dot(NN, N) > 0) signedDistancesPointsTriangle(localdist, dummy, nodes, P1, P2, P3); else signedDistancesPointsTriangle(localdist, dummy, nodes, P2, P1, P3); if(dist.empty()) dist = localdist; else{ for (unsigned int j = 0; j < localdist.size(); j++){ // FIXME: if there is an ambiguity assume we are inside (to // avoid holes in the structure). This is definitely just a // hack, as it could create pockets of matter outside the // structure... if(dist[j] * localdist[j] < 0 && fabs(fabs(dist[j]) - fabs(localdist[j])) < tol){ dist[j] = std::max(dist[j], localdist[j]); } else{ dist[j] = (fabs(dist[j]) < fabs(localdist[j])) ? dist[j] : localdist[j]; } } } } } for (unsigned int j = 0; j < dist.size(); j++) box.setNodalValue(indices[j], dist[j]); if(box.getChildBox()) computeLevelset(gm, *box.getChildBox()); }
int main() { int dimension = 3; auto resolution = Manta::Vec3i(128); if (dimension == 2) resolution.z = 1; auto main_solver = Manta::FluidSolver(resolution, dimension); main_solver.setTimeStep(0.5f); Real minParticles = pow(2, dimension); Real radiusFactor = 1.0f; // solver grids auto flags = Manta::FlagGrid(&main_solver); auto phi = Manta::LevelsetGrid(&main_solver); auto vel = Manta::MACGrid(&main_solver); auto vel_old = Manta::MACGrid(&main_solver); auto pressure = Manta::Grid<Real>(&main_solver); auto tmp_vec3 = Manta::Grid<Manta::Vec3>(&main_solver); auto tstGrid = Manta::Grid<Real>(&main_solver); // particles auto pp = Manta::BasicParticleSystem(&main_solver); auto pVel = Manta::PdataVec3(&pp); auto pTest = Manta::PdataReal(&pp); auto mesh = Manta::Mesh(&main_solver); // acceleration data for particle nbs auto pindex = Manta::ParticleIndexSystem(&main_solver); auto gpi = Manta::Grid<int>(&main_solver); int setup = 0; int boundaryWidth = 1; flags.initDomain(boundaryWidth); auto fluidVel = Manta::Sphere(&main_solver, Manta::Vec3(0.0f), 1.0f); Manta::Vec3 fluidSetVel; if (setup == 0) { // breakin dam auto fluidbox = Manta::Box(&main_solver, Manta::Vec3::Invalid, Manta::Vec3(0.0f, 0.0f, 0.0f), Manta::Vec3(resolution.x * 0.4f, resolution.y * 0.6f, resolution.z * 0.1f)); phi.copyFrom(fluidbox.computeLevelset()); } else if (setup == 1) { // drop into box auto fluidbasin = Manta::Box(&main_solver, Manta::Vec3::Invalid, Manta::Vec3(0.0f, 0.0f, 0.0f), Manta::Vec3(resolution.x * 1.0f, resolution.y * 0.1f, resolution.z * 1.0f)); auto dropCenter = Manta::Vec3(0.5f, 0.3f, 0.5f); Real dropRadius = 0.1f; auto fluidDrop = Manta::Sphere(&main_solver, Manta::Vec3(resolution.x * dropCenter.x, resolution.y * dropCenter.y, resolution.z * dropCenter.z), resolution.x * dropRadius); fluidVel = Manta::Sphere(&main_solver, Manta::Vec3(resolution.x * dropCenter.x, resolution.y * dropCenter.y, resolution.z * dropCenter.z), resolution.x * (dropRadius + 0.05f)); fluidSetVel = Manta::Vec3(0.0f, -1.0f, 0.0f); phi.copyFrom(fluidbasin.computeLevelset()); phi.join(fluidDrop.computeLevelset()); } flags.updateFromLevelset(phi); Manta::sampleLevelsetWithParticles(phi, flags, pp, 2, 0.05f); if (setup == 1) { fluidVel.applyToGrid(&vel, fluidSetVel); Manta::mapGridToPartsVec3(vel, pp, pVel); } Manta::testInitGridWithPos(tstGrid); pTest.setConst(0.1f); for (int t = 0; t < 250; t++) { // FLIP pp.advectInGrid(flags, vel, Manta::IntegrationMode::IntRK4, false); // make sure we have velocities throught liquid region Manta::mapParticlesToMAC(flags, vel, vel_old, pp, pVel, &tmp_vec3); Manta::extrapolateMACFromWeight(vel, tmp_vec3, 2); Manta::markFluidCells(pp, flags); // create approximate surface level set, resample particles Manta::gridParticleIndex(pp, pindex, flags, gpi); Manta::unionParticleLevelset(pp, pindex, flags, gpi, phi, radiusFactor); Manta::resetOutflow(flags, nullptr, &pp, nullptr, &gpi,&pindex); // extend levelset somewhat, needed by particle resampling in adjustNumber Manta::extrapolateLsSimple(phi, 4, true); // forces and pressure solve Manta::addGravity(flags, vel, Manta::Vec3(0.0f, -0.001f, 0.0f)); Manta::setWallBcs(flags, vel); Manta::solvePressure(vel, pressure, flags, &phi); Manta::setWallBcs(flags, vel); // set source grids for resampling, used in adjustNumber! pVel.setSource(&vel, true); pTest.setSource(&tstGrid); Manta::adjustNumber(pp, vel, flags, 1 * minParticles, 2 * minParticles, phi, radiusFactor); // make sure we have proper velocities Manta::extrapolateMACSimple(flags, vel); Manta::flipVelocityUpdate(flags, vel, vel_old, pp, pVel, 0.97f); if (dimension == 3) phi.createMesh(mesh); main_solver.step(); // generate data directly and for flip03_gen.py surface generation scene std::stringstream filename1, filename2; filename1 << "simulation data\\flip02_surface\\fluidsurface_final_" << std::setw(4) << std::setfill('0') << t << ".bobj.gz"; filename2 << "simulation data\\flip02_surface\\flipParts_" << std::setw(4) << std::setfill('0') << t << ".uni"; mesh.save(filename1.str()); pp.save(filename2.str()); } }
int main(int argc,char *argv[]) { if(argc < 6){ printf("Usage: %s file lx ly lz rmax [levels=1] [refcs=1]\n", argv[0]); printf("where\n"); printf(" 'file' contains a CAD model\n"); printf(" 'lx', 'ly' and 'lz' are the sizes of the elements along the" " x-, y- and z-axis at the coarsest level\n"); printf(" 'rmax' is the radius of the largest sphere that can be inscribed" " in the structure\n"); printf(" 'levels' sets the number of levels in the grid\n"); printf(" 'refcs' selects if curved surfaces should be refined\n"); return -1; } GmshInitialize(); GmshSetOption("General", "Terminal", 1.); GmshMergeFile(argv[1]); double lx = atof(argv[2]), ly = atof(argv[3]), lz = atof(argv[4]); double rmax = atof(argv[5]); int levels = (argc > 6) ? atof(argv[6]) : 1; int refineCurvedSurfaces = (argc > 7) ? atof(argv[7]) : 1; // minimum distance between points in the cloud at the coarsest // level double sampling = std::min(rmax, std::min(lx, std::min(ly, lz))); // radius of the "tube" created around parts to refine at the // coarsest level double rtube = std::max(lx, std::max(ly, lz)) * 2.; GModel *gm = GModel::current(); std::vector<SPoint3> points; Msg::Info("Filling coarse point cloud on surfaces"); for (GModel::fiter fit = gm->firstFace(); fit != gm->lastFace(); fit++) (*fit)->fillPointCloud(sampling, &points); Msg::Info(" %d points in the surface cloud", (int)points.size()); std::vector<SPoint3> refinePoints; if(levels > 1){ double s = sampling / pow(2., levels - 1); Msg::Info("Filling refined point cloud on curves and curved surfaces"); for (GModel::eiter eit = gm->firstEdge(); eit != gm->lastEdge(); eit++) fillPointCloud(*eit, s, refinePoints); // FIXME: refine this by computing e.g. "mean" curvature if(refineCurvedSurfaces){ for (GModel::fiter fit = gm->firstFace(); fit != gm->lastFace(); fit++) if((*fit)->geomType() != GEntity::Plane) (*fit)->fillPointCloud(2 * s, &refinePoints); } Msg::Info(" %d points in the refined cloud", (int)refinePoints.size()); } SBoundingBox3d bb; for(unsigned int i = 0; i < points.size(); i++) bb += points[i]; for(unsigned int i = 0; i < refinePoints.size(); i++) bb += refinePoints[i]; bb.scale(1.21, 1.21, 1.21); SVector3 range = bb.max() - bb.min(); int NX = range.x() / lx; int NY = range.y() / ly; int NZ = range.z() / lz; if(NX < 2) NX = 2; if(NY < 2) NY = 2; if(NZ < 2) NZ = 2; Msg::Info(" bounding box min: %g %g %g -- max: %g %g %g", bb.min().x(), bb.min().y(), bb.min().z(), bb.max().x(), bb.max().y(), bb.max().z()); Msg::Info(" Nx=%d Ny=%d Nz=%d", NX, NY, NZ); cartesianBox<double> box(bb.min().x(), bb.min().y(), bb.min().z(), SVector3(range.x(), 0, 0), SVector3(0, range.y(), 0), SVector3(0, 0, range.z()), NX, NY, NZ, levels); Msg::Info("Inserting active cells in the cartesian grid"); Msg::Info(" level %d", box.getLevel()); for (unsigned int i = 0; i < points.size(); i++) insertActiveCells(points[i].x(), points[i].y(), points[i].z(), rmax, box); cartesianBox<double> *parent = &box, *child; while((child = parent->getChildBox())){ Msg::Info(" level %d", child->getLevel()); for(unsigned int i = 0; i < refinePoints.size(); i++) insertActiveCells(refinePoints[i].x(), refinePoints[i].y(), refinePoints[i].z(), rtube / pow(2., (levels - child->getLevel())), *child); parent = child; } // remove child cells that do not entirely fill parent cell or for // which there is no parent neighbor; then remove parent cells that // have children Msg::Info("Removing cells to match X-FEM mesh topology constraints"); removeBadChildCells(&box); removeParentCellsWithChildren(&box); // we generate duplicate nodes at this point so we can easily access // cell values at each level; we will clean up by renumbering after // filtering Msg::Info("Initializing nodal values in the cartesian grid"); box.createNodalValues(); Msg::Info("Computing levelset on the cartesian grid"); computeLevelset(gm, box); Msg::Info("Removing cells outside the structure"); removeOutsideCells(&box); Msg::Info("Renumbering mesh vertices across levels"); box.renumberNodes(); bool decomposeInSimplex = false; box.writeMSH("yeah.msh", decomposeInSimplex); Msg::Info("Done!"); GmshFinalize(); }