MOAB: Mesh Oriented datABase
(version 5.4.1)
|
00001 /* 00002 * SmoothCurve.cpp 00003 * 00004 */ 00005 00006 #include "SmoothCurve.hpp" 00007 //#include "SmoothVertex.hpp" 00008 #include "SmoothFace.hpp" 00009 #include <cassert> 00010 #include <iostream> 00011 #include "moab/GeomTopoTool.hpp" 00012 00013 namespace moab 00014 { 00015 00016 SmoothCurve::SmoothCurve( Interface* mb, EntityHandle curve, GeomTopoTool* gTool ) 00017 : _mb( mb ), _set( curve ), _gtt( gTool ) 00018 { 00019 //_mbOut->create_meshset(MESHSET_ORDERED, _oSet); 00020 /*_cmlEdgeMesher = new CMLEdgeMesher (this, CML::STANDARD); 00021 _cmlEdgeMesher->set_sizing_function(CML::LINEAR_SIZING);*/ 00022 _leng = 0; // not initialized 00023 _edgeTag = 0; // not initialized 00024 } 00025 SmoothCurve::~SmoothCurve() 00026 { 00027 // TODO Auto-generated destructor stub 00028 } 00029 00030 double SmoothCurve::arc_length() 00031 { 00032 00033 return _leng; 00034 } 00035 00036 //! \brief Get the parametric status of the curve. 00037 //! 00038 //! \return \a true if curve is parametric, \a false otherwise. 00039 bool SmoothCurve::is_parametric() 00040 { 00041 return true; 00042 } 00043 00044 //! \brief Get the periodic status of the curve. 00045 //! 00046 //! \param period The period of the curve if periodic. 00047 //! 00048 //! \return \a true if curve is periodic, \a false otherwise. 00049 bool SmoothCurve::is_periodic( double& period ) 00050 { 00051 // assert(_ref_edge); 00052 // return _ref_edge->is_periodic( period); 00053 Range vsets; 00054 _mb->get_child_meshsets( _set, vsets ); // num_hops =1 00055 if( vsets.size() == 1 ) 00056 { 00057 period = _leng; 00058 return true; // true , especially for ice sheet data 00059 } 00060 return false; 00061 } 00062 00063 //! \brief Get the parameter range of the curve. 00064 //! 00065 //! \param u_start The beginning curve parameter 00066 //! \param u_end The ending curve parameter 00067 //! 00068 //! \note The numerical value of \a u_start may be greater 00069 //! than the numerical value of \a u_end. 00070 void SmoothCurve::get_param_range( double& u_start, double& u_end ) 00071 { 00072 // assert(_ref_edge); 00073 u_start = 0; 00074 u_end = 1.; 00075 00076 return; 00077 } 00078 00079 //! Compute the parameter value at a specified distance along the curve. 00080 //! 00081 //! \param u_root The start parameter from which to compute the distance 00082 //! along the curve. 00083 //! \param arc_length The distance to move along the curve. 00084 //! 00085 //! \note For positive values of \a arc_length the distance will be 00086 //! computed in the direction of increasing parameter value along the 00087 //! curve. For negative values of \a arc_length the distance will be 00088 //! computed in the direction of decreasing parameter value along the 00089 //! curve. 00090 //! 00091 //! \return The parametric coordinate u along the curve 00092 double SmoothCurve::u_from_arc_length( double u_root, double arc_leng ) 00093 { 00094 00095 if( _leng <= 0 ) return 0; 00096 return u_root + arc_leng / _leng; 00097 } 00098 00099 //! \brief Evaluate the curve at a specified parameter value. 00100 //! 00101 //! \param u The parameter at which to evaluate the curve 00102 //! \param x The x coordinate of the evaluated point 00103 //! \param y The y coordinate of the evaluated point 00104 //! \param z The z coordinate of the evaluated point 00105 bool SmoothCurve::position_from_u( double u, double& x, double& y, double& z, double* tg ) 00106 { 00107 00108 // _fractions are increasing, so find the 00109 double* ptr = std::lower_bound( &_fractions[0], ( &_fractions[0] ) + _fractions.size(), u ); 00110 int index = ptr - &_fractions[0]; 00111 double nextFraction = _fractions[index]; 00112 double prevFraction = 0; 00113 if( index > 0 ) 00114 { 00115 prevFraction = _fractions[index - 1]; 00116 } 00117 double t = ( u - prevFraction ) / ( nextFraction - prevFraction ); 00118 00119 EntityHandle edge = _entities[index]; 00120 00121 CartVect position, tangent; 00122 ErrorCode rval = evaluate_smooth_edge( edge, t, position, tangent ); 00123 if( MB_SUCCESS != rval ) return false; 00124 assert( rval == MB_SUCCESS ); 00125 x = position[0]; 00126 y = position[1]; 00127 z = position[2]; 00128 if( tg ) 00129 { 00130 // we need to do some scaling, 00131 double dtdu = 1 / ( nextFraction - prevFraction ); 00132 tg[0] = tangent[0] * dtdu; 00133 tg[1] = tangent[1] * dtdu; 00134 tg[2] = tangent[2] * dtdu; 00135 } 00136 00137 return true; 00138 } 00139 //! \brief Move a point near the curve to the closest point on the curve. 00140 //! 00141 //! \param x The x coordinate of the point 00142 //! \param y The y coordinate of the point 00143 //! \param z The z coordinate of the point 00144 void SmoothCurve::move_to_curve( double& x, double& y, double& z ) 00145 { 00146 00147 // find closest point to the curve, and the parametric position 00148 // must be close by, but how close ??? 00149 EntityHandle v; 00150 int edgeIndex; 00151 double u = u_from_position( x, y, z, v, edgeIndex ); 00152 position_from_u( u, x, y, z ); 00153 00154 return; 00155 } 00156 00157 //! Get the u parameter value on the curve closest to x,y,z 00158 //! and the point on the curve. 00159 //! 00160 //! \param x The x coordinate of the point 00161 //! \param y The y coordinate of the point 00162 //! \param z The z coordinate of the point 00163 //! 00164 //! \return The parametric coordinate u on the curve 00165 double SmoothCurve::u_from_position( double x, double y, double z, EntityHandle& v, int& edgeIndex ) 00166 { 00167 // this is an iterative process, expensive usually 00168 // get first all nodes , and their positions 00169 // find the closest node (and edge), and from there do some 00170 // iterations up to a point 00171 // do not exaggerate with convergence criteria 00172 00173 v = 0; // we do not have a close by vertex yet 00174 CartVect initialPos( x, y, z ); 00175 double u = 0; 00176 int nbNodes = (int)_entities.size() * 2; // the mesh edges are stored 00177 std::vector< EntityHandle > nodesConnec; 00178 nodesConnec.resize( nbNodes ); 00179 ErrorCode rval = this->_mb->get_connectivity( &( _entities[0] ), nbNodes / 2, nodesConnec ); 00180 if( MB_SUCCESS != rval ) 00181 { 00182 std::cout << "error in getting connectivity\n"; 00183 return 0; 00184 } 00185 // collapse nodesConnec, nodes should be in order 00186 for( int k = 0; k < nbNodes / 2; k++ ) 00187 { 00188 nodesConnec[k + 1] = nodesConnec[2 * k + 1]; 00189 } 00190 int numNodes = nbNodes / 2 + 1; 00191 std::vector< CartVect > coordNodes; 00192 coordNodes.resize( numNodes ); 00193 00194 rval = _mb->get_coords( &( nodesConnec[0] ), numNodes, (double*)&( coordNodes[0] ) ); 00195 if( MB_SUCCESS != rval ) 00196 { 00197 std::cout << "error in getting node positions\n"; 00198 return 0; 00199 } 00200 // find the closest node, then find the closest edge, based on closest node 00201 00202 int indexNode = 0; 00203 double minDist = 1.e30; 00204 // expensive linear search 00205 for( int i = 0; i < numNodes; i++ ) 00206 { 00207 double d1 = ( initialPos - coordNodes[i] ).length(); 00208 if( d1 < minDist ) 00209 { 00210 indexNode = i; 00211 minDist = d1; 00212 } 00213 } 00214 double tolerance = 0.00001; // what is the unit? 00215 // something reasonable 00216 if( minDist < tolerance ) 00217 { 00218 v = nodesConnec[indexNode]; 00219 // we are done, just return the proper u (from fractions) 00220 if( indexNode == 0 ) 00221 { 00222 return 0; // first node has u = 0 00223 } 00224 else 00225 return _fractions[indexNode - 1]; // fractions[0] > 0!!) 00226 } 00227 // find the mesh edge; could be previous or next edge 00228 edgeIndex = indexNode; // could be the previous one, though!! 00229 if( edgeIndex == numNodes - 1 ) 00230 edgeIndex--; // we have one less edge, and do not worry about next edge 00231 else 00232 { 00233 if( edgeIndex > 0 ) 00234 { 00235 // could be the previous; decide based on distance to the other 00236 // nodes of the 2 connected edges 00237 CartVect prevNodePos = coordNodes[edgeIndex - 1]; 00238 CartVect nextNodePos = coordNodes[edgeIndex + 1]; 00239 if( ( prevNodePos - initialPos ).length_squared() < ( nextNodePos - initialPos ).length_squared() ) 00240 { 00241 edgeIndex--; 00242 } 00243 } 00244 } 00245 // now, we know for sure that the closest point is somewhere on edgeIndex edge 00246 // 00247 00248 // do newton iteration for local t between 0 and 1 00249 00250 // copy from evaluation method 00251 CartVect P[2]; // P0 and P1 00252 CartVect controlPoints[3]; // edge control points 00253 double t4, t3, t2, one_minus_t, one_minus_t2, one_minus_t3, one_minus_t4; 00254 00255 P[0] = coordNodes[edgeIndex]; 00256 P[1] = coordNodes[edgeIndex + 1]; 00257 00258 if( 0 == _edgeTag ) 00259 { 00260 rval = _mb->tag_get_handle( "CONTROLEDGE", 9, MB_TYPE_DOUBLE, _edgeTag ); 00261 if( rval != MB_SUCCESS ) return 0; 00262 } 00263 rval = _mb->tag_get_data( _edgeTag, &( _entities[edgeIndex] ), 1, (double*)&controlPoints[0] ); 00264 if( rval != MB_SUCCESS ) return rval; 00265 00266 // starting point 00267 double tt = 0.5; // between the 2 ends of the edge 00268 int iterations = 0; 00269 // find iteratively a better point 00270 int maxIterations = 10; // not too many 00271 CartVect outv; 00272 // we will solve minimize F = 0.5 * ( ini - r(t) )^2 00273 // so solve F'(t) = 0 00274 // Iteration: t_ -> t - F'(t)/F"(t) 00275 // F'(t) = r'(t) (ini-r(t) ) 00276 // F"(t) = r"(t) (ini-r(t) ) - (r'(t))^2 00277 while( iterations < maxIterations ) 00278 // 00279 { 00280 t2 = tt * tt; 00281 t3 = t2 * tt; 00282 t4 = t3 * tt; 00283 one_minus_t = 1. - tt; 00284 one_minus_t2 = one_minus_t * one_minus_t; 00285 one_minus_t3 = one_minus_t2 * one_minus_t; 00286 one_minus_t4 = one_minus_t3 * one_minus_t; 00287 00288 outv = one_minus_t4 * P[0] + 4. * one_minus_t3 * tt * controlPoints[0] + 00289 6. * one_minus_t2 * t2 * controlPoints[1] + 4. * one_minus_t * t3 * controlPoints[2] + t4 * P[1]; 00290 00291 CartVect out_tangent = -4. * one_minus_t3 * P[0] + 00292 4. * ( one_minus_t3 - 3. * tt * one_minus_t2 ) * controlPoints[0] + 00293 12. * ( tt * one_minus_t2 - t2 * one_minus_t ) * controlPoints[1] + 00294 4. * ( 3. * t2 * one_minus_t - t3 ) * controlPoints[2] + 4. * t3 * P[1]; 00295 00296 CartVect second_deriv = 00297 12. * one_minus_t2 * P[0] + 00298 4. * ( -3. * one_minus_t2 - 3. * one_minus_t2 + 6. * tt * one_minus_t ) * controlPoints[0] + 00299 12. * ( one_minus_t2 - 4 * tt * one_minus_t + t2 ) * controlPoints[1] + 00300 4. * ( 6. * tt - 12 * t2 ) * controlPoints[2] + 12. * t2 * P[1]; 00301 CartVect diff = outv - initialPos; 00302 double F_d = out_tangent % diff; 00303 double F_dd = second_deriv % diff + out_tangent.length_squared(); 00304 00305 if( 0 == F_dd ) break; // get out, we found minimum? 00306 00307 double delta_t = -F_d / F_dd; 00308 00309 if( fabs( delta_t ) < 0.000001 ) break; 00310 tt = tt + delta_t; 00311 if( tt < 0 ) 00312 { 00313 tt = 0.; 00314 v = nodesConnec[edgeIndex]; // we are at end of mesh edge 00315 break; 00316 } 00317 if( tt > 1 ) 00318 { 00319 tt = 1; 00320 v = nodesConnec[edgeIndex + 1]; // we are at one end 00321 break; 00322 } 00323 iterations++; 00324 } 00325 // so we have t on the segment, convert to u, which should 00326 // be between _fractions[edgeIndex] numbers 00327 double prevFraction = 0; 00328 if( edgeIndex > 0 ) prevFraction = _fractions[edgeIndex - 1]; 00329 00330 u = prevFraction + tt * ( _fractions[edgeIndex] - prevFraction ); 00331 return u; 00332 } 00333 00334 //! \brief Get the starting point of the curve. 00335 //! 00336 //! \param x The x coordinate of the start point 00337 //! \param y The y coordinate of the start point 00338 //! \param z The z coordinate of the start point 00339 void SmoothCurve::start_coordinates( double& x, double& y, double& z ) 00340 { 00341 00342 int nnodes = 0; 00343 const EntityHandle* conn2 = NULL; 00344 _mb->get_connectivity( _entities[0], conn2, nnodes ); 00345 double c[3]; 00346 _mb->get_coords( conn2, 1, c ); 00347 00348 x = c[0]; 00349 y = c[1]; 00350 z = c[2]; 00351 00352 return; 00353 } 00354 00355 //! \brief Get the ending point of the curve. 00356 //! 00357 //! \param x The x coordinate of the start point 00358 //! \param y The y coordinate of the start point 00359 //! \param z The z coordinate of the start point 00360 void SmoothCurve::end_coordinates( double& x, double& y, double& z ) 00361 { 00362 00363 int nnodes = 0; 00364 const EntityHandle* conn2 = NULL; 00365 _mb->get_connectivity( _entities[_entities.size() - 1], conn2, nnodes ); 00366 double c[3]; 00367 // careful, the second node here 00368 _mb->get_coords( &conn2[1], 1, c ); 00369 00370 x = c[0]; 00371 y = c[1]; 00372 z = c[2]; 00373 return; 00374 } 00375 00376 // this will recompute the 2 tangents for each edge, considering the geo edge they are into 00377 void SmoothCurve::compute_tangents_for_each_edge() 00378 { 00379 // will retrieve the edges in each set; they are retrieved in order they were put into, because 00380 // these sets are "MESHSET_ORDERED" 00381 // retrieve the tag handle for the tangents; it should have been created already 00382 // this tangents are computed for the chain of edges that form a geometric edge 00383 // some checks should be performed on the vertices, but we trust the correctness of the model 00384 // completely (like the vertices should match in the chain...) 00385 Tag tangentsTag; 00386 ErrorCode rval = _mb->tag_get_handle( "TANGENTS", 6, MB_TYPE_DOUBLE, tangentsTag ); 00387 if( rval != MB_SUCCESS ) return; // some error should be thrown 00388 std::vector< EntityHandle > entities; 00389 _mb->get_entities_by_type( _set, MBEDGE, entities ); // no recursion!! 00390 // basically, each tangent at a node will depend on previous tangent 00391 int nbEdges = entities.size(); 00392 // now, we can advance in the loop 00393 // the only special problem is if the first node coincides with the last node, then we should 00394 // consider the closed loop; or maybe we should look at angles in that case too? 00395 // also, should we look at the 2 semi-circles case? How to decide if we need to continue the 00396 // "tangents" maybe we can do that later, and we can alter the tangents at the feature nodes, in 00397 // the directions of the loops again, do we need to decide the "closed" loop or not? Not yet... 00398 EntityHandle previousEdge = entities[0]; // this is the first edge in the chain 00399 CartVect TP[2]; // tangents for the previous edge 00400 rval = _mb->tag_get_data( tangentsTag, &previousEdge, 1, &TP[0] ); // tangents for previous edge 00401 if( rval != MB_SUCCESS ) return; // some error should be thrown 00402 CartVect TC[2]; // tangents for the current edge 00403 EntityHandle currentEdge; 00404 for( int i = 1; i < nbEdges; i++ ) 00405 { 00406 // current edge will start after first one 00407 currentEdge = entities[i]; 00408 rval = _mb->tag_get_data( tangentsTag, ¤tEdge, 1, &TC[0] ); // 00409 if( rval != MB_SUCCESS ) return; // some error should be thrown 00410 // now compute the new tangent at common vertex; reset tangents for previous edge and 00411 // current edge a little bit of CPU and memory waste, but this is life 00412 CartVect T = 0.5 * TC[0] + 0.5 * TP[1]; // 00413 T.normalize(); 00414 TP[1] = T; 00415 rval = _mb->tag_set_data( tangentsTag, &previousEdge, 1, &TP[0] ); // 00416 if( rval != MB_SUCCESS ) return; // some error should be thrown 00417 TC[0] = T; 00418 rval = _mb->tag_set_data( tangentsTag, ¤tEdge, 1, &TC[0] ); // 00419 if( rval != MB_SUCCESS ) return; // some error should be thrown 00420 // now set the next edge 00421 previousEdge = currentEdge; 00422 TP[0] = TC[0]; 00423 TP[1] = TC[1]; 00424 } 00425 return; 00426 } 00427 00428 void SmoothCurve::compute_control_points_on_boundary_edges( double, 00429 std::map< EntityHandle, SmoothFace* >& mapSurfaces, 00430 Tag controlPointsTag, 00431 Tag markTag ) 00432 { 00433 // these points really need the surfaces they belong to, because the control points on edges 00434 // depend on the normals on surfaces 00435 // the control points are averaged from different surfaces, by simple mean average 00436 // the surfaces have 00437 // do we really need min_dot here? 00438 // first of all, find out the SmoothFace for each surface set that is adjacent here 00439 // GeomTopoTool gTopoTool(_mb); 00440 std::vector< EntityHandle > faces; 00441 std::vector< int > senses; 00442 ErrorCode rval = _gtt->get_senses( _set, faces, senses ); 00443 if( MB_SUCCESS != rval ) return; 00444 00445 // need to find the smooth face attached 00446 unsigned int numSurfacesAdjacent = faces.size(); 00447 // get the edges, and then get the 00448 // std::vector<EntityHandle> entities; 00449 _mb->get_entities_by_type( _set, MBEDGE, _entities ); // no recursion!! 00450 // each edge has the tangent computed already 00451 Tag tangentsTag; 00452 rval = _mb->tag_get_handle( "TANGENTS", 6, MB_TYPE_DOUBLE, tangentsTag ); 00453 if( rval != MB_SUCCESS ) return; // some error should be thrown 00454 00455 // we do not want to search every time 00456 std::vector< SmoothFace* > smoothFaceArray; 00457 unsigned int i = 0; 00458 for( i = 0; i < numSurfacesAdjacent; i++ ) 00459 { 00460 SmoothFace* sms = mapSurfaces[faces[i]]; 00461 smoothFaceArray.push_back( sms ); 00462 } 00463 00464 unsigned int e = 0; 00465 for( e = 0; e < _entities.size(); e++ ) 00466 { 00467 CartVect zero( 0. ); 00468 CartVect ctrlP[3] = { zero, zero, zero }; // null positions initially 00469 // the control points are averaged from connected faces 00470 EntityHandle edge = _entities[e]; // the edge in the chain 00471 00472 int nnodes; 00473 const EntityHandle* conn2; 00474 rval = _mb->get_connectivity( edge, conn2, nnodes ); 00475 if( rval != MB_SUCCESS || 2 != nnodes ) return; // or continue or return error 00476 00477 // double coords[6]; // store the coordinates for the nodes 00478 CartVect P[2]; 00479 // ErrorCode rval = _mb->get_coords(conn2, 2, coords); 00480 rval = _mb->get_coords( conn2, 2, (double*)&P[0] ); 00481 if( rval != MB_SUCCESS ) return; 00482 00483 CartVect chord = P[1] - P[0]; 00484 _leng += chord.length(); 00485 _fractions.push_back( _leng ); 00486 CartVect N[2]; 00487 00488 // MBCartVect N0(&normalVec[0]); 00489 // MBCartVect N3(&normalVec[3]); 00490 CartVect T[2]; // T0, T3 00491 // if (edge->num_adj_facets() <= 1) { 00492 // stat = compute_curve_tangent(edge, min_dot, T0, T3); 00493 // if (stat != CUBIT_SUCCESS) 00494 // return stat; 00495 //} else { 00496 //} 00497 rval = _mb->tag_get_data( tangentsTag, &edge, 1, &T[0] ); 00498 if( rval != MB_SUCCESS ) return; 00499 00500 for( i = 0; i < numSurfacesAdjacent; i++ ) 00501 { 00502 CartVect controlForEdge[3]; 00503 rval = smoothFaceArray[i]->get_normals_for_vertices( conn2, N ); 00504 if( rval != MB_SUCCESS ) return; 00505 00506 rval = smoothFaceArray[i]->init_edge_control_points( P[0], P[1], N[0], N[1], T[0], T[1], controlForEdge ); 00507 if( rval != MB_SUCCESS ) return; 00508 00509 // accumulate those over faces!!! 00510 for( int j = 0; j < 3; j++ ) 00511 { 00512 ctrlP[j] += controlForEdge[j]; 00513 } 00514 } 00515 // now divide them for the average position! 00516 for( int j = 0; j < 3; j++ ) 00517 { 00518 ctrlP[j] /= numSurfacesAdjacent; 00519 } 00520 // we are done, set the control points now! 00521 // edge->control_points(ctrl_pts, 4); 00522 rval = _mb->tag_set_data( controlPointsTag, &edge, 1, &ctrlP[0] ); 00523 if( rval != MB_SUCCESS ) return; 00524 00525 this->_edgeTag = controlPointsTag; // this is a tag that will be stored with the edge 00526 // is that a waste of memory or not... 00527 // also mark the edge for later on 00528 unsigned char used = 1; 00529 _mb->tag_set_data( markTag, &edge, 1, &used ); 00530 } 00531 // now divide fractions, to make them vary from 0 to 1 00532 assert( _leng > 0. ); 00533 for( e = 0; e < _entities.size(); e++ ) 00534 _fractions[e] /= _leng; 00535 } 00536 00537 ErrorCode SmoothCurve::evaluate_smooth_edge( EntityHandle eh, double& tt, CartVect& outv, CartVect& out_tangent ) 00538 { 00539 CartVect P[2]; // P0 and P1 00540 CartVect controlPoints[3]; // edge control points 00541 double t4, t3, t2, one_minus_t, one_minus_t2, one_minus_t3, one_minus_t4; 00542 00543 // project the position to the linear edge 00544 // t is from 0 to 1 only!! 00545 // double tt = (t + 1) * 0.5; 00546 if( tt <= 0.0 ) tt = 0.0; 00547 if( tt >= 1.0 ) tt = 1.0; 00548 00549 int nnodes = 0; 00550 const EntityHandle* conn2 = NULL; 00551 ErrorCode rval = _mb->get_connectivity( eh, conn2, nnodes ); 00552 if( rval != MB_SUCCESS ) return rval; 00553 00554 rval = _mb->get_coords( conn2, 2, (double*)&P[0] ); 00555 if( rval != MB_SUCCESS ) return rval; 00556 00557 if( 0 == _edgeTag ) 00558 { 00559 rval = _mb->tag_get_handle( "CONTROLEDGE", 9, MB_TYPE_DOUBLE, _edgeTag ); 00560 if( rval != MB_SUCCESS ) return rval; 00561 } 00562 rval = _mb->tag_get_data( _edgeTag, &eh, 1, (double*)&controlPoints[0] ); 00563 if( rval != MB_SUCCESS ) return rval; 00564 00565 t2 = tt * tt; 00566 t3 = t2 * tt; 00567 t4 = t3 * tt; 00568 one_minus_t = 1. - tt; 00569 one_minus_t2 = one_minus_t * one_minus_t; 00570 one_minus_t3 = one_minus_t2 * one_minus_t; 00571 one_minus_t4 = one_minus_t3 * one_minus_t; 00572 00573 outv = one_minus_t4 * P[0] + 4. * one_minus_t3 * tt * controlPoints[0] + 6. * one_minus_t2 * t2 * controlPoints[1] + 00574 4. * one_minus_t * t3 * controlPoints[2] + t4 * P[1]; 00575 00576 out_tangent = -4. * one_minus_t3 * P[0] + 4. * ( one_minus_t3 - 3. * tt * one_minus_t2 ) * controlPoints[0] + 00577 12. * ( tt * one_minus_t2 - t2 * one_minus_t ) * controlPoints[1] + 00578 4. * ( 3. * t2 * one_minus_t - t3 ) * controlPoints[2] + 4. * t3 * P[1]; 00579 return MB_SUCCESS; 00580 } 00581 } // namespace moab