void LMESH::fit(ARRAY<Lvert*>& verts, bool do_gauss_seidel) { static bool debug = Config::get_var_bool("DEBUG_LMESH_FIT",false); static bool move_along_normal = Config::get_var_bool("FITTING_MOVE_ALONG_NORMAL",false); if (verts.empty()) return; // calculate the bounding box of the vertices BBOX box; int i; for (i=0; i<verts.num(); i++) box.update(verts[i]->loc()); double max_err = box.dim().length() * 1e-5; int n = verts.num(); // get original control point locations ARRAY<Wpt> C(n); // original control points ARRAY<Wpt> L(n); // current limit points for (i=0; i<n; i++) { C += verts[i]->loc(); L += Wpt::Origin(); } if(debug) { cerr << "LMESH::fit- fitting " << n << " vertices"<<endl; cerr << "Max_err = " << max_err <<endl; } // do 50 iterations... double prev_err = 0; ARRAY<double> errors; for (int k=0; k<50; k++) { errors.clear(); double err = 0; if (do_gauss_seidel) { // Gauss-Seidel iteration: use updated values from the // current iteration as they are computed... for (int j=0; j<n; j++) { // don't need that L[] array... Wpt limit; verts[j]->limit_loc(limit); Wvec delt = C[j] - limit; errors+=delt.length(); err += delt.length(); if(move_along_normal) delt = delt*verts[j]->norm()*verts[j]->norm(); verts[j]->offset_loc(delt); } } else { // compute the new offsets from the offsets computed in the // previous iteration int j; for (j=0; j<n; j++) verts[j]->limit_loc(L[j]); for (j=0; j<n; j++) { Wvec delt = C[j] - L[j]; err += delt.length(); errors+=delt.length(); if(move_along_normal) delt = delt*verts[j]->norm()*verts[j]->norm(); verts[j]->offset_loc(delt); } } // compute the average error: err /= n; double avg,std_d,max,min; if (debug) { if (prev_err != 0) { err_msg("Iter %d: avg error: %f, reduction: %f", k, err, err/prev_err); statistics(errors,true,&avg,&std_d,&max,&min); } else { err_msg("Iter %d: avg error: %f", k, err); statistics(errors,true,&avg,&std_d,&max,&min); } } else statistics(errors,false,&avg,&std_d,&max,&min); prev_err = err; if (max < max_err) { if(debug) cerr << "Terminating at " << k <<" th iterations"<<endl; return; } } }
///////////////////////////////////// // add() ///////////////////////////////////// bool HatchingGroupFixed::add( CNDCpt_list &pl, const ARRAY<double>&prl, int curve_type ) { int 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.num() != prl.num()) { 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; ARRAY<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; ARRAY<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.num(); k++) { f = HatchingGroupBase::find_face_vis(NDCpt(ndcpts[k]),wloc); if ((f) && (f->patch() == _patch) && (f->front_facing())) { wlProjList += 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.num()<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.num()* (double)wlProjList.length())/(double)wlClipList.length()); int num = max(guess,5); double step = 1.0/((double)(num-1)); for (k=0 ; k<num ; k++) wlScaledList += 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 int num = max(wlClipList.num(),5); double step = 1.0/((double)(num-1)); for (k=0 ; k<num ; k++) wlScaledList += 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.num();k++) ndczlScaledList += 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; } ARRAY<HatchingFixedVertex> verts; Wpt_list pts; ARRAY<Wvec> norms; err_mesg_cond(debug, ERR_LEV_SPAM, "HatchingGroupFixed::add() - Final sampling."); for (k=0; k<ndczlScaledList.num(); 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 += HatchingFixedVertex(f->index(),bc); f->bc2norm_blend(bc,norm); pts += wloc; norms += 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.num()>1) { //XXX - Okay, using the gesture pressure, but no offsets. //Need to go back and add offset generation... BaseStrokeOffsetLISTptr ol = new BaseStrokeOffsetLIST; ol->set_replicate(0); ol->set_hangover(1); ol->set_pix_len(pix_len); ol->add(BaseStrokeOffset( 0.0, 0.0, finalprl[0], BaseStrokeOffset::OFFSET_TYPE_BEGIN)); for (k=1; k< finalprl.num(); k++) ol->add(BaseStrokeOffset( (double)k/(double)(finalprl.num()-1), 0.0, finalprl[k], BaseStrokeOffset::OFFSET_TYPE_MIDDLE)); ol->add(BaseStrokeOffset( 1.0, 0.0, finalprl[finalprl.num()-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; }