void Bedge::fix_multi() { // If any faces in the _adj listed are labelled "primary", // then move them to a primary slot (_f1 or _f2). Used when // reading a mesh from file, when primary/secondary face // labels are specified only after all faces are created. if (!_adj) // no _adj, so no fixing required return; // It's not possible if the total number of "primary" faces // exceeds 2. if (nfaces_satisfy(PrimaryFaceFilter()) > 2) { cerr << "Bedge::fix_multi: error: more than 2 primary faces" << endl; return; } // Work backwards to remove items from the list: for (int i=_adj->num()-1; i>=0; i--) { Bface *face = (*_adj)[i]; if (face->is_primary()) { promote(face); } } }
///////////////////////////////////// // clip_to_patch() ///////////////////////////////////// void HatchingGroupFixed::clip_to_patch( CNDCpt_list &pts, NDCpt_list &cpts, const ARRAY<double>&prl, ARRAY<double>&cprl ) { int k, started = 0; Bface *f; Wpt foo; for (k=0; k<pts.num(); k++) { f = find_face_vis(pts[k], foo); if ((f) && (f->patch() == _patch)) { started = 1; cpts += pts[k]; cprl += prl[k]; } else { if (started) { k=pts.num(); } } } }
bool Bface::get_quad_verts(Bvert*& a, Bvert*& b, Bvert*& c, Bvert*& d) const { // Return CCW verts a, b, c, d as in the picture, orienting // things so that the weak edge runs NE as shown: // // d ---------- c = w->v2() ^ // | / | | // | / | | // | w / | tan1 tan2 | // | / | --------> | // | / f | | // |/ | // a ---------- b // = w->v1() // if (!is_quad()) return 0; Bedge* w = weak_edge(); Bface* f = w->ccw_face(w->v2()); a = w->v1(); b = f->next_vert_ccw(a); c = w->v2(); d = f->quad_vert(); return true; }
Patch* Bedge::patch() const { Bface* f = frontfacing_face(); f = f ? f : _f1 ? _f1 : _f2; return f ? f->patch() : 0; }
///////////////////////////////////// // notify_change() ///////////////////////////////////// void HatchingHatchFixed::notify_change(BMESHptr m, BMESH::change_t chg) { assert(chg == BMESH::VERT_POSITIONS_CHANGED); vector<HatchingFixedVertex>::size_type k; if (_verts.size() > 0) { //Sanity check assert(_verts.size() == _pts.size()); assert(_verts.size() == _norms.size()); for (k=0; k < _verts.size(); k++) { Bface *f = m->bf(_verts[k].ind); assert(f); f->bc2pos(_verts[k].bar,_pts[k]); f->bc2norm_blend(_verts[k].bar,_norms[k]); } //Clear these cached values so they regenerate _real_pts.clear(); _real_norms.clear(); _real_good.clear(); } else { err_mesg(ERR_LEV_WARN, "HatchingHatchFixed::notify_change() - Verts changed, but we can't update fixed hatches!!!"); } }
///////////////////////////////////// // store_visibility() ///////////////////////////////////// bool HatchingGroupFixed::store_visibility(HatchingLevelBase *hlb) { assert(!_complete); HatchingLevelBase::size_type k; NDCZpt_list::size_type j; NDCZpt_list pts, hull; //One side for (k=0; k<hlb->size(); k++) for (j=0; j<(*hlb)[k]->get_pts().size(); j++) pts.push_back(NDCZpt( _patch->xform() * ((*hlb)[k]->get_pts()[j]) )); compute_convex_hull(pts,hull); if (hull.size() == 0) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed:store_visibility() - Error!! There were %d convex hull verts found from %d hatch verts.", hull.size(), pts.size()); return false; } else { err_mesg(ERR_LEV_INFO, "HatchingGroupFixed:store_visibility() - There were %d convex hull verts found from %d hatch verts.", hull.size(), pts.size()); } Bface *f = nullptr; XYpt center = NDCpt(hull.average()); BMESHray r(center); VIEW::peek()->intersect(r); f = r.face(); if (!f) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed:store_visibility() - Seed face intersect failed (hull center)!"); } if (!(_patch == f->patch())) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed:store_visibility() - Seed face intersected wrong patch (hull center)!!"); return false; } CBface_list & faces = f->mesh()->faces(); for (k=0; k < faces.size(); k++) faces[k]->clear_bit(1); int ctr = 0; ctr = recurse_visibility(f,hull); err_mesg(ERR_LEV_INFO, "HatchingGroupFixed:store_visibility() - There were %d faces in the visible region (from %d).", ctr, faces.size()); _group->patch()->changed(); if (ctr>0) return true; else return false; }
int SELECT_WIDGET::tap_cb(CGESTUREptr& g, DrawState*& s) { err_adv(debug, "SELECT_WIDGET::tap_cb()"); assert(g && g->is_tap()); if (_mode==SLASH_SEL) { //pattern editing Bface* f = find_face(g->start(),0.25,MIN_PIX_AREA); if (f) { if (select_list.contains(f)||select_list.contains(f->quad_partner())) { //get whichever part of the quad is in the selection list int temp = select_list.contains(f) ? select_list.get_index(f)+1 : select_list.get_index(f->quad_partner())+1 ; if (temp>end_face) //user selected the end face { end_face=temp; } else //user is selecting a pattern { if (pattern<temp) pattern=temp; //select/deselect face if (temp < MAX_PATTERN_SIZE) pattern_array[temp]=!pattern_array[temp]; } return 1; } else cerr << "tap found a NULL face !" << endl; } return cancel_cb(g,s); } else { // Tap a selected face near the middle to deselect it: if (try_deselect_face(g->center(), 0.25)) return 1; // Tap edge to deselect if (try_deselect_edge(g->center())) return 1; } // Otherwise, turn off SELECT_WIDGET return cancel_cb(g,s); }
//! Same as try_select_face(), but deselects. //! Also requires the found face is currently selected. bool SELECT_WIDGET::try_deselect_face(CPIXEL &pix, double margin) { Bface* f = find_face(pix, margin, MIN_PIX_AREA); if (!(f && f->is_selected())) return false; WORLD::add_command(new MESH_DESELECT_CMD(f)); return true; }
inline Bface* check_partner(Bface* f) { // helper function used in quad_complete_faces() below if (!(f && f->is_quad())) return nullptr; Bface* p = f->quad_partner(); return p->flag() ? nullptr : p; }
int INFLATE::stroke_cb(CGESTUREptr& gest, DrawState*& s) { err_adv(debug, "INFLATE::stroke_cb"); reset_timeout(); // Verify that we have a starting face if ( _orig_face ) { Bface* face = 0; // Check that the stroke is straight enough to represent a line if (!(gest->straightness() > 0.8)) { err_adv(debug, "INFLATE::stroke_cb: gesture not straight"); return false; } // Check that the gesture starts on the mesh Bsurface::hit_ctrl_surface(gest->start(), 1, &face); if (!(face)) { err_adv(debug, "INFLATE::stroke_cb: can't get hit face"); return false; } // create VEXELs for the gesture and the face normal VEXEL fvec = VEXEL(face->v1()->loc(), face->norm()); VEXEL fgest = gest->endpt_vec(); // If gesture nearly parallel to normal: double a = rad2deg(line_angle(fvec,fgest)); err_adv(debug, "INFLATE::stroke_cb: angle: %f %s", a, (a > 15) ? "(bad)" : "(good)"); if (a > 15) { // Fail if angle is too extreme WORLD::message("Bad angle"); return 0; } // calculate extrude width double dist = fgest.length()/fvec.length(); err_adv(debug, "INFLATE::stroke_cb: strong_edge_len: %f, gest_len: %f, \ fvect_len: %f", avg_strong_edge_len(face), fgest.length(), fvec.length() ); // Convert to relative to local edge length dist /= avg_strong_edge_len(face); if (fvec*fgest<0) dist=-dist; // Get the sign right // Store the new inflate distance _preview_dist = dist; } return 1; // we used up the gesture... }
int XformPen::tap_cb(CGESTUREptr& tap, DrawState*& s) { assert(tap); if (tap->is_double_tap()) { // should never happen given order of arcs in XformPen constructor cerr << "XformPen::tap_cb: error: gesture is double tap" << endl; return 0; } // tap on cursor? BMESHray ray(tap->center()); _view->intersect(ray); Cursor3D* c = Cursor3D::upcast(ray.geom()); if (c) { err_adv(debug, "XformPen::tap_cb: hit axis"); c->handle_gesture(tap); return 0; } // tap on mesh? _mesh = 0; Bface* f = cur_face(); if (!f) { err_adv(debug, "XformPen::tap_cb: missed face"); return cancel_cb(tap, s); } BMESH* m = f->mesh(); if (!m) { err_adv(debug, "XformPen::tap_cb: hit face, no mesh"); return cancel_cb(tap, s); } GEOMptr g = bmesh_to_geom(m); if (!g) { err_adv(debug, "XformPen::tap_cb: hit mesh, no geom"); return cancel_cb(tap, s); } // skip floor: if (FLOOR::isa(g)) { err_adv(debug, "XformPen::tap_cb: hit floor, skipping..."); return cancel_cb(tap, s); } // tap on ordinary mesh (not floor): _mesh = m; assert(_mesh); BMESH::set_focus(_mesh, f->patch()); FLOOR::show(); FLOOR::realign(_mesh,0); // 0 = no undo command Cursor3D::attach(g); return 0; }
/*! Find the nearest Bface at the given screen location pix, (within the search radius rad), then re-map it to the corresponding edit-level face and barycentric coordinates bc. */ inline Bface* find_face(CPIXEL& pix, double rad, Wvec& bc) { Bface* f = VisRefImage::get_edit_face(bc, pix, rad); // Only select faces belonging to a TEXBODY if (!(f && f->mesh() && TEXBODY::isa(f->mesh()->geom()))) return 0; return f; }
void EdgeStrip::build_ccw_boundaries( CBedge_list& edges, CSimplexFilter& face_filter ) { // Similar to previous... // // XXX - needs comments // Clear edge flags to screen for unreached edges: // set edge flags to 1 in 1-ring of verts, // then clear edge flags of internal edges set_adjacent_edges(edges.get_verts(), 1); edges.clear_flags(); // get an edge filter that accepts "boundary" edges WRT the // given face filter BoundaryEdgeFilter boundary(face_filter); // Pull out the edge tips: Bedge_list tips = edges.filter(ChainTipEdgeFilter(boundary)); // Construct the filter that screens out previously reached // edges: UnreachedSimplexFilter unreached; AndFilter wanted = unreached + boundary; int k; // Start from all the tips first: for (k=0; k<tips.num(); k++) { Bedge* e = tips[k]; Bvert* v = (e->v2()->degree(boundary) != 2) ? e->v2() : e->v1(); Bface* f = e->screen_face(face_filter); assert(f); // e must have 1 face satisfying the filter // If this will start out running ccw, take it. // otherwise skip: if (f->next_vert_ccw(v) == e->other_vertex(v)) build(v, e, wanted); } // Now check the rest: for (k=0; k<edges.num(); k++) { Bedge* e = edges[k]; Bface* f = e->screen_face(face_filter); assert(f); // e must have 1 face satisfying the filter // Go CCW around faces build(f->leading_vert_ccw(e), e, wanted); } }
// 2 // 3|_|1 // 0 Bface* ProxySurface::neighbor_face(int dir, Bface* face) { UVpt base_uv = baseUVpt(face); UVpt uv1, uv2, uv3, uv4; switch (dir) { case 0: uv1 = UVpt(base_uv[0], base_uv[1]); uv2 = UVpt(base_uv[0]+1, base_uv[1]); uv3 = UVpt(base_uv[0], base_uv[1]-1); uv4 = UVpt(base_uv[0]+1, base_uv[1]-1); break; case 1: uv1 = UVpt(base_uv[0]+1, base_uv[1]); uv2 = UVpt(base_uv[0]+2, base_uv[1]); uv3 = UVpt(base_uv[0]+1, base_uv[1]+1); uv4 = UVpt(base_uv[0]+2, base_uv[1]+1); break; case 2: uv1 = UVpt(base_uv[0], base_uv[1]+1); uv2 = UVpt(base_uv[0]+1, base_uv[1]+1); uv3 = UVpt(base_uv[0], base_uv[1]+2); uv4 = UVpt(base_uv[0]+1, base_uv[1]+2); break; case 3: uv1 = UVpt(base_uv[0]-1, base_uv[1]); uv2 = UVpt(base_uv[0], base_uv[1]); uv3 = UVpt(base_uv[0], base_uv[1]+1); uv4 = UVpt(base_uv[0]-1, base_uv[1]+1); break; default: cerr << "ProxySurface::neighbor_face: invalid diraction" << endl; assert(0); } Bvert* v1 = get_vert_grid(uv1); Bvert* v2 = get_vert_grid(uv2); Bvert* v3 = get_vert_grid(uv3); Bvert* v4 = get_vert_grid(uv4); if(!v1 || !v2 || !v3 || !v4) return 0; Bface* n = lookup_quad(v1, v2, v3, v4); assert(n); assert(n->is_quad()); n = (n->is_quad_rep()) ? n : n->quad_partner(); return n; }
inline void get_other_face(CBedge* e, CBface* f, Bsimplex_list& ret) { assert(e && f && f->contains(e)); if (e->is_weak()) return; Bface* g = e->other_face(f); if (!g) return; ret.push_back(g); g = g->quad_partner(); if (g) ret.push_back(g); }
void visit(OctreeNode* node, double regularity, Bface_list& flist, ARRAY<Wvec>& blist) { if (node->get_leaf()) { if (node->get_disp()) { // subdivision ARRAY<QuadtreeNode*> fs; Bface_list temp; for (int i = 0; i < node->intersects().num(); i++) { Bface* f = node->intersects()[i]; temp += f; fs += new QuadtreeNode(f->v1()->loc(), f->v2()->loc(), f->v3()->loc()); fs.last()->build_quadtree(node, regularity); fs.last()->set_terms(); } // assign weights assign_weights(fs, regularity, node->center()); // pick a triangle int t = pick(fs); // moved below; want to ensure flist and blist stay in sync: // flist += temp[t]; //set node face Bface_list ftemp; ftemp += temp[t]; node->set_face(ftemp); // pick a point int p = pick(fs[t]->terms()); if (p != -1) { Wvec bc; temp[t]->project_barycentric(fs[t]->terms()[p]->urand_pick(), bc); blist += bc; flist += temp[t]; // moved from above node->set_point(bc); } for (int i = 0; i < fs.num(); i++) delete fs[i]; fs.clear(); } } else { for (int i = 0; i < 8; i++) visit(node->get_children()[i], regularity, flist, blist); } }
int INFLATE::line_cb(CGESTUREptr& gest, DrawState*& s) { // Activity occurred to extend the deadline for fading away: reset_timeout(); err_adv(debug, "INFLATE::line_cb"); // Verify that we have a starting face if ( _orig_face ) { // Check that the stroke is straight enough to represent a line Bface* face = 0; if (!(gest->straightness() > 0.8)) { err_adv(debug, "INFLATE::line_cb: gesture not straight"); return false; } // Check that the gesture starts on the mesh Bsurface::hit_ctrl_surface(gest->start(), 1, &face); if (!(face)) { err_adv(debug, "INFLATE::line_cb: can't get hit face"); return false; } // create VEXELs for the gesture and the face normal VEXEL fvec = VEXEL(face->v1()->loc(), face->norm()); VEXEL fgest = gest->endpt_vec(); // If gesture nearly parallel to normal: double a = rad2deg(line_angle(fvec,fgest)); err_adv(debug, "INFLATE::line_cb: angle: %f %s", a, (a > 15) ? "(bad)" : "(good)"); if (a > 15) { return false; } // calculate extrude width double dist = fgest.length()/fvec.length(); if (fvec*fgest<0) dist=-dist; // Get the sign right _preview_dist = dist; } // get here if nothing happened... // don't cancel (which would deactivate the widget), // just return 1 to indicate that we used up the gesture: return 1; }
/***************************************************************** * UVdata *****************************************************************/ UVdata::UVdata(Bsimplex* s) : SimplexData(key(), s), _uv_valid(false), _calc_type(SIMPLE_CALC), // default: simple subdiv scheme _did_subdiv(false), _mapping(0) { // The constructor is called (internally) only when the given // simplex does not already have a UVdata associated with it. // // UVdata gets looked-up by its classname. // For face data, slather UVdatas all over the // vertices and edges: if (is_face(s)) { Bface* f = (Bface*)s; get_data(f->v1()); get_data(f->v2()); get_data(f->v3()); get_data(f->e1()); get_data(f->e2()); get_data(f->e3()); // XXX - // If the UVdata is created on a pre-existing face // that has already generated its subdivision // elements, then currently the UVdatas won't be // created on those subdivision elements ... } }
void OctreeNode::build_octree(int height) { if (_leaf || _height == height) return; int i, j; Wpt_list pts; points(pts); for (i = 0; i < 8; i++) { _children[i] = new OctreeNode((pts[0]+pts[i])/2, (pts[i]+pts[7])/2, _height+1, this); } for (j = 0; j < _intersects.num(); j++) { Bface* f = _intersects[j]; for (i = 0; i < 8; i++) { OctreeNode* n = _children[i]; if (n->contains(f->v1()->loc()) && n->contains(f->v2()->loc()) && n->contains(f->v3()->loc())) { n->intersects() += f; break; } else if (n->overlaps(bface_bbox(f))) { n->intersects() += f; } } } for (i = 0; i < 8; i++) { if (_height+1 == height) { _children[i]->set_leaf(true); _children[i]->set_disp(true); } if (_children[i]->intersects().empty()) { _children[i]->set_leaf(true); _children[i]->set_disp(false); } _children[i]->build_octree(height); } }
bool SELECT_WIDGET::select_faces(CPIXEL_list& pts) { err_adv(debug, "SELECT_WIDGET::select_faces:"); if (pts.num() < 2) { err_adv(debug, " too few points: %d", pts.num()); return false; } Bface* f = find_face(pts[0], 0.25, MIN_PIX_AREA); if (!(f && f->is_selected())) { err_adv(debug, " bad starter face"); return false; } // // XXX - Old code (to be deleted). Selects faces individually instead of as // // a group: // for (int i=0; i<pts.num(); i++) // try_select_face(pts[i], 0.1); Bface_list flist; for(int i = 0; i < pts.num(); ++i){ f = find_face(pts[i], 0.1, MIN_PIX_AREA); if (!f || f->is_selected()) continue; flist += f; } if(flist.num() > 0){ WORLD::add_command(new MESH_SELECT_CMD(flist)); err_adv(debug, " succeeded"); return true; } err_adv(debug, " no faces selected"); return false; }
Bface* Bedge::lookup_face(CBedge *e) const { // Reject null pointers. // Also, it's not helpful if the edge is this one. if (!e || this == e) return 0; // Return an adjacent Bface containing the edge if (_f1 && _f1->contains(e)) return _f1; if (_f2 && _f2->contains(e)) return _f2; if (_adj) { for (int i=0; i<_adj->num(); i++) { Bface* f = (*_adj)[i]; if (f->contains(e)) return f; } } return 0; }
///////////////////////////////////// // clip_to_patch() ///////////////////////////////////// void HatchingGroupFixed::clip_to_patch( CNDCpt_list &pts, NDCpt_list &cpts, const vector<double>&prl, vector<double>&cprl ) { NDCpt_list::size_type k; bool started = false; Bface *f; Wpt foo; for (k=0; k<pts.size(); k++) { f = find_face_vis(pts[k], foo); if (f && f->patch() == _patch) { started = true; cpts.push_back(pts[k]); cprl.push_back(prl[k]); } else { if (started) { k=pts.size(); } } } }
Bface* Bedge::lookup_face(CBvert *v) const { // Reject null pointers. // Also, it's not helpful if the vertex belongs // to this edge. if (!v || this->contains(v)) return 0; // Return an adjacent Bface containing the vertex if (_f1 && _f1->contains(v)) return _f1; if (_f2 && _f2->contains(v)) return _f2; if (_adj) { for (int i=0; i<_adj->num(); i++) { Bface* f = (*_adj)[i]; if (f->contains(v)) return f; } } return 0; }
int SELECT_WIDGET:: slash_cb(CGESTUREptr& gest, DrawState*& s) { if (_mode==SEL_FACE) //widget is in face selection mode { select_list.clear(); Bface* f = find_face(gest->start(),0.25,MIN_PIX_AREA); // f should be the currently selected face if (f && f->is_selected() && f->is_quad()) { f=f->quad_rep(); //line in screen space coresponding to the slash PIXELline slash(gest->start(),gest->end()); Bedge *e1,*e2,*e3,*e4; Bedge* edge = 0; //get and test the quad edges against the stroke line f->get_quad_edges(e1,e2,e3,e4); if( e1->pix_line().intersect_segs(slash) ) { edge=e1; } else if( e2->pix_line().intersect_segs(slash) ) { edge=e2; } else if( e3->pix_line().intersect_segs(slash) ) { edge=e3; } else if( e4->pix_line().intersect_segs(slash) ) { edge=e4; } else { //error cerr << "ERROR no intersection" << endl; return 1; } //walk the geometry and select faces Bface* fn = f; do { if (!fn->is_selected()) select_list +=fn; assert(edge); //I'm paranoid too assert(edge->f1()!=edge->f2()); //grabs the face on the other side of the edge //even if we are not directly adjacent to this edge fn=fn->other_quad_face(edge); if (fn) //if a valid face than advance the edge pointer { assert(edge!=fn->opposite_quad_edge(edge)); edge = fn->opposite_quad_edge(edge); fn = fn->quad_rep(); //all faces on the selection list are rep faces } else cerr << "No face on the other side of the edge" << endl; } //quit if not a valid face or not a quad while(fn&&(fn->is_quad())&&edge&&(fn!=f)); _mode=SLASH_SEL; //go into pattern editing mode end_face=0; //prepare the 2nd step data pattern=0; for (int i=0; i<MAX_PATTERN_SIZE; i++) pattern_array[i]=1; //fill the default pattern with ones } else cerr << "This is not a quad" << endl; } else if( _mode==SLASH_SEL)//pattern editing mode { //activates upon second slash motion //adds the entire list to the selected group //undo deselects the entire group Bface_list final_list; //copy the face pointers to the final list //using the pattern as a repeating template //and stop at the end face for (int i = 0; i<(end_face ? (end_face) : select_list.num()) ; i++) { if (pattern ? pattern_array[(i%pattern)+1] : 1) final_list+=select_list[i]; } WORLD::add_command(new MESH_SELECT_CMD(final_list)); return cancel_cb(gest,s); } else cerr << "wrong mode " << endl; return 1; }
bool TriStrip::build( Bface* start, // build a new strip starting here. Bface_list& stack // used to build nearby parallel strips ) { // a set flag means this face is already in a TriStrip assert(!start->flag()); // start fresh reset(); // repeat 1st vertex if needed if (!start->orient_strip()) start->orient_strip(start->v1()); // get the starting vert. i.e., the strip will // continue onto the next face across the edge // opposite this vertex. Bvert *a = start->orient_strip(), *b, *c; // squash and stretch start = backup_strip(start,a); if (!start) { // should never happen, but can happen // if there are inconsistently oriented faces err_msg("TriStrip::build: error: backup_strip() failed"); err_msg("*** check mesh for inconsistently oriented faces ***"); return 0; } // claim it claim_face(start); // record direction of strip on 1st face: start->orient_strip(a); // faces alternate CCW / CW if (_orientation) { c = start->next_vert_ccw(a); b = start->next_vert_ccw(c); } else { b = start->next_vert_ccw(a); c = start->next_vert_ccw(b); } add(a,start); add(b,start); add(c,start); Bface* opp; if ((opp = start->opposite_face(b)) && is_cleared(opp) && opp->patch() == start->patch()) { opp->orient_strip(_orientation ? a : c); stack.push_back(opp); } int i=_orientation; Bface* cur = start; while ((cur = cur->next_strip_face()) && !is_claimed(cur)) { claim_face(cur); i++; a = b; b = c; c = cur->other_vertex(a,b); cur->orient_strip(a); if ((opp = cur->opposite_face(b)) && is_cleared(opp) && opp->patch() == start->patch()) { opp->orient_strip(i % 2 ? a : c); stack.push_back(opp); } add(c,cur); } return 1; }
///////////////////////////////////// // recurse() ///////////////////////////////////// void UVMapping::recurse(Bface *seed_f, rec_fun_t fun ) { int k; // bool done = false; Bface* f; ARRAY<Bface*> faces; assert(seed_f); faces.push(seed_f); while (faces.num()>0) { //Remove oldest face from end of queue f = faces.pop(); //Skip if already seen if (!f->is_set(1)) { f->set_bit(1); //If we get here, then this face *should* have uvdata //and *should* be unmapped UVdata* uvdata = UVdata::lookup(f); assert(uvdata); assert(uvdata->mapping()==0); //Do the action (add to map, or update limits, etc.) (this->*fun)(f); for (k=1; k<=3; k++) { Bedge *nxt_e = f->e(k); assert(nxt_e); Bface *nxt_f = nxt_e->other_face(f); if (nxt_f) { UVdata *nxt_uvdata = UVdata::lookup(nxt_f); if (nxt_uvdata) { UVpt uva = uvdata->uv(k); UVpt uvb = uvdata->uv((k==3)?(1):(k+1)); int nxt_k = ( (nxt_f->e1()==nxt_e)?(1): ((nxt_f->e2()==nxt_e)?(2): ((nxt_f->e3()==nxt_e)?(3):(0)))); assert(nxt_k); UVpt nxt_uva = nxt_uvdata->uv(nxt_k); UVpt nxt_uvb = nxt_uvdata->uv((nxt_k==3)?(1):(nxt_k+1)); //If neighboring face has uv, and the they match //we recurse into this next face if ((uva==nxt_uvb)&&(uvb==nxt_uva)) { //Add to front of queue faces.push(nxt_f); } else { //Nothing } } } } } } }
///////////////////////////////////// // recurse_wrapping() ///////////////////////////////////// void UVMapping::recurse_wrapping(Bface *seed_f) { int k; Bface *f; ARRAY<Bface*> faces; assert(seed_f); faces.push(seed_f); while (faces.num()>0) { //Remove face from end of queue f = faces.pop(); //Skip if already seen if (!f->is_set(1)) { f->set_bit(1); //If we get here, then this face *should* have uvdata //and *should* be allready be mapped to us! UVdata* uvdata = UVdata::lookup(f); assert(uvdata); assert(uvdata->mapping()==this); for (k=1; k<=3; k++) { Bedge *nxt_e = f->e(k); assert(nxt_e); Bface *nxt_f = nxt_e->other_face(f); if (nxt_f) { UVdata *nxt_uvdata = UVdata::lookup(nxt_f); if (nxt_uvdata) { UVpt uva = uvdata->uv(k); UVpt uvb = uvdata->uv((k==3)?(1):(k+1)); int nxt_k = ( (nxt_f->e1()==nxt_e)?(1): ((nxt_f->e2()==nxt_e)?(2): ((nxt_f->e3()==nxt_e)?(3):(0)))); assert(nxt_k); UVpt nxt_uva = nxt_uvdata->uv(nxt_k); UVpt nxt_uvb = nxt_uvdata->uv((nxt_k==3)?(1):(nxt_k+1)); //If neighboring face has uv, and the they match //we recurse into this next face if ((uva==nxt_uvb)&&(uvb==nxt_uva)) { //Stick face on start of queue faces.push(nxt_f); } //But if not, let's see if the other face is //part of this mapping. If it is, then we found //a seam. Find the direction (u or v) and if //its consistent with the _min_u/_max_u (or v) //Then set the wrap flag for u or v appropriately //or just turn all wrapping off if something's amiss else { //Here's a seam! if (nxt_uvdata->mapping() == this) { //We support 2 kinds of wrapping: //-Wrap on a line of constant u (wrap at _min_u,_max_u) //-Wrap on a line of constant v (wrap at _min_v,_max_v) //If neither is seen, or if the discontinuity isn't //at the extrema, we found something anomolous, abort! //Note - There can be holes at the seam without problems. if ((uva[0]==uvb[0])&&(nxt_uva[0]==nxt_uvb[0])) { //This looks like wrapping on a line of const. u //Let's make sure the discontinuity is at the extrema if ( (uva[0]==_min_u && nxt_uva[0]==_max_u) || (uva[0]==_max_u && nxt_uva[0]==_min_u)) { //It's all good if (!_wrap_u) { err_mesg(ERR_LEV_SPAM, "UVMapping::recurse_wrapping() - Found a valid wrapping seam in u."); _wrap_u = true; } } else { //We aren't at the extrema, so set the bad flag //to avoid further checking _wrap_bad = true; _wrap_u = false; _wrap_v = false; err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Found an INVALID wrapping seam in u: (%f,%f) since u extrema are: (%f,%f)", uva[0], nxt_uva[0], _min_u, _max_u); err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Aborting all wrapping."); } } else if ((uva[1]==uvb[1])&&(nxt_uva[1]==nxt_uvb[1])) { //This looks like wrapping on a line of const. v //Let's make sure the discontinuity is at the extrema if ( (uva[1]==_min_v && nxt_uva[1]==_max_v) || (uva[1]==_max_v && nxt_uva[1]==_min_v)) { //It's all good if (!_wrap_v) { err_mesg(ERR_LEV_INFO, "UVMapping::recurse_wrapping() - Found a valid wrapping seam in v."); _wrap_v = true; } } else { //We aren't at the extrema, so set the bad flag //to avoid further checking _wrap_bad = true; _wrap_u = false; _wrap_v = false; err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Found an INVALID wrapping seam in v: (%f,%f) since v extrema are: (%f,%f)", uva[1], nxt_uva[1], _min_v, _max_v); err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Aborting all wrapping."); } } else { //One or both edges failed to show constant u or v //Abort any further wrapping... _wrap_bad = true; _wrap_u = false; _wrap_v = false; err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Found an INVALID wrapping. The seam wasn't constant in u or v."); err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Edge #1 (%f,%f)-(%f,%f) Edge #2 (%f,%f)-(%f,%f)", uva[0], uva[1], uvb[0], uvb[1], nxt_uvb[0], nxt_uvb[1], nxt_uva[0], nxt_uva[1]); err_mesg(ERR_LEV_WARN, "UVMapping::recurse_wrapping() - Aborting all wrapping."); } } } } } } } } }
///////////////////////////////////// // add() ///////////////////////////////////// bool HatchingGroupFixed::add( CNDCpt_list &pl, const vector<double>&prl, int curve_type ) { size_t k; double a,b; Bface *f; // It happens: if (pl.empty()) { err_mesg(ERR_LEV_ERROR, "HatchingGroupFixed:add() - Error: point list is empty!"); return false; } if (prl.empty()) { err_mesg(ERR_LEV_ERROR, "HatchingGroupFixed:add() - Error: pressure list is empty!"); return false; } if (pl.size() != prl.size()) { err_mesg(ERR_LEV_ERROR, "HatchingGroupFixed:add() - gesture pixel list and pressure list are not same length."); return false; } err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - smoothing gesture."); //Smooth the input gesture NDCpt_list smoothpts; vector<double> smoothprl; if (!smooth_gesture(pl, smoothpts, prl, smoothprl, _params.anim_style())) return false; err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - clipping gesture to model."); NDCpt_list ndcpts; vector<double> finalprl; clip_to_patch(smoothpts, ndcpts, smoothprl, finalprl); ndcpts.update_length(); err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Checking gesture silliness."); if (HatchingGroupBase::is_gesture_silly(ndcpts,_params.anim_style())) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Punting silly gesture..."); return false; } //Even if the user wants to project to create the //hatch, we continue with plane cutting to //generate a curve we can use to estimate //the mesh spacing so that the final projected //hatch is sampled evenly on the level of the mesh //spacing //Get the cutting line err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - fitting line."); if (!fit_line(ndcpts,a,b)) return false; //Slide to midpoint if desired if (Config::get_var_bool("HATCHING_GROUP_SLIDE_FIT",false,true)) b = ndcpts.interpolate(0.5)[1] - (a*ndcpts.interpolate(0.5)[0]); err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - computing plane."); //Find the cutting plane Wplane wpPlane; f = compute_cutting_plane(_patch, a, b, ndcpts, wpPlane); if (!f) return false; else { if (!f->front_facing()) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Nearest pt. on fit line hit backfacing surface."); return false; } } err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - slicing mesh."); //Intersect the mesh to get a 3D curve Wpt_list wlList; slice_mesh_with_plane(f,wpPlane,wlList); err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - cliping curve to gesture."); //Clip end of 3D curve to match gesture Wpt_list wlClipList; clip_curve_to_stroke(_patch, ndcpts, wlList, wlClipList); wlClipList.update_length(); Wpt_list wlScaledList; if (curve_type == HatchingGroup::CURVE_MODE_PROJECT) { err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Projecting to surface."); //Okay, the user wants to get literal, projected //points, so lets do it. We're careful to //toss points that hit the no/wrong mesh Wpt_list wlProjList; Wpt wloc; for (k=0; k<ndcpts.size(); k++) { f = HatchingGroupBase::find_face_vis(NDCpt(ndcpts[k]),wloc); if ((f) && (f->patch() == _patch) && (f->front_facing())) { wlProjList.push_back(wloc); } else { if (!f) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed while projecting: No hit on a mesh!"); else if (!(f->patch() == _patch)) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed while projecting: Hit wrong patch."); else if (!f->front_facing()) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed while projecting: Hit backfacing tri."); else err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed while projecting: WHAT?!?!?!?!"); } } if (wlProjList.size()<2) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed:add() - Nothing left after projection failures. Punting..."); return false; } wlProjList.update_length(); err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Resampling curve."); //Resample to even spacing in world space. Sample //at a world distance similar to wlClipList, which //will be on the order of the mesh resolution //unless the gesture fits into one triangle, //in which case we ensure a minimum sampling int guess = (int)ceil(((double)wlClipList.size()* (double)wlProjList.length())/(double)wlClipList.length()); size_t num = max(guess,5); double step = 1.0/((double)(num-1)); for (k=0 ; k<num ; k++) wlScaledList.push_back(wlProjList.interpolate((double)k*step)); } else { //CURVE_MODE_PLANE assert(curve_type == HatchingGroup::CURVE_MODE_PLANE); err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Resampling curve."); //Resample to even spacing in world space. This curve will //be sampled on the order of the mesh spacing but we'll //not allow the num of samples to drop too low in case //the gesture's on the scale of one triangle size_t num = max(wlClipList.size(), 5UL); double step = 1.0/((double)(num-1)); for (k=0 ; k<num ; k++) wlScaledList.push_back(wlClipList.interpolate((double)k*step)); } // Convert back to 2D err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed:add() - converting to 2D."); NDCZpt_list ndczlScaledList; for (k=0;k<wlScaledList.size();k++) ndczlScaledList.push_back(NDCZpt(_patch->xform()*wlScaledList[k])); ndczlScaledList.update_length(); // Calculate pixel length of hatch double pix_len = ndczlScaledList.length() * VIEW::peek()->ndc2pix_scale(); if (pix_len < 8.0) { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Stroke only %f pixels. Probably an accident. Punting...", pix_len); return false; } vector<HatchingFixedVertex> verts; Wpt_list pts; vector<Wvec> norms; err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Final sampling."); for (k=0; k<ndczlScaledList.size(); k++) { Wpt wloc; f = HatchingGroupBase::find_face_vis(NDCpt(ndczlScaledList[k]),wloc); if (f && f->patch() == _patch && f->front_facing()) { Wvec bc; Wvec norm; //f->project_barycentric(wloc,bc); f->project_barycentric_ndc(NDCpt(ndczlScaledList[k]),bc); Wvec bc_old = bc; Bsimplex::clamp_barycentric(bc); double dL = fabs(bc.length() - bc_old.length()); if (bc != bc_old) { err_mesg(ERR_LEV_INFO, "HatchingGroupFixed::add() - Baycentric clamp modified result: (%f,%f,%f) --> (%f,%f,%f) Length Change: %f", bc_old[0], bc_old[1], bc_old[2], bc[0], bc[1], bc[2], dL); } if (dL < 1e-3) { verts.push_back(HatchingFixedVertex(f->index(), bc)); f->bc2norm_blend(bc,norm); pts.push_back(wloc); norms.push_back(norm); } else { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Change too large due to error in projection. Dumping point..."); } } else { if (!f) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed in final lookup: No hit on a mesh!"); else if (!(f->patch() == _patch)) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed in final lookup: Hit wrong patch."); else if (!(f->front_facing())) err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed in final lookup: Hit backfracing tri."); else err_mesg(ERR_LEV_WARN, "HatchingGroupFixed::add() - Missed in final lookup: WHAT?!?!?!?!"); } } if (pts.size()>1) { //XXX - Okay, using the gesture pressure, but no offsets. //Need to go back and add offset generation... BaseStrokeOffsetLISTptr ol = make_shared<BaseStrokeOffsetLIST>(); ol->set_replicate(0); ol->set_hangover(1); ol->set_pix_len(pix_len); ol->push_back(BaseStrokeOffset(0.0, 0.0, finalprl[0], BaseStrokeOffset::OFFSET_TYPE_BEGIN)); for (k=1; k<finalprl.size(); k++) ol->push_back(BaseStrokeOffset((double)k / (double)(finalprl.size()-1), 0.0, finalprl[k], BaseStrokeOffset::OFFSET_TYPE_MIDDLE)); ol->push_back(BaseStrokeOffset(1.0, 0.0, finalprl[finalprl.size()-1], BaseStrokeOffset::OFFSET_TYPE_END)); if (base_level(num_base_levels()-1)->pix_size() > 0) { assert(_params.anim_style() != HatchingGroup::STYLE_MODE_NEAT); add_base_level(); // Make sure we can see it whil we're editing! base_level(num_base_levels()-1)->set_desired_frac(1.0); } base_level(num_base_levels()-1)->add_hatch( new HatchingHatchFixed( base_level(num_base_levels()-1),_patch->mesh()->pix_size(),verts,pts,norms,ol) ); return true; } else { err_mesg(ERR_LEV_WARN, "HatchingGroupFixed:add() - All lookups are bad. Punting..."); return false; } return true; }
Bsimplex* Bface::ndc_walk( CNDCpt& target, CWvec &passed_bc, CNDCpt &nearest, int is_on_tri, bool use_passed_in_params) const { // just like local_search, but in NDC space // // start from this face, move in NDC space // across the mesh to reach the target // // if reached, return the simplex that contains // the target point. // we only move if it will get us closer to the goal. Hence, we // can never wander off forever. // if can't reach it, return 0 NDCpt y (nearest); Wvec bc(passed_bc); if (!use_passed_in_params) { y = nearest_pt_ndc(target, bc, is_on_tri); } Bsimplex* sim = bc2sim(bc); if (is_on_tri) { // target is on this triangle // return the lowest-dimensional // simplex on which it lies return sim; } if (is_edge(sim)) { Bedge* e = (Bedge*)sim; Bface* f = e->is_sil() ? nullptr : e->other_face(this); if (f) { Wvec new_bc; int new_on_tri; NDCpt new_best = f->nearest_pt_ndc(target, new_bc, new_on_tri); if (new_best.dist_sqrd(target) < y.dist_sqrd(target)) return f->ndc_walk(target, new_bc, new_best, new_on_tri, true); else return nullptr; } else { return nullptr; } } // better be a vertex assert(is_vert(sim)); Bvert* v = (Bvert*)sim; if (v->degree(SilEdgeFilter()) > 0) return nullptr; Bface_list nbrs(16); ((Bvert*)sim)->get_faces(nbrs); double dist_sqrd = 1e50; Bface* best = nullptr; Wvec best_bc; NDCpt best_nearest; int best_on_tri = 0; Wvec curr_bc; NDCpt curr_nearest; int curr_on_tri=0; for (Bface_list::size_type k = 0; k < nbrs.size(); k++) { if (nbrs[k] != this) { curr_nearest = nbrs[k]->nearest_pt_ndc(target, curr_bc, curr_on_tri); if (curr_nearest.dist_sqrd(target) < dist_sqrd ) { dist_sqrd = curr_nearest.dist_sqrd(target); best_bc = curr_bc; best_on_tri = curr_on_tri; best_nearest = curr_nearest; best = nbrs[k]; } } } if (dist_sqrd < y.dist_sqrd(target)) { return best->ndc_walk(target, best_bc, best_nearest, best_on_tri, true); } return nullptr; }