static void cartesian_grid_triangulate (CartesianGrid * g, GtsSurface * s) { gint i, j; GtsVertex *** v; g_return_if_fail (g != NULL); g_return_if_fail (s != NULL); v = g->vertices; for (i = 0; i < g->nx - 1; i++) for (j = 0; j < g->ny - 1; j++) if (v[i][j]) { if (v[i][j+1]) { if (v[i+1][j+1]) { GtsEdge * e1 = new_edge (v[i][j+1], v[i][j]); GtsEdge * e2 = gts_edge_new (GTS_EDGE_CLASS (gts_constraint_class ()), v[i][j], v[i+1][j+1]); GtsEdge * e3 = new_edge (v[i+1][j+1], v[i][j+1]); gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3)); if (v[i+1][j]) { e1 = new_edge (v[i+1][j], v[i+1][j+1]); e3 = new_edge (v[i][j], v[i+1][j]); gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3)); } } else if (v[i+1][j]) { GtsEdge * e1 = new_edge (v[i][j+1], v[i][j]); GtsEdge * e2 = new_edge (v[i][j], v[i+1][j]); GtsEdge * e3 = gts_edge_new (GTS_EDGE_CLASS (gts_constraint_class ()), v[i+1][j], v[i][j+1]); gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3)); } } else if (v[i+1][j] && v[i+1][j+1]) { GtsEdge * e1 = new_edge (v[i][j], v[i+1][j]); GtsEdge * e2 = new_edge (v[i+1][j], v[i+1][j+1]); GtsEdge * e3 = gts_edge_new (GTS_EDGE_CLASS (gts_constraint_class ()), v[i+1][j+1], v[i][j]); gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3)); } } else if (v[i][j+1] && v[i+1][j+1] && v[i+1][j]) { GtsEdge * e1 = new_edge (v[i+1][j], v[i+1][j+1]); GtsEdge * e2 = new_edge (v[i+1][j+1], v[i][j+1]); GtsEdge * e3 = gts_edge_new (GTS_EDGE_CLASS (gts_constraint_class ()), v[i][j+1], v[i+1][j]); gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3)); } }
void add_triangle(k3d::uint_t Vertices[3], k3d::uint_t Edges[3]) { GtsVertex* const vertex1 = get_vertex(Vertices[0]); GtsVertex* const vertex2 = get_vertex(Vertices[1]); GtsVertex* const vertex3 = get_vertex(Vertices[2]); GtsEdge* const edge1 = gts_edge_new(gts_edge_class(), vertex1, vertex2); GtsEdge* const edge2 = gts_edge_new(gts_edge_class(), vertex2, vertex3); GtsEdge* const edge3 = gts_edge_new(gts_edge_class(), vertex3, vertex1); GtsFace* const face = gts_face_new(gts_face_class(), edge1, edge2, edge3); gts_surface_add_face(gts_surface, face); }
static GtsEdge * new_edge (GtsVertex * v1, GtsVertex * v2) { GtsSegment * s = gts_vertices_are_connected (v1, v2); return s == NULL ? gts_edge_new (GTS_EDGE_CLASS (gts_constraint_class ()), v1, v2) : GTS_EDGE (s); }
void ofxGtsSurface::addFace(ofPoint &p1, ofPoint &p2, ofPoint &p3) { if(!loaded) { setup(); } GtsVertex *v1 = gts_vertex_new(surface->vertex_class, p1.x, p1.y, p1.z); GtsVertex *v2 = gts_vertex_new(surface->vertex_class, p2.x, p2.y, p2.z); GtsVertex *v3 = gts_vertex_new(surface->vertex_class, p3.x, p3.y, p3.z); GtsEdge *e1 = gts_edge_new(surface->edge_class, v1, v2); GtsEdge *e2 = gts_edge_new(surface->edge_class, v2, v3); GtsEdge *e3 = gts_edge_new(surface->edge_class, v3, v1); gts_surface_add_face(surface, gts_face_new(surface->face_class, e1, e2, e3)); }
/** * gts_edge_swap: * @e: a #GtsEdge. * @s: a #GtsSurface. * * Performs an "edge swap" on the two triangles sharing @e and * belonging to @s. */ void gts_edge_swap (GtsEdge * e, GtsSurface * s) { GtsTriangle * t1 = NULL, * t2 = NULL, * t; GtsFace * f; GSList * i; GtsVertex * v1, * v2, * v3, * v4, * v5, * v6; GtsEdge * e1, * e2, * e3, * e4; GtsSegment * v3v6; g_return_if_fail (e != NULL); g_return_if_fail (s != NULL); i = e->triangles; while (i) { if (GTS_IS_FACE (i->data) && gts_face_has_parent_surface (i->data, s)) { if (!t1) t1 = i->data; else if (!t2) t2 = i->data; else g_return_if_fail (gts_edge_face_number (e, s) == 2); } i = i->next; } g_assert (t1 && t2); gts_triangle_vertices_edges (t1, e, &v1, &v2, &v3, &e, &e1, &e2); gts_triangle_vertices_edges (t2, e, &v4, &v5, &v6, &e, &e3, &e4); g_assert (v2 == v4 && v1 == v5); v3v6 = gts_vertices_are_connected (v3, v6); if (!GTS_IS_EDGE (v3v6)) v3v6 = GTS_SEGMENT (gts_edge_new (s->edge_class, v3, v6)); f = gts_face_new (s->face_class, e1, GTS_EDGE (v3v6), e4); if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) && GTS_IS_FACE (t)) { gts_object_destroy (GTS_OBJECT (f)); f = GTS_FACE (t); } gts_surface_add_face (s, f); f = gts_face_new (s->face_class, GTS_EDGE (v3v6), e2, e3); if ((t = gts_triangle_is_duplicate (GTS_TRIANGLE (f))) && GTS_IS_FACE (t)) { gts_object_destroy (GTS_OBJECT (f)); f = GTS_FACE (t); } gts_surface_add_face (s, f); gts_surface_remove_face (s, GTS_FACE (t1)); gts_surface_remove_face (s, GTS_FACE (t2)); }
/** * gts_triangle_enclosing: * @klass: the class of the new triangle. * @points: a list of #GtsPoint. * @scale: a scaling factor (must be larger than one). * * Builds a new triangle (including new vertices and edges) enclosing * the plane projection of all the points in @points. This triangle is * equilateral and encloses a rectangle defined by the maximum and * minimum x and y coordinates of the points. @scale is an homothetic * scaling factor. If equal to one, the triangle encloses exactly the * enclosing rectangle. * * Returns: a new #GtsTriangle. */ GtsTriangle * gts_triangle_enclosing (GtsTriangleClass * klass, GSList * points, gdouble scale) { gdouble xmax, xmin, ymax, ymin; gdouble xo, yo, r; GtsVertex * v1, * v2, * v3; GtsEdge * e1, * e2, * e3; if (points == NULL) return NULL; xmax = xmin = GTS_POINT (points->data)->x; ymax = ymin = GTS_POINT (points->data)->y; points = points->next; while (points) { GtsPoint * p = points->data; if (p->x > xmax) xmax = p->x; else if (p->x < xmin) xmin = p->x; if (p->y > ymax) ymax = p->y; else if (p->y < ymin) ymin = p->y; points = points->next; } xo = (xmax + xmin)/2.; yo = (ymax + ymin)/2.; r = scale*sqrt((xmax - xo)*(xmax - xo) + (ymax - yo)*(ymax - yo)); if (r == 0.0) r = scale; v1 = gts_vertex_new (gts_vertex_class (), xo + r*SQRT3, yo - r, 0.0); v2 = gts_vertex_new (gts_vertex_class (), xo, yo + 2.*r, 0.0); v3 = gts_vertex_new (gts_vertex_class (), xo - r*SQRT3, yo - r, 0.0); e1 = gts_edge_new (gts_edge_class (), v1, v2); e2 = gts_edge_new (gts_edge_class (), v2, v3); e3 = gts_edge_new (gts_edge_class (), v3, v1); return gts_triangle_new (gts_triangle_class (), e1, e2, e3); }
/* the file format is the classic GTS file format but only the vertex and * edge sections are read. */ static guint read_list (GPtrArray * vertices, GtsFifo * constraints, GtsEdgeClass * edge_class, FILE * fptr) { guint nv, ne, nt, i; guint line = 1; g_return_val_if_fail (vertices != NULL, 1); g_return_val_if_fail (constraints != NULL, 1); g_return_val_if_fail (edge_class != NULL, 1); g_return_val_if_fail (fptr != NULL, 1); if (fscanf (fptr, "%u %u %u", &nv, &ne, &nt) != 3) return line; line++; g_ptr_array_set_size (vertices, nv); i = 1; while (i <= nv) { gdouble x, y, z; if (fscanf (fptr, "%lf %lf %lf", &x, &y, &z) != 3) return line; line++; g_ptr_array_index (vertices, i++ - 1) = gts_vertex_new (gts_vertex_class (), x, y, z); } i = 1; while (i <= ne) { guint iv1, iv2; if (fscanf (fptr, "%u %u", &iv1, &iv2) != 2 || iv1 <= 0 || iv1 > nv || iv2 <= 0 || iv2 > nv) return line; line++; gts_fifo_push (constraints, gts_edge_new (edge_class, g_ptr_array_index (vertices, iv1 - 1), g_ptr_array_index (vertices, iv2 - 1))); i++; } return 0; }
static void edge_swap (GtsEdge * e, GtsSurface * s, GtsEHeap * heap) { GSList * i; GtsTriangle * t1 = NULL, * t2 = NULL; GtsVertex * v1, * v2, * v3, * v4; GtsEdge * e1, * e2, * e3, * e4; i = e->triangles; while (i) { if (GTS_IS_FACE (i->data)) { if (!t1) t1 = i->data; else if (!t2) t2 = i->data; else g_assert_not_reached (); } i = i->next; } g_assert (t1 && t2); gts_triangle_vertices_edges (t1, e, &v1, &v2, &v3, &e, &e3, &e4); gts_triangle_vertices_edges (t2, e, &v2, &v1, &v4, &e, &e1, &e2); gts_object_destroy (GTS_OBJECT (e)); e = gts_edge_new (s->edge_class, v3, v4); gts_surface_add_face (s, gts_face_new (s->face_class, e, e4, e1)); gts_surface_add_face (s, gts_face_new (s->face_class, e, e2, e3)); HEAP_INSERT_EDGE (heap, e); HEAP_REMOVE_EDGE (heap, e1); HEAP_INSERT_EDGE (heap, e1); HEAP_REMOVE_EDGE (heap, e2); HEAP_INSERT_EDGE (heap, e2); HEAP_REMOVE_EDGE (heap, e3); HEAP_INSERT_EDGE (heap, e3); HEAP_REMOVE_EDGE (heap, e4); HEAP_INSERT_EDGE (heap, e4); }
static GtsSurface * cartesian_grid_triangulate_holes (CartesianGrid * grid, GtsSurface * s) { GtsVertex * v1, * v2, * v3, * v4; GtsEdge * e1, * e2, * e3, * e4, * e5; gdouble w, h; GtsSurface * box; GSList * constraints = NULL, * vertices = NULL, * i; gpointer data[2]; g_return_val_if_fail (grid != NULL, NULL); g_return_val_if_fail (s != NULL, NULL); /* build enclosing box */ w = grid->xmax - grid->xmin; h = grid->ymax - grid->ymin; v1 = gts_vertex_new (s->vertex_class, grid->xmin - w, grid->ymin - h, 0.); v2 = gts_vertex_new (s->vertex_class, grid->xmax + w, grid->ymin - h, 0.); v3 = gts_vertex_new (s->vertex_class, grid->xmax + w, grid->ymax + h, 0.); v4 = gts_vertex_new (s->vertex_class, grid->xmin - w, grid->ymax + h, 0.); e1 = gts_edge_new (s->edge_class, v1, v2); e2 = gts_edge_new (s->edge_class, v2, v3); e3 = gts_edge_new (s->edge_class, v3, v4); e4 = gts_edge_new (s->edge_class, v4, v1); e5 = gts_edge_new (s->edge_class, v1, v3); box = gts_surface_new (GTS_SURFACE_CLASS (GTS_OBJECT (s)->klass), s->face_class, s->edge_class, s->vertex_class); gts_surface_add_face (box, gts_face_new (s->face_class, e1, e2, e5)); gts_surface_add_face (box, gts_face_new (s->face_class, e3, e4, e5)); /* build vertex and constraint list from the boundaries of the input surface s */ data[0] = s; data[1] = &constraints; gts_surface_foreach_edge (s, (GtsFunc) build_constraint_list, data); vertices = gts_vertices_from_segments (constraints); /* triangulate holes */ i = vertices; while (i) { g_assert (!gts_delaunay_add_vertex (box, i->data, NULL)); i = i->next; } g_slist_free (vertices); i = constraints; while (i) { g_assert (!gts_delaunay_add_constraint (box, i->data)); i = i->next; } /* destroy corners of the enclosing box */ gts_allow_floating_vertices = TRUE; gts_object_destroy (GTS_OBJECT (v1)); gts_object_destroy (GTS_OBJECT (v2)); gts_object_destroy (GTS_OBJECT (v3)); gts_object_destroy (GTS_OBJECT (v4)); gts_allow_floating_vertices = FALSE; /* remove parts of the mesh which are not holes */ i = constraints; while (i) { edge_mark_as_hole (i->data, box); i = i->next; } g_slist_free (constraints); /* remove marked and duplicate faces */ gts_surface_foreach_face_remove (box, (GtsFunc) face_is_marked, NULL); /* box now contains only the triangulated holes. */ return box; }
static void gts_constraint_split (GtsConstraint * c, GtsSurface * s, GtsFifo * fifo) { GSList * i; GtsVertex * v1, * v2; GtsEdge * e; g_return_if_fail (c != NULL); g_return_if_fail (s != NULL); v1 = GTS_SEGMENT (c)->v1; v2 = GTS_SEGMENT (c)->v2; e = GTS_EDGE (c); i = e->triangles; while (i) { GtsFace * f = i->data; if (GTS_IS_FACE (f) && gts_face_has_parent_surface (f, s)) { GtsVertex * v = gts_triangle_vertex_opposite (GTS_TRIANGLE (f), e); if (gts_point_orientation (GTS_POINT (v1), GTS_POINT (v2), GTS_POINT (v)) == 0.) { GSList * j = e->triangles; GtsFace * f1 = NULL; GtsEdge * e1, * e2; /* replaces edges with constraints */ gts_triangle_vertices_edges (GTS_TRIANGLE (f), e, &v1, &v2, &v, &e, &e1, &e2); if (!GTS_IS_CONSTRAINT (e1)) { GtsEdge * ne1 = gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (c)->klass), v2, v); gts_edge_replace (e1, ne1); gts_object_destroy (GTS_OBJECT (e1)); e1 = ne1; if (fifo) gts_fifo_push (fifo, e1); } if (!GTS_IS_CONSTRAINT (e2)) { GtsEdge * ne2 = gts_edge_new (GTS_EDGE_CLASS (GTS_OBJECT (c)->klass), v, v1); gts_edge_replace (e2, ne2); gts_object_destroy (GTS_OBJECT (e2)); e2 = ne2; if (fifo) gts_fifo_push (fifo, e2); } /* look for face opposite */ while (j && !f1) { if (GTS_IS_FACE (j->data) && gts_face_has_parent_surface (j->data, s)) f1 = j->data; j = j->next; } if (f1) { /* c is not a boundary of s */ GtsEdge * e3, * e4, * e5; GtsVertex * v3; gts_triangle_vertices_edges (GTS_TRIANGLE (f1), e, &v1, &v2, &v3, &e, &e3, &e4); e5 = gts_edge_new (s->edge_class, v, v3); gts_surface_add_face (s, gts_face_new (s->face_class, e5, e2, e3)); gts_surface_add_face (s, gts_face_new (s->face_class, e5, e4, e1)); gts_object_destroy (GTS_OBJECT (f1)); } gts_object_destroy (GTS_OBJECT (f)); return; } } i = i->next; } }
/* tri: * Main entry point to using GTS for triangulation. * Input is npt points with x and y coordinates stored either separately * in x[] and y[] (sepArr != 0) or consecutively in x[] (sepArr == 0). * Optionally, the input can include nsegs line segments, whose endpoint * indices are supplied in segs[2*i] and segs[2*i+1] yielding a constrained * triangulation. * * The return value is the corresponding gts surface, which can be queries for * the triangles and line segments composing the triangulation. */ static GtsSurface* tri(double *x, double *y, int npt, int *segs, int nsegs, int sepArr) { int i; GtsSurface *surface; GVertex **vertices = N_GNEW(npt, GVertex *); GtsEdge **edges = N_GNEW(nsegs, GtsEdge*); GSList *list = NULL; GtsVertex *v1, *v2, *v3; GtsTriangle *t; GtsVertexClass *vcl = (GtsVertexClass *) g_vertex_class(); GtsEdgeClass *ecl = GTS_EDGE_CLASS (gts_constraint_class ()); if (sepArr) { for (i = 0; i < npt; i++) { GVertex *p = (GVertex *) gts_vertex_new(vcl, x[i], y[i], 0); p->idx = i; vertices[i] = p; } } else { for (i = 0; i < npt; i++) { GVertex *p = (GVertex *) gts_vertex_new(vcl, x[2*i], x[2*i+1], 0); p->idx = i; vertices[i] = p; } } /* N.B. Edges need to be created here, presumably before the * the vertices are added to the face. In particular, they cannot * be added created and added vi gts_delaunay_add_constraint() below. */ for (i = 0; i < nsegs; i++) { edges[i] = gts_edge_new(ecl, (GtsVertex *) (vertices[ segs[ 2 * i]]), (GtsVertex *) (vertices[ segs[ 2 * i + 1]])); } for (i = 0; i < npt; i++) list = g_slist_prepend(list, vertices[i]); t = gts_triangle_enclosing(gts_triangle_class(), list, 100.); g_slist_free(list); gts_triangle_vertices(t, &v1, &v2, &v3); surface = gts_surface_new(gts_surface_class(), (GtsFaceClass *) g_face_class(), gts_edge_class(), gts_vertex_class()); gts_surface_add_face(surface, gts_face_new(gts_face_class(), t->e1, t->e2, t->e3)); for (i = 0; i < npt; i++) { GtsVertex *v1 = (GtsVertex *) vertices[i]; GtsVertex *v = gts_delaunay_add_vertex(surface, v1, NULL); /* if v != NULL, it is a previously added pt with the same * coordinates as v1, in which case we replace v1 with v */ if (v) { /* agerr (AGWARN, "Duplicate point %d %d\n", i, ((GVertex*)v)->idx); */ gts_vertex_replace (v1, v); } } for (i = 0; i < nsegs; i++) { gts_delaunay_add_constraint(surface,GTS_CONSTRAINT(edges[i])); } /* destroy enclosing triangle */ gts_allow_floating_vertices = TRUE; gts_allow_floating_edges = TRUE; /* gts_object_destroy(GTS_OBJECT(v1)); gts_object_destroy(GTS_OBJECT(v2)); gts_object_destroy(GTS_OBJECT(v3)); */ destroy(v1); destroy(v2); destroy(v3); gts_allow_floating_edges = FALSE; gts_allow_floating_vertices = FALSE; if (nsegs) delaunay_remove_holes(surface); free (edges); free(vertices); return surface; }
int spheroidsToSTL(const string& out, const shared_ptr<DemField>& dem, Real tol, const string& solid, int mask, bool append, bool clipCell, bool merge){ if(tol==0 || isnan(tol)) throw std::runtime_error("tol must be non-zero."); #ifndef WOO_GTS if(merge) throw std::runtime_error("woo.triangulated.spheroidsToSTL: merge=True only possible in builds with the 'gts' feature."); #endif // first traversal to find reference radius auto particleOk=[&](const shared_ptr<Particle>&p){ return (mask==0 || (p->mask & mask)) && (p->shape->isA<Sphere>() || p->shape->isA<Ellipsoid>() || p->shape->isA<Capsule>()); }; int numTri=0; if(tol<0){ LOG_DEBUG("tolerance is negative, taken as relative to minimum radius."); Real minRad=Inf; for(const auto& p: *dem->particles){ if(particleOk(p)) minRad=min(minRad,p->shape->equivRadius()); } if(isinf(minRad) || isnan(minRad)) throw std::runtime_error("Minimum radius not found (relative tolerance specified); no matching particles?"); tol=-minRad*tol; LOG_DEBUG("Minimum radius "<<minRad<<"."); } LOG_DEBUG("Triangulation tolerance is "<<tol); std::ofstream stl(out,append?(std::ofstream::app|std::ofstream::binary):std::ofstream::binary); // binary better, anyway if(!stl.good()) throw std::runtime_error("Failed to open output file "+out+" for writing."); Scene* scene=dem->scene; if(!scene) throw std::logic_error("DEM field has not associated scene?"); // periodicity, cache that for later use AlignedBox3r cell; /* wasteful memory-wise, but we need to store the whole triangulation in case *merge* is in effect, when it is only an intermediary result and will not be output as-is */ vector<vector<Vector3r>> ppts; vector<vector<Vector3i>> ttri; vector<Particle::id_t> iid; for(const auto& p: *dem->particles){ if(!particleOk(p)) continue; const auto sphere=dynamic_cast<Sphere*>(p->shape.get()); const auto ellipsoid=dynamic_cast<Ellipsoid*>(p->shape.get()); const auto capsule=dynamic_cast<Capsule*>(p->shape.get()); vector<Vector3r> pts; vector<Vector3i> tri; if(sphere || ellipsoid){ Real r=sphere?sphere->radius:ellipsoid->semiAxes.minCoeff(); // 1 is for icosahedron int tess=ceil(M_PI/(5*acos(1-tol/r))); LOG_DEBUG("Tesselation level for #"<<p->id<<": "<<tess); tess=max(tess,0); auto uSphTri(CompUtils::unitSphereTri20(/*0 for icosahedron*/max(tess-1,0))); const auto& uPts=std::get<0>(uSphTri); // unit sphere point coords pts.resize(uPts.size()); const auto& node=(p->shape->nodes[0]); Vector3r scale=(sphere?sphere->radius*Vector3r::Ones():ellipsoid->semiAxes); for(size_t i=0; i<uPts.size(); i++){ pts[i]=node->loc2glob(uPts[i].cwiseProduct(scale)); } tri=std::get<1>(uSphTri); // this makes a copy, but we need out own for capsules } if(capsule){ #ifdef WOO_VTK int subdiv=max(4.,ceil(M_PI/(acos(1-tol/capsule->radius)))); std::tie(pts,tri)=VtkExport::triangulateCapsule(static_pointer_cast<Capsule>(p->shape),subdiv); #else throw std::runtime_error("Triangulation of capsules is (for internal and entirely fixable reasons) only available when compiled with the 'vtk' features."); #endif } // do not write out directly, store first for later ppts.push_back(pts); ttri.push_back(tri); LOG_TRACE("#"<<p->id<<" triangulated: "<<tri.size()<<","<<pts.size()<<" faces,vertices."); if(scene->isPeriodic){ // make sure we have aabb, in skewed coords and such if(!p->shape->bound){ // this is a bit ugly, but should do the trick; otherwise we would recompute all that ourselves here if(sphere) Bo1_Sphere_Aabb().go(p->shape); else if(ellipsoid) Bo1_Ellipsoid_Aabb().go(p->shape); else if(capsule) Bo1_Capsule_Aabb().go(p->shape); } assert(p->shape->bound); const AlignedBox3r& box(p->shape->bound->box); AlignedBox3r cell(Vector3r::Zero(),scene->cell->getSize()); // possibly in skewed coords // central offset Vector3i off0; scene->cell->canonicalizePt(p->shape->nodes[0]->pos,off0); // computes off0 Vector3i off; // offset from the original cell //cerr<<"#"<<p->id<<" at "<<p->shape->nodes[0]->pos.transpose()<<", off0="<<off0<<endl; for(off[0]=off0[0]-1; off[0]<=off0[0]+1; off[0]++) for(off[1]=off0[1]-1; off[1]<=off0[1]+1; off[1]++) for(off[2]=off0[2]-1; off[2]<=off0[2]+1; off[2]++){ Vector3r dx=scene->cell->intrShiftPos(off); //cerr<<" off="<<off.transpose()<<", dx="<<dx.transpose()<<endl; AlignedBox3r boxOff(box); boxOff.translate(dx); //cerr<<" boxOff="<<boxOff.min()<<";"<<boxOff.max()<<" | cell="<<cell.min()<<";"<<cell.max()<<endl; if(boxOff.intersection(cell).isEmpty()) continue; // copy the entire triangulation, offset by dx vector<Vector3r> pts2(pts); for(auto& p: pts2) p+=dx; vector<Vector3i> tri2(tri); // same topology ppts.push_back(pts2); ttri.push_back(tri2); LOG_TRACE(" offset "<<off.transpose()<<": #"<<p->id<<": "<<tri2.size()<<","<<pts2.size()<<" faces,vertices."); } } } if(!merge){ LOG_DEBUG("Will export (unmerged) "<<ppts.size()<<" particles to STL."); stl<<"solid "<<solid<<"\n"; for(size_t i=0; i<ppts.size(); i++){ const auto& pts(ppts[i]); const auto& tri(ttri[i]); LOG_TRACE("Exporting "<<i<<" with "<<tri.size()<<" faces."); for(const Vector3i& t: tri){ Vector3r pp[]={pts[t[0]],pts[t[1]],pts[t[2]]}; // skip triangles which are entirely out of the canonical periodic cell if(scene->isPeriodic && clipCell && (!scene->cell->isCanonical(pp[0]) && !scene->cell->isCanonical(pp[1]) && !scene->cell->isCanonical(pp[2]))) continue; numTri++; Vector3r n=(pp[1]-pp[0]).cross(pp[2]-pp[1]).normalized(); stl<<" facet normal "<<n.x()<<" "<<n.y()<<" "<<n.z()<<"\n"; stl<<" outer loop\n"; for(auto p: {pp[0],pp[1],pp[2]}){ stl<<" vertex "<<p[0]<<" "<<p[1]<<" "<<p[2]<<"\n"; } stl<<" endloop\n"; stl<<" endfacet\n"; } } stl<<"endsolid "<<solid<<"\n"; stl.close(); return numTri; } #if WOO_GTS /***** Convert all triangulation to GTS surfaces, find their distances, isolate connected components, merge these components incrementally and write to STL *****/ // total number of points const size_t N(ppts.size()); // bounds for collision detection struct Bound{ Bound(Real _coord, int _id, bool _isMin): coord(_coord), id(_id), isMin(_isMin){}; Bound(): coord(NaN), id(-1), isMin(false){}; // just for allocation Real coord; int id; bool isMin; bool operator<(const Bound& b) const { return coord<b.coord; } }; vector<Bound> bounds[3]={vector<Bound>(2*N),vector<Bound>(2*N),vector<Bound>(2*N)}; /* construct GTS surface objects; all objects must be deleted explicitly! */ vector<GtsSurface*> ssurf(N); vector<vector<GtsVertex*>> vvert(N); vector<vector<GtsEdge*>> eedge(N); vector<AlignedBox3r> boxes(N); for(size_t i=0; i<N; i++){ LOG_TRACE("** Creating GTS surface for #"<<i<<", with "<<ttri[i].size()<<" faces, "<<ppts[i].size()<<" vertices."); AlignedBox3r box; // new surface object ssurf[i]=gts_surface_new(gts_surface_class(),gts_face_class(),gts_edge_class(),gts_vertex_class()); // copy over all vertices vvert[i].reserve(ppts[i].size()); eedge[i].reserve(size_t(1.5*ttri[i].size())); // each triangle consumes 1.5 edges, for closed surfs for(size_t v=0; v<ppts[i].size(); v++){ vvert[i].push_back(gts_vertex_new(gts_vertex_class(),ppts[i][v][0],ppts[i][v][1],ppts[i][v][2])); box.extend(ppts[i][v]); } // create faces, and create edges on the fly as needed std::map<std::pair<int,int>,int> edgeIndices; for(size_t t=0; t<ttri[i].size(); t++){ //const Vector3i& t(ttri[i][t]); //LOG_TRACE("Face with vertices "<<ttri[i][t][0]<<","<<ttri[i][t][1]<<","<<ttri[i][t][2]); Vector3i eIxs; for(int a:{0,1,2}){ int A(ttri[i][t][a]), B(ttri[i][t][(a+1)%3]); auto AB=std::make_pair(min(A,B),max(A,B)); auto ABI=edgeIndices.find(AB); if(ABI==edgeIndices.end()){ // this edge not created yet edgeIndices[AB]=eedge[i].size(); // last index eIxs[a]=eedge[i].size(); //LOG_TRACE(" New edge #"<<eIxs[a]<<": "<<A<<"--"<<B<<" (length "<<(ppts[i][A]-ppts[i][B]).norm()<<")"); eedge[i].push_back(gts_edge_new(gts_edge_class(),vvert[i][A],vvert[i][B])); } else { eIxs[a]=ABI->second; //LOG_TRACE(" Found edge #"<<ABI->second<<" for "<<A<<"--"<<B); } } //LOG_TRACE(" New face: edges "<<eIxs[0]<<"--"<<eIxs[1]<<"--"<<eIxs[2]); GtsFace* face=gts_face_new(gts_face_class(),eedge[i][eIxs[0]],eedge[i][eIxs[1]],eedge[i][eIxs[2]]); gts_surface_add_face(ssurf[i],face); } // make sure the surface is OK if(!gts_surface_is_orientable(ssurf[i])) LOG_ERROR("Surface of #"+to_string(iid[i])+" is not orientable (expect troubles)."); if(!gts_surface_is_closed(ssurf[i])) LOG_ERROR("Surface of #"+to_string(iid[i])+" is not closed (expect troubles)."); assert(!gts_surface_is_self_intersecting(ssurf[i])); // copy bounds LOG_TRACE("Setting bounds of surf #"<<i); boxes[i]=box; for(int ax:{0,1,2}){ bounds[ax][2*i+0]=Bound(box.min()[ax],/*id*/i,/*isMin*/true); bounds[ax][2*i+1]=Bound(box.max()[ax],/*id*/i,/*isMin*/false); } } /* broad-phase collision detection between GTS surfaces only those will be probed with exact algorithms below and merged if needed */ for(int ax:{0,1,2}) std::sort(bounds[ax].begin(),bounds[ax].end()); vector<Bound>& bb(bounds[0]); // run the search along x-axis, does not matter really std::list<std::pair<int,int>> int0; // broad-phase intersections for(size_t i=0; i<2*N; i++){ if(!bb[i].isMin) continue; // only start with lower bound // go up to the upper bound, but handle overflow safely (no idea why it would happen here) as well for(size_t j=i+1; j<2*N && bb[j].id!=bb[i].id; j++){ if(bb[j].isMin) continue; // this is handled by symmetry #if EIGEN_VERSION_AT_LEAST(3,2,5) if(!boxes[bb[i].id].intersects(boxes[bb[j].id])) continue; // no intersection along all axes #else // old, less elegant if(boxes[bb[i].id].intersection(boxes[bb[j].id]).isEmpty()) continue; #endif int0.push_back(std::make_pair(min(bb[i].id,bb[j].id),max(bb[i].id,bb[j].id))); LOG_TRACE("Broad-phase collision "<<int0.back().first<<"+"<<int0.back().second); } } /* narrow-phase collision detection between GTS surface this must be done via gts_surface_inter_new, since gts_surface_distance always succeeds */ std::list<std::pair<int,int>> int1; for(const std::pair<int,int> ij: int0){ LOG_TRACE("Testing narrow-phase collision "<<ij.first<<"+"<<ij.second); #if 0 GtsRange gr1, gr2; gts_surface_distance(ssurf[ij.first],ssurf[ij.second],/*delta ??*/(gfloat).2,&gr1,&gr2); if(gr1.min>0 && gr2.min>0) continue; LOG_TRACE(" GTS reports collision "<<ij.first<<"+"<<ij.second<<" (min. distances "<<gr1.min<<", "<<gr2.min); #else GtsSurface *s1(ssurf[ij.first]), *s2(ssurf[ij.second]); GNode* t1=gts_bb_tree_surface(s1); GNode* t2=gts_bb_tree_surface(s2); GtsSurfaceInter* I=gts_surface_inter_new(gts_surface_inter_class(),s1,s2,t1,t2,/*is_open_1*/false,/*is_open_2*/false); GSList* l=gts_surface_intersection(s1,s2,t1,t2); // list of edges describing intersection int n1=g_slist_length(l); // extra check by looking at number of faces of the intersected surface #if 1 GtsSurface* s12=gts_surface_new(gts_surface_class(),gts_face_class(),gts_edge_class(),gts_vertex_class()); gts_surface_inter_boolean(I,s12,GTS_1_OUT_2); gts_surface_inter_boolean(I,s12,GTS_2_OUT_1); int n2=gts_surface_face_number(s12); gts_object_destroy(GTS_OBJECT(s12)); #endif gts_bb_tree_destroy(t1,TRUE); gts_bb_tree_destroy(t2,TRUE); gts_object_destroy(GTS_OBJECT(I)); g_slist_free(l); if(n1==0) continue; #if 1 if(n2==0){ LOG_ERROR("n1==0 but n2=="<<n2<<" (no narrow-phase collision)"); continue; } #endif LOG_TRACE(" GTS reports collision "<<ij.first<<"+"<<ij.second<<" ("<<n<<" edges describe the intersection)"); #endif int1.push_back(ij); } /* connected components on the graph: graph nodes are 0…(N-1), graph edges are in int1 see http://stackoverflow.com/a/37195784/761090 */ typedef boost::subgraph<boost::adjacency_list<boost::vecS,boost::vecS,boost::undirectedS,boost::property<boost::vertex_index_t,int>,boost::property<boost::edge_index_t,int>>> Graph; Graph graph(N); for(const auto& ij: int1) boost::add_edge(ij.first,ij.second,graph); vector<size_t> clusters(boost::num_vertices(graph)); size_t numClusters=boost::connected_components(graph,clusters.data()); for(size_t n=0; n<numClusters; n++){ // beginning cluster #n // first, count how many surfaces are in this cluster; if 1, things are easier int numThisCluster=0; int cluster1st=-1; for(size_t i=0; i<N; i++){ if(clusters[i]!=n) continue; numThisCluster++; if(cluster1st<0) cluster1st=(int)i; } GtsSurface* clusterSurf=NULL; LOG_DEBUG("Cluster "<<n<<" has "<<numThisCluster<<" surfaces."); if(numThisCluster==1){ clusterSurf=ssurf[cluster1st]; } else { clusterSurf=ssurf[cluster1st]; // surface of the cluster itself LOG_TRACE(" Initial cluster surface from "<<cluster1st<<"."); /* composed surface */ for(size_t i=0; i<N; i++){ if(clusters[i]!=n || ((int)i)==cluster1st) continue; LOG_TRACE(" Adding "<<i<<" to the cluster"); // ssurf[i] now belongs to cluster #n // trees need to be rebuild every time anyway, since the merged surface keeps changing in every cycle //if(gts_surface_face_number(clusterSurf)==0) LOG_ERROR("clusterSurf has 0 faces."); //if(gts_surface_face_number(ssurf[i])==0) LOG_ERROR("Surface #"<<i<<" has 0 faces."); GNode* t1=gts_bb_tree_surface(clusterSurf); GNode* t2=gts_bb_tree_surface(ssurf[i]); GtsSurfaceInter* I=gts_surface_inter_new(gts_surface_inter_class(),clusterSurf,ssurf[i],t1,t2,/*is_open_1*/false,/*is_open_2*/false); GtsSurface* merged=gts_surface_new(gts_surface_class(),gts_face_class(),gts_edge_class(),gts_vertex_class()); gts_surface_inter_boolean(I,merged,GTS_1_OUT_2); gts_surface_inter_boolean(I,merged,GTS_2_OUT_1); gts_object_destroy(GTS_OBJECT(I)); gts_bb_tree_destroy(t1,TRUE); gts_bb_tree_destroy(t2,TRUE); if(gts_surface_face_number(merged)==0){ LOG_ERROR("Cluster #"<<n<<": 0 faces after fusing #"<<i<<" (why?), adding #"<<i<<" separately!"); // this will cause an extra 1-particle cluster to be created clusters[i]=numClusters; numClusters+=1; } else { // not from global vectors (cleanup at the end), explicit delete! if(clusterSurf!=ssurf[cluster1st]) gts_object_destroy(GTS_OBJECT(clusterSurf)); clusterSurf=merged; } } } #if 0 LOG_TRACE(" GTS surface cleanups..."); pygts_vertex_cleanup(clusterSurf,.1*tol); // cleanup 10× smaller than tolerance pygts_edge_cleanup(clusterSurf); pygts_face_cleanup(clusterSurf); #endif LOG_TRACE(" STL: cluster "<<n<<" output"); stl<<"solid "<<solid<<"_"<<n<<"\n"; /* output cluster to STL here */ _gts_face_to_stl_data data(stl,scene,clipCell,numTri); gts_surface_foreach_face(clusterSurf,(GtsFunc)_gts_face_to_stl,(gpointer)&data); stl<<"endsolid\n"; if(clusterSurf!=ssurf[cluster1st]) gts_object_destroy(GTS_OBJECT(clusterSurf)); } // this deallocates also edges and vertices for(size_t i=0; i<ssurf.size(); i++) gts_object_destroy(GTS_OBJECT(ssurf[i])); return numTri; #endif /* WOO_GTS */ }
int main (int argc, char * argv[]) { GtsSurface * surface = gts_surface_new (gts_surface_class (), gts_face_class (), gts_edge_class (), gts_vertex_class ()); GtsVertex * v1 = gts_vertex_new (gts_vertex_class (), 0, 0, 0); GtsVertex * v2 = gts_vertex_new (gts_vertex_class (), 0, -0.5, 1); GtsVertex * v3 = gts_vertex_new (gts_vertex_class (), 0, 0.5, 1); GtsVertex * v4 = gts_vertex_new (gts_vertex_class (), 1, 0, 0); GtsVertex * v5 = gts_vertex_new (gts_vertex_class (), 0, -0.5, -1); GtsVertex * v6 = gts_vertex_new (gts_vertex_class (), 1, -0.5, -1); GtsVertex * v7 = gts_vertex_new (gts_vertex_class (), 1, 0.5, -1); GtsVertex * v8 = gts_vertex_new (gts_vertex_class (), 1, -0.5, 1); GtsVertex * v9 = gts_vertex_new (gts_vertex_class (), 1, 0.5, 1); GtsVertex * v10 = gts_vertex_new (gts_vertex_class (), 0, 0.5, -1); GtsEdge * e1 = gts_edge_new (gts_edge_class (), v1, v2); GtsEdge * e2 = gts_edge_new (gts_edge_class (), v1, v3); GtsEdge * e3 = gts_edge_new (gts_edge_class (), v2, v3); GtsEdge * e4 = gts_edge_new (gts_edge_class (), v4, v5); GtsEdge * e5 = gts_edge_new (gts_edge_class (), v4, v6); GtsEdge * e6 = gts_edge_new (gts_edge_class (), v5, v6); GtsEdge * e7 = gts_edge_new (gts_edge_class (), v1, v4); GtsEdge * e8 = gts_edge_new (gts_edge_class (), v1, v5); GtsEdge * e9 = gts_edge_new (gts_edge_class (), v6, v7); GtsEdge * e10 = gts_edge_new (gts_edge_class (), v4, v7); GtsEdge * e11 = gts_edge_new (gts_edge_class (), v1, v7); GtsEdge * e12 = gts_edge_new (gts_edge_class (), v8, v9); GtsEdge * e13 = gts_edge_new (gts_edge_class (), v4, v9); GtsEdge * e14 = gts_edge_new (gts_edge_class (), v4, v8); GtsEdge * e15 = gts_edge_new (gts_edge_class (), v5, v10); GtsEdge * e16 = gts_edge_new (gts_edge_class (), v1, v10); GtsEdge * e17 = gts_edge_new (gts_edge_class (), v10, v7); GtsEdge * e18 = gts_edge_new (gts_edge_class (), v1, v8); GtsEdge * e19 = gts_edge_new (gts_edge_class (), v2, v8); GtsEdge * e20 = gts_edge_new (gts_edge_class (), v4, v3); GtsEdge * e21 = gts_edge_new (gts_edge_class (), v3, v9); GtsFace * f1 = gts_face_new (gts_face_class (), e1, e2, e3); GtsFace * f2 = gts_face_new (gts_face_class (), e4, e5, e6); GtsFace * f3 = gts_face_new (gts_face_class (), e7, e4, e8); GtsFace * f4 = gts_face_new (gts_face_class (), e9, e5, e10); GtsFace * f5 = gts_face_new (gts_face_class (), e11, e10, e7); GtsFace * f6 = gts_face_new (gts_face_class (), e12, e13, e14); GtsFace * f7 = gts_face_new (gts_face_class (), e15, e16, e8); GtsFace * f8 = gts_face_new (gts_face_class (), e17, e11, e16); GtsFace * f9 = gts_face_new (gts_face_class (), e18, e14, e7); GtsFace * f10 = gts_face_new (gts_face_class (), e19, e18, e1); GtsFace * f11 = gts_face_new (gts_face_class (), e20, e13, e21); GtsFace * f12 = gts_face_new (gts_face_class (), e7, e20, e2); GtsVertex * v; GtsVolumeOptimizedParams params = { 0.5, 0.5, 0.0 }; GtsSplit * vs; gts_surface_add_face (surface, f1); gts_surface_add_face (surface, f2); gts_surface_add_face (surface, f3); gts_surface_add_face (surface, f4); gts_surface_add_face (surface, f5); gts_surface_add_face (surface, f6); gts_surface_add_face (surface, f7); gts_surface_add_face (surface, f8); gts_surface_add_face (surface, f9); gts_surface_add_face (surface, f10); gts_surface_add_face (surface, f11); gts_surface_add_face (surface, f12); g_assert (gts_edge_collapse_is_valid (e7)); v = gts_volume_optimized_vertex (e7, gts_vertex_class (), ¶ms); vs = gts_split_new (gts_split_class (), v, GTS_OBJECT (GTS_SEGMENT (e7)->v1), GTS_OBJECT (GTS_SEGMENT (e7)->v2)); gts_split_collapse (vs, gts_edge_class (), NULL); gts_surface_write (surface, stdout); return 0; }
static PyObject * new_(PyTypeObject *type, PyObject *args, PyObject *kwds) { PyObject *o; PygtsObject *obj; guint alloc_gtsobj = TRUE; PyObject *o1_,*o2_,*o3_; GtsVertex *v1=NULL, *v2=NULL, *v3=NULL; GtsEdge *e1=NULL,*e2=NULL,*e3=NULL,*e; GtsSegment *s1,*s2,*s3; gboolean flag=FALSE; /* Flag when the args are gts.Point objects */ GtsFace *f; GtsTriangle *t; guint N; /* Parse the args */ if(kwds) { o = PyDict_GetItemString(kwds,"alloc_gtsobj"); if(o==Py_False) { alloc_gtsobj = FALSE; } if(o!=NULL) { PyDict_DelItemString(kwds, "alloc_gtsobj"); } } if(kwds) { Py_INCREF(Py_False); PyDict_SetItemString(kwds,"alloc_gtsobj", Py_False); } /* Allocate the gtsobj (if needed) */ if( alloc_gtsobj ) { /* Parse the args */ if( (N = PyTuple_Size(args)) < 3 ) { PyErr_SetString(PyExc_TypeError,"expected three Edges or three Vertices"); return NULL; } o1_ = PyTuple_GET_ITEM(args,0); o2_ = PyTuple_GET_ITEM(args,1); o3_ = PyTuple_GET_ITEM(args,2); /* Convert to PygtsObjects */ if( pygts_edge_check(o1_) ) { e1 = PYGTS_EDGE_AS_GTS_EDGE(o1_); } else { if( pygts_vertex_check(o1_) ) { v1 = PYGTS_VERTEX_AS_GTS_VERTEX(o1_); flag = TRUE; } } if( pygts_edge_check(o2_) ) { e2 = PYGTS_EDGE_AS_GTS_EDGE(o2_); } else { if( pygts_vertex_check(o2_) ) { v2 = PYGTS_VERTEX_AS_GTS_VERTEX(o2_); flag = TRUE; } } if( pygts_edge_check(o3_) ) { e3 = PYGTS_EDGE_AS_GTS_EDGE(o3_); } else { if(pygts_vertex_check(o3_)) { v3 = PYGTS_VERTEX_AS_GTS_VERTEX(o3_); flag = TRUE; } } /* Check for three edges or three vertices */ if( !((e1!=NULL && e2!=NULL && e3!=NULL) || (v1!=NULL && v2!=NULL && v3!=NULL)) ) { PyErr_SetString(PyExc_TypeError, "three Edge or three Vertex objects expected"); return NULL; } if(flag) { /* Create gts edges */ if( (e1 = gts_edge_new(gts_edge_class(),v1,v2)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); return NULL; } if( (e2 = gts_edge_new(gts_edge_class(),v2,v3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); return NULL; } if( (e3 = gts_edge_new(gts_edge_class(),v3,v1)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Edge"); gts_object_destroy(GTS_OBJECT(e1)); gts_object_destroy(GTS_OBJECT(e2)); return NULL; } /* Check for duplicates */ if( (e = gts_edge_is_duplicate(e1)) != NULL ) { gts_object_destroy(GTS_OBJECT(e1)); e1 = e; } if( (e = gts_edge_is_duplicate(e2)) != NULL ) { gts_object_destroy(GTS_OBJECT(e2)); e2 = e; } if( (e = gts_edge_is_duplicate(e3)) != NULL ) { gts_object_destroy(GTS_OBJECT(e3)); e3 = e; } } /* Check that edges connect */ s1 = GTS_SEGMENT(e1); s2 = GTS_SEGMENT(e2); s3 = GTS_SEGMENT(e3); if( !((s1->v1==s3->v2 && s1->v2==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v2 && s1->v2==s2->v2 && s2->v1==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v1 && s2->v2==s3->v1) || (s1->v1==s3->v1 && s1->v2==s2->v2 && s2->v1==s3->v2) || (s1->v2==s3->v2 && s1->v1==s2->v2 && s2->v1==s3->v1) || (s1->v2==s3->v1 && s1->v1==s2->v1 && s2->v2==s3->v2) || (s1->v2==s3->v1 && s1->v1==s2->v2 && s2->v1==s3->v2)) ) { PyErr_SetString(PyExc_RuntimeError, "Edges in face must connect"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Create the GtsFace */ if( (f = gts_face_new(gts_face_class(),e1,e2,e3)) == NULL ) { PyErr_SetString(PyExc_MemoryError, "could not create Face"); if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e1)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e2)); } if(!g_hash_table_lookup(obj_table,GTS_OBJECT(e1))) { gts_object_destroy(GTS_OBJECT(e3)); } return NULL; } /* Check for duplicate */ t = gts_triangle_is_duplicate(GTS_TRIANGLE(f)); if( t != NULL ) { gts_object_destroy(GTS_OBJECT(f)); if(!GTS_IS_FACE(t)) { PyErr_SetString(PyExc_TypeError, "expected a Face (internal error)"); } f = GTS_FACE(t); } /* If corresponding PyObject found in object table, we are done */ if( (obj=(PygtsObject*)g_hash_table_lookup(obj_table,GTS_OBJECT(f))) != NULL ) { Py_INCREF(obj); return (PyObject*)obj; } } /* Chain up */ obj = PYGTS_OBJECT(PygtsTriangleType.tp_new(type,args,kwds)); if( alloc_gtsobj ) { obj->gtsobj = GTS_OBJECT(f); /* Create the parent GtsSurface */ if( (obj->gtsobj_parent = parent(GTS_FACE(obj->gtsobj))) == NULL ) { gts_object_destroy(obj->gtsobj); obj->gtsobj = NULL; return NULL; } pygts_object_register(PYGTS_OBJECT(obj)); } return (PyObject*)obj; }