1 /* 2 Bullet Continuous Collision Detection and Physics Library 3 Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ 4 5 This software is provided 'as-is', without any express or implied warranty. 6 In no event will the authors be held liable for any damages arising from the use of this software. 7 Permission is granted to anyone to use this software for any purpose, 8 including commercial applications, and to alter it and redistribute it freely, 9 subject to the following restrictions: 10 11 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 12 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 13 3. This notice may not be removed or altered from any source distribution. 14 */ 15 ///btSoftBodyHelpers.cpp by Nathanael Presson 16 17 #include "btSoftBodyInternals.h" 18 #include <stdio.h> 19 #include <string.h> 20 #include "btSoftBodyHelpers.h" 21 #include "LinearMath/btConvexHull.h" 22 #include "LinearMath/btConvexHullComputer.h" 23 24 25 // 26 static void drawVertex( btIDebugDraw* idraw, 27 const btVector3& x,btScalar s,const btVector3& c) 28 { 29 idraw->drawLine(x-btVector3(s,0,0),x+btVector3(s,0,0),c); 30 idraw->drawLine(x-btVector3(0,s,0),x+btVector3(0,s,0),c); 31 idraw->drawLine(x-btVector3(0,0,s),x+btVector3(0,0,s),c); 32 } 33 34 // 35 static void drawBox( btIDebugDraw* idraw, 36 const btVector3& mins, 37 const btVector3& maxs, 38 const btVector3& color) 39 { 40 const btVector3 c[]={ btVector3(mins.x(),mins.y(),mins.z()), 41 btVector3(maxs.x(),mins.y(),mins.z()), 42 btVector3(maxs.x(),maxs.y(),mins.z()), 43 btVector3(mins.x(),maxs.y(),mins.z()), 44 btVector3(mins.x(),mins.y(),maxs.z()), 45 btVector3(maxs.x(),mins.y(),maxs.z()), 46 btVector3(maxs.x(),maxs.y(),maxs.z()), 47 btVector3(mins.x(),maxs.y(),maxs.z())}; 48 idraw->drawLine(c[0],c[1],color);idraw->drawLine(c[1],c[2],color); 49 idraw->drawLine(c[2],c[3],color);idraw->drawLine(c[3],c[0],color); 50 idraw->drawLine(c[4],c[5],color);idraw->drawLine(c[5],c[6],color); 51 idraw->drawLine(c[6],c[7],color);idraw->drawLine(c[7],c[4],color); 52 idraw->drawLine(c[0],c[4],color);idraw->drawLine(c[1],c[5],color); 53 idraw->drawLine(c[2],c[6],color);idraw->drawLine(c[3],c[7],color); 54 } 55 56 // 57 static void drawTree( btIDebugDraw* idraw, 58 const btDbvtNode* node, 59 int depth, 60 const btVector3& ncolor, 61 const btVector3& lcolor, 62 int mindepth, 63 int maxdepth) 64 { 65 if(node) 66 { 67 if(node->isinternal()&&((depth<maxdepth)||(maxdepth<0))) 68 { 69 drawTree(idraw,node->childs[0],depth+1,ncolor,lcolor,mindepth,maxdepth); 70 drawTree(idraw,node->childs[1],depth+1,ncolor,lcolor,mindepth,maxdepth); 71 } 72 if(depth>=mindepth) 73 { 74 const btScalar scl=(btScalar)(node->isinternal()?1:1); 75 const btVector3 mi=node->volume.Center()-node->volume.Extents()*scl; 76 const btVector3 mx=node->volume.Center()+node->volume.Extents()*scl; 77 drawBox(idraw,mi,mx,node->isleaf()?lcolor:ncolor); 78 } 79 } 80 } 81 82 // 83 template <typename T> 84 static inline T sum(const btAlignedObjectArray<T>& items) 85 { 86 T v; 87 if(items.size()) 88 { 89 v=items[0]; 90 for(int i=1,ni=items.size();i<ni;++i) 91 { 92 v+=items[i]; 93 } 94 } 95 return(v); 96 } 97 98 // 99 template <typename T,typename Q> 100 static inline void add(btAlignedObjectArray<T>& items,const Q& value) 101 { 102 for(int i=0,ni=items.size();i<ni;++i) 103 { 104 items[i]+=value; 105 } 106 } 107 108 // 109 template <typename T,typename Q> 110 static inline void mul(btAlignedObjectArray<T>& items,const Q& value) 111 { 112 for(int i=0,ni=items.size();i<ni;++i) 113 { 114 items[i]*=value; 115 } 116 } 117 118 // 119 template <typename T> 120 static inline T average(const btAlignedObjectArray<T>& items) 121 { 122 const btScalar n=(btScalar)(items.size()>0?items.size():1); 123 return(sum(items)/n); 124 } 125 126 // 127 static inline btScalar tetravolume(const btVector3& x0, 128 const btVector3& x1, 129 const btVector3& x2, 130 const btVector3& x3) 131 { 132 const btVector3 a=x1-x0; 133 const btVector3 b=x2-x0; 134 const btVector3 c=x3-x0; 135 return(btDot(a,btCross(b,c))); 136 } 137 138 // 139 #if 0 140 static btVector3 stresscolor(btScalar stress) 141 { 142 static const btVector3 spectrum[]= { btVector3(1,0,1), 143 btVector3(0,0,1), 144 btVector3(0,1,1), 145 btVector3(0,1,0), 146 btVector3(1,1,0), 147 btVector3(1,0,0), 148 btVector3(1,0,0)}; 149 static const int ncolors=sizeof(spectrum)/sizeof(spectrum[0])-1; 150 static const btScalar one=1; 151 stress=btMax<btScalar>(0,btMin<btScalar>(1,stress))*ncolors; 152 const int sel=(int)stress; 153 const btScalar frc=stress-sel; 154 return(spectrum[sel]+(spectrum[sel+1]-spectrum[sel])*frc); 155 } 156 #endif 157 158 // 159 void btSoftBodyHelpers::Draw( btSoftBody* psb, 160 btIDebugDraw* idraw, 161 int drawflags) 162 { 163 const btScalar scl=(btScalar)0.1; 164 const btScalar nscl=scl*5; 165 const btVector3 lcolor=btVector3(0,0,0); 166 const btVector3 ncolor=btVector3(1,1,1); 167 const btVector3 ccolor=btVector3(1,0,0); 168 int i,j,nj; 169 170 /* Clusters */ 171 if(0!=(drawflags&fDrawFlags::Clusters)) 172 { 173 srand(1806); 174 for(i=0;i<psb->m_clusters.size();++i) 175 { 176 if(psb->m_clusters[i]->m_collide) 177 { 178 btVector3 color( rand()/(btScalar)RAND_MAX, 179 rand()/(btScalar)RAND_MAX, 180 rand()/(btScalar)RAND_MAX); 181 color=color.normalized()*0.75; 182 btAlignedObjectArray<btVector3> vertices; 183 vertices.resize(psb->m_clusters[i]->m_nodes.size()); 184 for(j=0,nj=vertices.size();j<nj;++j) 185 { 186 vertices[j]=psb->m_clusters[i]->m_nodes[j]->m_x; 187 } 188 #define USE_NEW_CONVEX_HULL_COMPUTER 189 #ifdef USE_NEW_CONVEX_HULL_COMPUTER 190 btConvexHullComputer computer; 191 int stride = sizeof(btVector3); 192 int count = vertices.size(); 193 btScalar shrink=0.f; 194 btScalar shrinkClamp=0.f; 195 computer.compute(&vertices[0].getX(),stride,count,shrink,shrinkClamp); 196 for (int i=0;i<computer.faces.size();i++) 197 { 198 199 int face = computer.faces[i]; 200 //printf("face=%d\n",face); 201 const btConvexHullComputer::Edge* firstEdge = &computer.edges[face]; 202 const btConvexHullComputer::Edge* edge = firstEdge->getNextEdgeOfFace(); 203 204 int v0 = firstEdge->getSourceVertex(); 205 int v1 = firstEdge->getTargetVertex(); 206 while (edge!=firstEdge) 207 { 208 int v2 = edge->getTargetVertex(); 209 idraw->drawTriangle(computer.vertices[v0],computer.vertices[v1],computer.vertices[v2],color,1); 210 edge = edge->getNextEdgeOfFace(); 211 v0=v1; 212 v1=v2; 213 }; 214 } 215 #else 216 217 HullDesc hdsc(QF_TRIANGLES,vertices.size(),&vertices[0]); 218 HullResult hres; 219 HullLibrary hlib; 220 hdsc.mMaxVertices=vertices.size(); 221 hlib.CreateConvexHull(hdsc,hres); 222 const btVector3 center=average(hres.m_OutputVertices); 223 add(hres.m_OutputVertices,-center); 224 mul(hres.m_OutputVertices,(btScalar)1); 225 add(hres.m_OutputVertices,center); 226 for(j=0;j<(int)hres.mNumFaces;++j) 227 { 228 const int idx[]={hres.m_Indices[j*3+0],hres.m_Indices[j*3+1],hres.m_Indices[j*3+2]}; 229 idraw->drawTriangle(hres.m_OutputVertices[idx[0]], 230 hres.m_OutputVertices[idx[1]], 231 hres.m_OutputVertices[idx[2]], 232 color,1); 233 } 234 hlib.ReleaseResult(hres); 235 #endif 236 237 } 238 /* Velocities */ 239 #if 0 240 for(int j=0;j<psb->m_clusters[i].m_nodes.size();++j) 241 { 242 const btSoftBody::Cluster& c=psb->m_clusters[i]; 243 const btVector3 r=c.m_nodes[j]->m_x-c.m_com; 244 const btVector3 v=c.m_lv+btCross(c.m_av,r); 245 idraw->drawLine(c.m_nodes[j]->m_x,c.m_nodes[j]->m_x+v,btVector3(1,0,0)); 246 } 247 #endif 248 /* Frame */ 249 // btSoftBody::Cluster& c=*psb->m_clusters[i]; 250 // idraw->drawLine(c.m_com,c.m_framexform*btVector3(10,0,0),btVector3(1,0,0)); 251 // idraw->drawLine(c.m_com,c.m_framexform*btVector3(0,10,0),btVector3(0,1,0)); 252 // idraw->drawLine(c.m_com,c.m_framexform*btVector3(0,0,10),btVector3(0,0,1)); 253 } 254 } 255 else 256 { 257 /* Nodes */ 258 if(0!=(drawflags&fDrawFlags::Nodes)) 259 { 260 for(i=0;i<psb->m_nodes.size();++i) 261 { 262 const btSoftBody::Node& n=psb->m_nodes[i]; 263 if(0==(n.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 264 idraw->drawLine(n.m_x-btVector3(scl,0,0),n.m_x+btVector3(scl,0,0),btVector3(1,0,0)); 265 idraw->drawLine(n.m_x-btVector3(0,scl,0),n.m_x+btVector3(0,scl,0),btVector3(0,1,0)); 266 idraw->drawLine(n.m_x-btVector3(0,0,scl),n.m_x+btVector3(0,0,scl),btVector3(0,0,1)); 267 } 268 } 269 /* Links */ 270 if(0!=(drawflags&fDrawFlags::Links)) 271 { 272 for(i=0;i<psb->m_links.size();++i) 273 { 274 const btSoftBody::Link& l=psb->m_links[i]; 275 if(0==(l.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 276 idraw->drawLine(l.m_n[0]->m_x,l.m_n[1]->m_x,lcolor); 277 } 278 } 279 /* Normals */ 280 if(0!=(drawflags&fDrawFlags::Normals)) 281 { 282 for(i=0;i<psb->m_nodes.size();++i) 283 { 284 const btSoftBody::Node& n=psb->m_nodes[i]; 285 if(0==(n.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 286 const btVector3 d=n.m_n*nscl; 287 idraw->drawLine(n.m_x,n.m_x+d,ncolor); 288 idraw->drawLine(n.m_x,n.m_x-d,ncolor*0.5); 289 } 290 } 291 /* Contacts */ 292 if(0!=(drawflags&fDrawFlags::Contacts)) 293 { 294 static const btVector3 axis[]={btVector3(1,0,0), 295 btVector3(0,1,0), 296 btVector3(0,0,1)}; 297 for(i=0;i<psb->m_rcontacts.size();++i) 298 { 299 const btSoftBody::RContact& c=psb->m_rcontacts[i]; 300 const btVector3 o= c.m_node->m_x-c.m_cti.m_normal* 301 (btDot(c.m_node->m_x,c.m_cti.m_normal)+c.m_cti.m_offset); 302 const btVector3 x=btCross(c.m_cti.m_normal,axis[c.m_cti.m_normal.minAxis()]).normalized(); 303 const btVector3 y=btCross(x,c.m_cti.m_normal).normalized(); 304 idraw->drawLine(o-x*nscl,o+x*nscl,ccolor); 305 idraw->drawLine(o-y*nscl,o+y*nscl,ccolor); 306 idraw->drawLine(o,o+c.m_cti.m_normal*nscl*3,btVector3(1,1,0)); 307 } 308 } 309 /* Faces */ 310 if(0!=(drawflags&fDrawFlags::Faces)) 311 { 312 const btScalar scl=(btScalar)0.8; 313 const btScalar alp=(btScalar)1; 314 const btVector3 col(0,(btScalar)0.7,0); 315 for(i=0;i<psb->m_faces.size();++i) 316 { 317 const btSoftBody::Face& f=psb->m_faces[i]; 318 if(0==(f.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 319 const btVector3 x[]={f.m_n[0]->m_x,f.m_n[1]->m_x,f.m_n[2]->m_x}; 320 const btVector3 c=(x[0]+x[1]+x[2])/3; 321 idraw->drawTriangle((x[0]-c)*scl+c, 322 (x[1]-c)*scl+c, 323 (x[2]-c)*scl+c, 324 col,alp); 325 } 326 } 327 /* Tetras */ 328 if(0!=(drawflags&fDrawFlags::Tetras)) 329 { 330 const btScalar scl=(btScalar)0.8; 331 const btScalar alp=(btScalar)1; 332 const btVector3 col((btScalar)0.3,(btScalar)0.3,(btScalar)0.7); 333 for(int i=0;i<psb->m_tetras.size();++i) 334 { 335 const btSoftBody::Tetra& t=psb->m_tetras[i]; 336 if(0==(t.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 337 const btVector3 x[]={t.m_n[0]->m_x,t.m_n[1]->m_x,t.m_n[2]->m_x,t.m_n[3]->m_x}; 338 const btVector3 c=(x[0]+x[1]+x[2]+x[3])/4; 339 idraw->drawTriangle((x[0]-c)*scl+c,(x[1]-c)*scl+c,(x[2]-c)*scl+c,col,alp); 340 idraw->drawTriangle((x[0]-c)*scl+c,(x[1]-c)*scl+c,(x[3]-c)*scl+c,col,alp); 341 idraw->drawTriangle((x[1]-c)*scl+c,(x[2]-c)*scl+c,(x[3]-c)*scl+c,col,alp); 342 idraw->drawTriangle((x[2]-c)*scl+c,(x[0]-c)*scl+c,(x[3]-c)*scl+c,col,alp); 343 } 344 } 345 } 346 /* Anchors */ 347 if(0!=(drawflags&fDrawFlags::Anchors)) 348 { 349 for(i=0;i<psb->m_anchors.size();++i) 350 { 351 const btSoftBody::Anchor& a=psb->m_anchors[i]; 352 const btVector3 q=a.m_body->getWorldTransform()*a.m_local; 353 drawVertex(idraw,a.m_node->m_x,0.25,btVector3(1,0,0)); 354 drawVertex(idraw,q,0.25,btVector3(0,1,0)); 355 idraw->drawLine(a.m_node->m_x,q,btVector3(1,1,1)); 356 } 357 for(i=0;i<psb->m_nodes.size();++i) 358 { 359 const btSoftBody::Node& n=psb->m_nodes[i]; 360 if(0==(n.m_material->m_flags&btSoftBody::fMaterial::DebugDraw)) continue; 361 if(n.m_im<=0) 362 { 363 drawVertex(idraw,n.m_x,0.25,btVector3(1,0,0)); 364 } 365 } 366 } 367 368 369 /* Notes */ 370 if(0!=(drawflags&fDrawFlags::Notes)) 371 { 372 for(i=0;i<psb->m_notes.size();++i) 373 { 374 const btSoftBody::Note& n=psb->m_notes[i]; 375 btVector3 p=n.m_offset; 376 for(int j=0;j<n.m_rank;++j) 377 { 378 p+=n.m_nodes[j]->m_x*n.m_coords[j]; 379 } 380 idraw->draw3dText(p,n.m_text); 381 } 382 } 383 /* Node tree */ 384 if(0!=(drawflags&fDrawFlags::NodeTree)) DrawNodeTree(psb,idraw); 385 /* Face tree */ 386 if(0!=(drawflags&fDrawFlags::FaceTree)) DrawFaceTree(psb,idraw); 387 /* Cluster tree */ 388 if(0!=(drawflags&fDrawFlags::ClusterTree)) DrawClusterTree(psb,idraw); 389 /* Joints */ 390 if(0!=(drawflags&fDrawFlags::Joints)) 391 { 392 for(i=0;i<psb->m_joints.size();++i) 393 { 394 const btSoftBody::Joint* pj=psb->m_joints[i]; 395 switch(pj->Type()) 396 { 397 case btSoftBody::Joint::eType::Linear: 398 { 399 const btSoftBody::LJoint* pjl=(const btSoftBody::LJoint*)pj; 400 const btVector3 a0=pj->m_bodies[0].xform()*pjl->m_refs[0]; 401 const btVector3 a1=pj->m_bodies[1].xform()*pjl->m_refs[1]; 402 idraw->drawLine(pj->m_bodies[0].xform().getOrigin(),a0,btVector3(1,1,0)); 403 idraw->drawLine(pj->m_bodies[1].xform().getOrigin(),a1,btVector3(0,1,1)); 404 drawVertex(idraw,a0,0.25,btVector3(1,1,0)); 405 drawVertex(idraw,a1,0.25,btVector3(0,1,1)); 406 } 407 break; 408 case btSoftBody::Joint::eType::Angular: 409 { 410 //const btSoftBody::AJoint* pja=(const btSoftBody::AJoint*)pj; 411 const btVector3 o0=pj->m_bodies[0].xform().getOrigin(); 412 const btVector3 o1=pj->m_bodies[1].xform().getOrigin(); 413 const btVector3 a0=pj->m_bodies[0].xform().getBasis()*pj->m_refs[0]; 414 const btVector3 a1=pj->m_bodies[1].xform().getBasis()*pj->m_refs[1]; 415 idraw->drawLine(o0,o0+a0*10,btVector3(1,1,0)); 416 idraw->drawLine(o0,o0+a1*10,btVector3(1,1,0)); 417 idraw->drawLine(o1,o1+a0*10,btVector3(0,1,1)); 418 idraw->drawLine(o1,o1+a1*10,btVector3(0,1,1)); 419 break; 420 } 421 default: 422 { 423 } 424 425 } 426 } 427 } 428 } 429 430 // 431 void btSoftBodyHelpers::DrawInfos( btSoftBody* psb, 432 btIDebugDraw* idraw, 433 bool masses, 434 bool areas, 435 bool /*stress*/) 436 { 437 for(int i=0;i<psb->m_nodes.size();++i) 438 { 439 const btSoftBody::Node& n=psb->m_nodes[i]; 440 char text[2048]={0}; 441 char buff[1024]; 442 if(masses) 443 { 444 sprintf(buff," M(%.2f)",1/n.m_im); 445 strcat(text,buff); 446 } 447 if(areas) 448 { 449 sprintf(buff," A(%.2f)",n.m_area); 450 strcat(text,buff); 451 } 452 if(text[0]) idraw->draw3dText(n.m_x,text); 453 } 454 } 455 456 // 457 void btSoftBodyHelpers::DrawNodeTree( btSoftBody* psb, 458 btIDebugDraw* idraw, 459 int mindepth, 460 int maxdepth) 461 { 462 drawTree(idraw,psb->m_ndbvt.m_root,0,btVector3(1,0,1),btVector3(1,1,1),mindepth,maxdepth); 463 } 464 465 // 466 void btSoftBodyHelpers::DrawFaceTree( btSoftBody* psb, 467 btIDebugDraw* idraw, 468 int mindepth, 469 int maxdepth) 470 { 471 drawTree(idraw,psb->m_fdbvt.m_root,0,btVector3(0,1,0),btVector3(1,0,0),mindepth,maxdepth); 472 } 473 474 // 475 void btSoftBodyHelpers::DrawClusterTree( btSoftBody* psb, 476 btIDebugDraw* idraw, 477 int mindepth, 478 int maxdepth) 479 { 480 drawTree(idraw,psb->m_cdbvt.m_root,0,btVector3(0,1,1),btVector3(1,0,0),mindepth,maxdepth); 481 } 482 483 484 //The btSoftBody object from the BulletSDK includes an array of Nodes and Links. These links appear 485 // to be first set up to connect a node to between 5 and 6 of its neighbors [480 links], 486 //and then to the rest of the nodes after the execution of the Floyd-Warshall graph algorithm 487 //[another 930 links]. 488 //The way the links are stored by default, we have a number of cases where adjacent links share a node in common 489 // - this leads to the creation of a data dependency through memory. 490 //The PSolve_Links() function reads and writes nodes as it iterates over each link. 491 //So, we now have the possibility of a data dependency between iteration X 492 //that processes link L with iteration X+1 that processes link L+1 493 //because L and L+1 have one node in common, and iteration X updates the positions of that node, 494 //and iteration X+1 reads in the position of that shared node. 495 // 496 //Such a memory dependency limits the ability of a modern CPU to speculate beyond 497 //a certain point because it has to respect a possible dependency 498 //- this prevents the CPU from making full use of its out-of-order resources. 499 //If we re-order the links such that we minimize the cases where a link L and L+1 share a common node, 500 //we create a temporal gap between when the node position is written, 501 //and when it is subsequently read. This in turn allows the CPU to continue execution without 502 //risking a dependency violation. Such a reordering would result in significant speedups on 503 //modern CPUs with lots of execution resources. 504 //In our testing, we see it have a tremendous impact not only on the A7, 505 //but also on all x86 cores that ship with modern Macs. 506 //The attached source file includes a single function (ReoptimizeLinkOrder) which can be called on a 507 //btSoftBody object in the solveConstraints() function before the actual solver is invoked, 508 //or right after generateBendingConstraints() once we have all 1410 links. 509 510 511 //=================================================================== 512 // 513 // 514 // This function takes in a list of interdependent Links and tries 515 // to maximize the distance between calculation 516 // of dependent links. This increases the amount of parallelism that can 517 // be exploited by out-of-order instruction processors with large but 518 // (inevitably) finite instruction windows. 519 // 520 //=================================================================== 521 522 // A small structure to track lists of dependent link calculations 523 class LinkDeps_t { 524 public: 525 int value; // A link calculation that is dependent on this one 526 // Positive values = "input A" while negative values = "input B" 527 LinkDeps_t *next; // Next dependence in the list 528 }; 529 typedef LinkDeps_t *LinkDepsPtr_t; 530 531 // Dependency list constants 532 #define REOP_NOT_DEPENDENT -1 533 #define REOP_NODE_COMPLETE -2 // Must be less than REOP_NOT_DEPENDENT 534 535 536 void btSoftBodyHelpers::ReoptimizeLinkOrder(btSoftBody *psb /* This can be replaced by a btSoftBody pointer */) 537 { 538 int i, nLinks=psb->m_links.size(), nNodes=psb->m_nodes.size(); 539 btSoftBody::Link *lr; 540 int ar, br; 541 btSoftBody::Node *node0 = &(psb->m_nodes[0]); 542 btSoftBody::Node *node1 = &(psb->m_nodes[1]); 543 LinkDepsPtr_t linkDep; 544 int readyListHead, readyListTail, linkNum, linkDepFrees, depLink; 545 546 // Allocate temporary buffers 547 int *nodeWrittenAt = new int[nNodes+1]; // What link calculation produced this node's current values? 548 int *linkDepA = new int[nLinks]; // Link calculation input is dependent upon prior calculation #N 549 int *linkDepB = new int[nLinks]; 550 int *readyList = new int[nLinks]; // List of ready-to-process link calculations (# of links, maximum) 551 LinkDeps_t *linkDepFreeList = new LinkDeps_t[2*nLinks]; // Dependent-on-me list elements (2x# of links, maximum) 552 LinkDepsPtr_t *linkDepListStarts = new LinkDepsPtr_t[nLinks]; // Start nodes of dependent-on-me lists, one for each link 553 554 // Copy the original, unsorted links to a side buffer 555 btSoftBody::Link *linkBuffer = new btSoftBody::Link[nLinks]; 556 memcpy(linkBuffer, &(psb->m_links[0]), sizeof(btSoftBody::Link)*nLinks); 557 558 // Clear out the node setup and ready list 559 for (i=0; i < nNodes+1; i++) { 560 nodeWrittenAt[i] = REOP_NOT_DEPENDENT; 561 } 562 for (i=0; i < nLinks; i++) { 563 linkDepListStarts[i] = NULL; 564 } 565 readyListHead = readyListTail = linkDepFrees = 0; 566 567 // Initial link analysis to set up data structures 568 for (i=0; i < nLinks; i++) { 569 570 // Note which prior link calculations we are dependent upon & build up dependence lists 571 lr = &(psb->m_links[i]); 572 ar = (lr->m_n[0] - node0)/(node1 - node0); 573 br = (lr->m_n[1] - node0)/(node1 - node0); 574 if (nodeWrittenAt[ar] > REOP_NOT_DEPENDENT) { 575 linkDepA[i] = nodeWrittenAt[ar]; 576 linkDep = &linkDepFreeList[linkDepFrees++]; 577 linkDep->value = i; 578 linkDep->next = linkDepListStarts[nodeWrittenAt[ar]]; 579 linkDepListStarts[nodeWrittenAt[ar]] = linkDep; 580 } else { 581 linkDepA[i] = REOP_NOT_DEPENDENT; 582 } 583 if (nodeWrittenAt[br] > REOP_NOT_DEPENDENT) { 584 linkDepB[i] = nodeWrittenAt[br]; 585 linkDep = &linkDepFreeList[linkDepFrees++]; 586 linkDep->value = -(i+1); 587 linkDep->next = linkDepListStarts[nodeWrittenAt[br]]; 588 linkDepListStarts[nodeWrittenAt[br]] = linkDep; 589 } else { 590 linkDepB[i] = REOP_NOT_DEPENDENT; 591 } 592 593 // Add this link to the initial ready list, if it is not dependent on any other links 594 if ((linkDepA[i] == REOP_NOT_DEPENDENT) && (linkDepB[i] == REOP_NOT_DEPENDENT)) { 595 readyList[readyListTail++] = i; 596 linkDepA[i] = linkDepB[i] = REOP_NODE_COMPLETE; // Probably not needed now 597 } 598 599 // Update the nodes to mark which ones are calculated by this link 600 nodeWrittenAt[ar] = nodeWrittenAt[br] = i; 601 } 602 603 // Process the ready list and create the sorted list of links 604 // -- By treating the ready list as a queue, we maximize the distance between any 605 // inter-dependent node calculations 606 // -- All other (non-related) nodes in the ready list will automatically be inserted 607 // in between each set of inter-dependent link calculations by this loop 608 i = 0; 609 while (readyListHead != readyListTail) { 610 // Use ready list to select the next link to process 611 linkNum = readyList[readyListHead++]; 612 // Copy the next-to-calculate link back into the original link array 613 psb->m_links[i++] = linkBuffer[linkNum]; 614 615 // Free up any link inputs that are dependent on this one 616 linkDep = linkDepListStarts[linkNum]; 617 while (linkDep) { 618 depLink = linkDep->value; 619 if (depLink >= 0) { 620 linkDepA[depLink] = REOP_NOT_DEPENDENT; 621 } else { 622 depLink = -depLink - 1; 623 linkDepB[depLink] = REOP_NOT_DEPENDENT; 624 } 625 // Add this dependent link calculation to the ready list if *both* inputs are clear 626 if ((linkDepA[depLink] == REOP_NOT_DEPENDENT) && (linkDepB[depLink] == REOP_NOT_DEPENDENT)) { 627 readyList[readyListTail++] = depLink; 628 linkDepA[depLink] = linkDepB[depLink] = REOP_NODE_COMPLETE; // Probably not needed now 629 } 630 linkDep = linkDep->next; 631 } 632 } 633 634 // Delete the temporary buffers 635 delete [] nodeWrittenAt; 636 delete [] linkDepA; 637 delete [] linkDepB; 638 delete [] readyList; 639 delete [] linkDepFreeList; 640 delete [] linkDepListStarts; 641 delete [] linkBuffer; 642 } 643 644 645 // 646 void btSoftBodyHelpers::DrawFrame( btSoftBody* psb, 647 btIDebugDraw* idraw) 648 { 649 if(psb->m_pose.m_bframe) 650 { 651 static const btScalar ascl=10; 652 static const btScalar nscl=(btScalar)0.1; 653 const btVector3 com=psb->m_pose.m_com; 654 const btMatrix3x3 trs=psb->m_pose.m_rot*psb->m_pose.m_scl; 655 const btVector3 Xaxis=(trs*btVector3(1,0,0)).normalized(); 656 const btVector3 Yaxis=(trs*btVector3(0,1,0)).normalized(); 657 const btVector3 Zaxis=(trs*btVector3(0,0,1)).normalized(); 658 idraw->drawLine(com,com+Xaxis*ascl,btVector3(1,0,0)); 659 idraw->drawLine(com,com+Yaxis*ascl,btVector3(0,1,0)); 660 idraw->drawLine(com,com+Zaxis*ascl,btVector3(0,0,1)); 661 for(int i=0;i<psb->m_pose.m_pos.size();++i) 662 { 663 const btVector3 x=com+trs*psb->m_pose.m_pos[i]; 664 drawVertex(idraw,x,nscl,btVector3(1,0,1)); 665 } 666 } 667 } 668 669 // 670 btSoftBody* btSoftBodyHelpers::CreateRope( btSoftBodyWorldInfo& worldInfo, const btVector3& from, 671 const btVector3& to, 672 int res, 673 int fixeds) 674 { 675 /* Create nodes */ 676 const int r=res+2; 677 btVector3* x=new btVector3[r]; 678 btScalar* m=new btScalar[r]; 679 int i; 680 681 for(i=0;i<r;++i) 682 { 683 const btScalar t=i/(btScalar)(r-1); 684 x[i]=lerp(from,to,t); 685 m[i]=1; 686 } 687 btSoftBody* psb= new btSoftBody(&worldInfo,r,x,m); 688 if(fixeds&1) psb->setMass(0,0); 689 if(fixeds&2) psb->setMass(r-1,0); 690 delete[] x; 691 delete[] m; 692 /* Create links */ 693 for(i=1;i<r;++i) 694 { 695 psb->appendLink(i-1,i); 696 } 697 /* Finished */ 698 return(psb); 699 } 700 701 // 702 btSoftBody* btSoftBodyHelpers::CreatePatch(btSoftBodyWorldInfo& worldInfo,const btVector3& corner00, 703 const btVector3& corner10, 704 const btVector3& corner01, 705 const btVector3& corner11, 706 int resx, 707 int resy, 708 int fixeds, 709 bool gendiags) 710 { 711 #define IDX(_x_,_y_) ((_y_)*rx+(_x_)) 712 /* Create nodes */ 713 if((resx<2)||(resy<2)) return(0); 714 const int rx=resx; 715 const int ry=resy; 716 const int tot=rx*ry; 717 btVector3* x=new btVector3[tot]; 718 btScalar* m=new btScalar[tot]; 719 int iy; 720 721 for(iy=0;iy<ry;++iy) 722 { 723 const btScalar ty=iy/(btScalar)(ry-1); 724 const btVector3 py0=lerp(corner00,corner01,ty); 725 const btVector3 py1=lerp(corner10,corner11,ty); 726 for(int ix=0;ix<rx;++ix) 727 { 728 const btScalar tx=ix/(btScalar)(rx-1); 729 x[IDX(ix,iy)]=lerp(py0,py1,tx); 730 m[IDX(ix,iy)]=1; 731 } 732 } 733 btSoftBody* psb=new btSoftBody(&worldInfo,tot,x,m); 734 if(fixeds&1) psb->setMass(IDX(0,0),0); 735 if(fixeds&2) psb->setMass(IDX(rx-1,0),0); 736 if(fixeds&4) psb->setMass(IDX(0,ry-1),0); 737 if(fixeds&8) psb->setMass(IDX(rx-1,ry-1),0); 738 delete[] x; 739 delete[] m; 740 /* Create links and faces */ 741 for(iy=0;iy<ry;++iy) 742 { 743 for(int ix=0;ix<rx;++ix) 744 { 745 const int idx=IDX(ix,iy); 746 const bool mdx=(ix+1)<rx; 747 const bool mdy=(iy+1)<ry; 748 if(mdx) psb->appendLink(idx,IDX(ix+1,iy)); 749 if(mdy) psb->appendLink(idx,IDX(ix,iy+1)); 750 if(mdx&&mdy) 751 { 752 if((ix+iy)&1) 753 { 754 psb->appendFace(IDX(ix,iy),IDX(ix+1,iy),IDX(ix+1,iy+1)); 755 psb->appendFace(IDX(ix,iy),IDX(ix+1,iy+1),IDX(ix,iy+1)); 756 if(gendiags) 757 { 758 psb->appendLink(IDX(ix,iy),IDX(ix+1,iy+1)); 759 } 760 } 761 else 762 { 763 psb->appendFace(IDX(ix,iy+1),IDX(ix,iy),IDX(ix+1,iy)); 764 psb->appendFace(IDX(ix,iy+1),IDX(ix+1,iy),IDX(ix+1,iy+1)); 765 if(gendiags) 766 { 767 psb->appendLink(IDX(ix+1,iy),IDX(ix,iy+1)); 768 } 769 } 770 } 771 } 772 } 773 /* Finished */ 774 #undef IDX 775 return(psb); 776 } 777 778 // 779 btSoftBody* btSoftBodyHelpers::CreatePatchUV(btSoftBodyWorldInfo& worldInfo, 780 const btVector3& corner00, 781 const btVector3& corner10, 782 const btVector3& corner01, 783 const btVector3& corner11, 784 int resx, 785 int resy, 786 int fixeds, 787 bool gendiags, 788 float* tex_coords) 789 { 790 791 /* 792 * 793 * corners: 794 * 795 * [0][0] corner00 ------- corner01 [resx][0] 796 * | | 797 * | | 798 * [0][resy] corner10 -------- corner11 [resx][resy] 799 * 800 * 801 * 802 * 803 * 804 * 805 * "fixedgs" map: 806 * 807 * corner00 --> +1 808 * corner01 --> +2 809 * corner10 --> +4 810 * corner11 --> +8 811 * upper middle --> +16 812 * left middle --> +32 813 * right middle --> +64 814 * lower middle --> +128 815 * center --> +256 816 * 817 * 818 * tex_coords size (resx-1)*(resy-1)*12 819 * 820 * 821 * 822 * SINGLE QUAD INTERNALS 823 * 824 * 1) btSoftBody's nodes and links, 825 * diagonal link is optional ("gendiags") 826 * 827 * 828 * node00 ------ node01 829 * | . 830 * | . 831 * | . 832 * | . 833 * | . 834 * node10 node11 835 * 836 * 837 * 838 * 2) Faces: 839 * two triangles, 840 * UV Coordinates (hier example for single quad) 841 * 842 * (0,1) (0,1) (1,1) 843 * 1 |\ 3 \-----| 2 844 * | \ \ | 845 * | \ \ | 846 * | \ \ | 847 * | \ \ | 848 * 2 |-----\ 3 \| 1 849 * (0,0) (1,0) (1,0) 850 * 851 * 852 * 853 * 854 * 855 * 856 */ 857 858 #define IDX(_x_,_y_) ((_y_)*rx+(_x_)) 859 /* Create nodes */ 860 if((resx<2)||(resy<2)) return(0); 861 const int rx=resx; 862 const int ry=resy; 863 const int tot=rx*ry; 864 btVector3* x=new btVector3[tot]; 865 btScalar* m=new btScalar[tot]; 866 867 int iy; 868 869 for(iy=0;iy<ry;++iy) 870 { 871 const btScalar ty=iy/(btScalar)(ry-1); 872 const btVector3 py0=lerp(corner00,corner01,ty); 873 const btVector3 py1=lerp(corner10,corner11,ty); 874 for(int ix=0;ix<rx;++ix) 875 { 876 const btScalar tx=ix/(btScalar)(rx-1); 877 x[IDX(ix,iy)]=lerp(py0,py1,tx); 878 m[IDX(ix,iy)]=1; 879 } 880 } 881 btSoftBody* psb=new btSoftBody(&worldInfo,tot,x,m); 882 if(fixeds&1) psb->setMass(IDX(0,0),0); 883 if(fixeds&2) psb->setMass(IDX(rx-1,0),0); 884 if(fixeds&4) psb->setMass(IDX(0,ry-1),0); 885 if(fixeds&8) psb->setMass(IDX(rx-1,ry-1),0); 886 if(fixeds&16) psb->setMass(IDX((rx-1)/2,0),0); 887 if(fixeds&32) psb->setMass(IDX(0,(ry-1)/2),0); 888 if(fixeds&64) psb->setMass(IDX(rx-1,(ry-1)/2),0); 889 if(fixeds&128) psb->setMass(IDX((rx-1)/2,ry-1),0); 890 if(fixeds&256) psb->setMass(IDX((rx-1)/2,(ry-1)/2),0); 891 delete[] x; 892 delete[] m; 893 894 895 int z = 0; 896 /* Create links and faces */ 897 for(iy=0;iy<ry;++iy) 898 { 899 for(int ix=0;ix<rx;++ix) 900 { 901 const bool mdx=(ix+1)<rx; 902 const bool mdy=(iy+1)<ry; 903 904 int node00=IDX(ix,iy); 905 int node01=IDX(ix+1,iy); 906 int node10=IDX(ix,iy+1); 907 int node11=IDX(ix+1,iy+1); 908 909 if(mdx) psb->appendLink(node00,node01); 910 if(mdy) psb->appendLink(node00,node10); 911 if(mdx&&mdy) 912 { 913 psb->appendFace(node00,node10,node11); 914 if (tex_coords) { 915 tex_coords[z+0]=CalculateUV(resx,resy,ix,iy,0); 916 tex_coords[z+1]=CalculateUV(resx,resy,ix,iy,1); 917 tex_coords[z+2]=CalculateUV(resx,resy,ix,iy,0); 918 tex_coords[z+3]=CalculateUV(resx,resy,ix,iy,2); 919 tex_coords[z+4]=CalculateUV(resx,resy,ix,iy,3); 920 tex_coords[z+5]=CalculateUV(resx,resy,ix,iy,2); 921 } 922 psb->appendFace(node11,node01,node00); 923 if (tex_coords) { 924 tex_coords[z+6 ]=CalculateUV(resx,resy,ix,iy,3); 925 tex_coords[z+7 ]=CalculateUV(resx,resy,ix,iy,2); 926 tex_coords[z+8 ]=CalculateUV(resx,resy,ix,iy,3); 927 tex_coords[z+9 ]=CalculateUV(resx,resy,ix,iy,1); 928 tex_coords[z+10]=CalculateUV(resx,resy,ix,iy,0); 929 tex_coords[z+11]=CalculateUV(resx,resy,ix,iy,1); 930 } 931 if (gendiags) psb->appendLink(node00,node11); 932 z += 12; 933 } 934 } 935 } 936 /* Finished */ 937 #undef IDX 938 return(psb); 939 } 940 941 float btSoftBodyHelpers::CalculateUV(int resx,int resy,int ix,int iy,int id) 942 { 943 944 /* 945 * 946 * 947 * node00 --- node01 948 * | | 949 * node10 --- node11 950 * 951 * 952 * ID map: 953 * 954 * node00 s --> 0 955 * node00 t --> 1 956 * 957 * node01 s --> 3 958 * node01 t --> 1 959 * 960 * node10 s --> 0 961 * node10 t --> 2 962 * 963 * node11 s --> 3 964 * node11 t --> 2 965 * 966 * 967 */ 968 969 float tc=0.0f; 970 if (id == 0) { 971 tc = (1.0f/((resx-1))*ix); 972 } 973 else if (id==1) { 974 tc = (1.0f/((resy-1))*(resy-1-iy)); 975 } 976 else if (id==2) { 977 tc = (1.0f/((resy-1))*(resy-1-iy-1)); 978 } 979 else if (id==3) { 980 tc = (1.0f/((resx-1))*(ix+1)); 981 } 982 return tc; 983 } 984 // 985 btSoftBody* btSoftBodyHelpers::CreateEllipsoid(btSoftBodyWorldInfo& worldInfo,const btVector3& center, 986 const btVector3& radius, 987 int res) 988 { 989 struct Hammersley 990 { 991 static void Generate(btVector3* x,int n) 992 { 993 for(int i=0;i<n;i++) 994 { 995 btScalar p=0.5,t=0; 996 for(int j=i;j;p*=0.5,j>>=1) if(j&1) t+=p; 997 btScalar w=2*t-1; 998 btScalar a=(SIMD_PI+2*i*SIMD_PI)/n; 999 btScalar s=btSqrt(1-w*w); 1000 *x++=btVector3(s*btCos(a),s*btSin(a),w); 1001 } 1002 } 1003 }; 1004 btAlignedObjectArray<btVector3> vtx; 1005 vtx.resize(3+res); 1006 Hammersley::Generate(&vtx[0],vtx.size()); 1007 for(int i=0;i<vtx.size();++i) 1008 { 1009 vtx[i]=vtx[i]*radius+center; 1010 } 1011 return(CreateFromConvexHull(worldInfo,&vtx[0],vtx.size())); 1012 } 1013 1014 1015 1016 // 1017 btSoftBody* btSoftBodyHelpers::CreateFromTriMesh(btSoftBodyWorldInfo& worldInfo,const btScalar* vertices, 1018 const int* triangles, 1019 int ntriangles, bool randomizeConstraints) 1020 { 1021 int maxidx=0; 1022 int i,j,ni; 1023 1024 for(i=0,ni=ntriangles*3;i<ni;++i) 1025 { 1026 maxidx=btMax(triangles[i],maxidx); 1027 } 1028 ++maxidx; 1029 btAlignedObjectArray<bool> chks; 1030 btAlignedObjectArray<btVector3> vtx; 1031 chks.resize(maxidx*maxidx,false); 1032 vtx.resize(maxidx); 1033 for(i=0,j=0,ni=maxidx*3;i<ni;++j,i+=3) 1034 { 1035 vtx[j]=btVector3(vertices[i],vertices[i+1],vertices[i+2]); 1036 } 1037 btSoftBody* psb=new btSoftBody(&worldInfo,vtx.size(),&vtx[0],0); 1038 for( i=0,ni=ntriangles*3;i<ni;i+=3) 1039 { 1040 const int idx[]={triangles[i],triangles[i+1],triangles[i+2]}; 1041 #define IDX(_x_,_y_) ((_y_)*maxidx+(_x_)) 1042 for(int j=2,k=0;k<3;j=k++) 1043 { 1044 if(!chks[IDX(idx[j],idx[k])]) 1045 { 1046 chks[IDX(idx[j],idx[k])]=true; 1047 chks[IDX(idx[k],idx[j])]=true; 1048 psb->appendLink(idx[j],idx[k]); 1049 } 1050 } 1051 #undef IDX 1052 psb->appendFace(idx[0],idx[1],idx[2]); 1053 } 1054 1055 if (randomizeConstraints) 1056 { 1057 psb->randomizeConstraints(); 1058 } 1059 1060 return(psb); 1061 } 1062 1063 // 1064 btSoftBody* btSoftBodyHelpers::CreateFromConvexHull(btSoftBodyWorldInfo& worldInfo, const btVector3* vertices, 1065 int nvertices, bool randomizeConstraints) 1066 { 1067 HullDesc hdsc(QF_TRIANGLES,nvertices,vertices); 1068 HullResult hres; 1069 HullLibrary hlib;/*??*/ 1070 hdsc.mMaxVertices=nvertices; 1071 hlib.CreateConvexHull(hdsc,hres); 1072 btSoftBody* psb=new btSoftBody(&worldInfo,(int)hres.mNumOutputVertices, 1073 &hres.m_OutputVertices[0],0); 1074 for(int i=0;i<(int)hres.mNumFaces;++i) 1075 { 1076 const int idx[]={ static_cast<int>(hres.m_Indices[i*3+0]), 1077 static_cast<int>(hres.m_Indices[i*3+1]), 1078 static_cast<int>(hres.m_Indices[i*3+2])}; 1079 if(idx[0]<idx[1]) psb->appendLink( idx[0],idx[1]); 1080 if(idx[1]<idx[2]) psb->appendLink( idx[1],idx[2]); 1081 if(idx[2]<idx[0]) psb->appendLink( idx[2],idx[0]); 1082 psb->appendFace(idx[0],idx[1],idx[2]); 1083 } 1084 hlib.ReleaseResult(hres); 1085 if (randomizeConstraints) 1086 { 1087 psb->randomizeConstraints(); 1088 } 1089 return(psb); 1090 } 1091 1092 1093 1094 1095 static int nextLine(const char* buffer) 1096 { 1097 int numBytesRead=0; 1098 1099 while (*buffer != '\n') 1100 { 1101 buffer++; 1102 numBytesRead++; 1103 } 1104 1105 1106 if (buffer[0]==0x0a) 1107 { 1108 buffer++; 1109 numBytesRead++; 1110 } 1111 return numBytesRead; 1112 } 1113 1114 /* Create from TetGen .ele, .face, .node data */ 1115 btSoftBody* btSoftBodyHelpers::CreateFromTetGenData(btSoftBodyWorldInfo& worldInfo, 1116 const char* ele, 1117 const char* face, 1118 const char* node, 1119 bool bfacelinks, 1120 bool btetralinks, 1121 bool bfacesfromtetras) 1122 { 1123 btAlignedObjectArray<btVector3> pos; 1124 int nnode=0; 1125 int ndims=0; 1126 int nattrb=0; 1127 int hasbounds=0; 1128 int result = sscanf(node,"%d %d %d %d",&nnode,&ndims,&nattrb,&hasbounds); 1129 result = sscanf(node,"%d %d %d %d",&nnode,&ndims,&nattrb,&hasbounds); 1130 node += nextLine(node); 1131 1132 pos.resize(nnode); 1133 for(int i=0;i<pos.size();++i) 1134 { 1135 int index=0; 1136 //int bound=0; 1137 float x,y,z; 1138 sscanf(node,"%d %f %f %f",&index,&x,&y,&z); 1139 1140 // sn>>index; 1141 // sn>>x;sn>>y;sn>>z; 1142 node += nextLine(node); 1143 1144 //for(int j=0;j<nattrb;++j) 1145 // sn>>a; 1146 1147 //if(hasbounds) 1148 // sn>>bound; 1149 1150 pos[index].setX(btScalar(x)); 1151 pos[index].setY(btScalar(y)); 1152 pos[index].setZ(btScalar(z)); 1153 } 1154 btSoftBody* psb=new btSoftBody(&worldInfo,nnode,&pos[0],0); 1155 #if 0 1156 if(face&&face[0]) 1157 { 1158 int nface=0; 1159 sf>>nface;sf>>hasbounds; 1160 for(int i=0;i<nface;++i) 1161 { 1162 int index=0; 1163 int bound=0; 1164 int ni[3]; 1165 sf>>index; 1166 sf>>ni[0];sf>>ni[1];sf>>ni[2]; 1167 sf>>bound; 1168 psb->appendFace(ni[0],ni[1],ni[2]); 1169 if(btetralinks) 1170 { 1171 psb->appendLink(ni[0],ni[1],0,true); 1172 psb->appendLink(ni[1],ni[2],0,true); 1173 psb->appendLink(ni[2],ni[0],0,true); 1174 } 1175 } 1176 } 1177 #endif 1178 1179 if(ele&&ele[0]) 1180 { 1181 int ntetra=0; 1182 int ncorner=0; 1183 int neattrb=0; 1184 sscanf(ele,"%d %d %d",&ntetra,&ncorner,&neattrb); 1185 ele += nextLine(ele); 1186 1187 //se>>ntetra;se>>ncorner;se>>neattrb; 1188 for(int i=0;i<ntetra;++i) 1189 { 1190 int index=0; 1191 int ni[4]; 1192 1193 //se>>index; 1194 //se>>ni[0];se>>ni[1];se>>ni[2];se>>ni[3]; 1195 sscanf(ele,"%d %d %d %d %d",&index,&ni[0],&ni[1],&ni[2],&ni[3]); 1196 ele+=nextLine(ele); 1197 //for(int j=0;j<neattrb;++j) 1198 // se>>a; 1199 psb->appendTetra(ni[0],ni[1],ni[2],ni[3]); 1200 if(btetralinks) 1201 { 1202 psb->appendLink(ni[0],ni[1],0,true); 1203 psb->appendLink(ni[1],ni[2],0,true); 1204 psb->appendLink(ni[2],ni[0],0,true); 1205 psb->appendLink(ni[0],ni[3],0,true); 1206 psb->appendLink(ni[1],ni[3],0,true); 1207 psb->appendLink(ni[2],ni[3],0,true); 1208 } 1209 } 1210 } 1211 printf("Nodes: %u\r\n",psb->m_nodes.size()); 1212 printf("Links: %u\r\n",psb->m_links.size()); 1213 printf("Faces: %u\r\n",psb->m_faces.size()); 1214 printf("Tetras: %u\r\n",psb->m_tetras.size()); 1215 return(psb); 1216 } 1217 1218