2 Bullet Continuous Collision Detection and Physics Library
\r
3 Copyright (c) 2008 Advanced Micro Devices
\r
5 This software is provided 'as-is', without any express or implied warranty.
\r
6 In no event will the authors be held liable for any damages arising from the use of this software.
\r
7 Permission is granted to anyone to use this software for any purpose,
\r
8 including commercial applications, and to alter it and redistribute it freely,
\r
9 subject to the following restrictions:
\r
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.
\r
12 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
\r
13 3. This notice may not be removed or altered from any source distribution.
\r
17 #include <GL/glew.h>
\r
23 #define USE_SIMDAWARE_SOLVER
\r
26 #if !defined (__APPLE__)
\r
27 #define USE_GPU_SOLVER
\r
28 #if defined (_WIN32) && !defined(USE_MINICL)
\r
29 #define USE_GPU_COPY //only tested on Windows
\r
30 #endif //_WIN32 && !USE_MINICL
\r
31 #endif //!__APPLE__
\r
37 #include "clstuff.h"
\r
41 #include "../OpenGL/GLDebugDrawer.h"
\r
43 GLDebugDrawer debugDraw;
\r
45 const int numFlags = 5;
\r
46 const int clothWidth = 40;
\r
47 const int clothHeight = 60;//60;
\r
48 float _windAngle = 1.0;//0.4;
\r
49 float _windStrength = 0.;
\r
53 #include "btBulletDynamicsCommon.h"
\r
54 #include "LinearMath/btHashMap.h"
\r
55 #include "BulletSoftBody/btSoftRigidDynamicsWorld.h"
\r
56 #include "vectormath/vmInclude.h"
\r
57 #include "BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/btSoftBodySolver_OpenCL.h"
\r
58 #include "BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/btSoftBodySolver_OpenCLSIMDAware.h"
\r
59 #include "BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/btSoftBodySolverVertexBuffer_OpenGL.h"
\r
60 #include "BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/btSoftBodySolverOutputCLtoGL.h"
\r
63 btRigidBody *capCollider;
\r
66 using Vectormath::Aos::Vector3;
\r
68 class piece_of_cloth;
\r
69 class btBroadphaseInterface;
\r
70 class btCollisionShape;
\r
71 class btOverlappingPairCache;
\r
72 class btCollisionDispatcher;
\r
73 class btConstraintSolver;
\r
74 struct btCollisionAlgorithmCreateFunc;
\r
75 class btDefaultCollisionConfiguration;
\r
77 #include "BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h"
\r
79 namespace Vectormath
\r
88 btAlignedObjectArray<btCollisionShape*> m_collisionShapes;
\r
89 btBroadphaseInterface* m_broadphase;
\r
90 btCollisionDispatcher* m_dispatcher;
\r
91 btConstraintSolver* m_solver;
\r
92 btDefaultCollisionConfiguration* m_collisionConfiguration;
\r
94 btOpenCLSoftBodySolver *g_openCLSolver = NULL;
\r
95 btOpenCLSoftBodySolverSIMDAware *g_openCLSIMDSolver = NULL;
\r
97 btSoftBodySolver *g_solver = NULL;
\r
99 btSoftBodySolverOutput *g_softBodyOutput = NULL;
\r
101 btAlignedObjectArray<btSoftBody *> m_flags;
\r
102 btSoftRigidDynamicsWorld* m_dynamicsWorld;
\r
103 btAlignedObjectArray<piece_of_cloth> cloths;
\r
105 extern cl_context g_cxMainContext;
\r
106 extern cl_device_id g_cdDevice;
\r
107 extern cl_command_queue g_cqCommandQue;
\r
110 const float flagSpacing = 30.f;
\r
113 // Helper to test and add links correctly.
\r
114 // Records links that have already been generated
\r
115 static bool testAndAddLink( btAlignedObjectArray<int> &trianglesForLinks, btSoftBody *softBody, int triangle, int *triangleVertexIndexArray, int numVertices, int vertex0, int vertex1, int nonLinkVertex, btSoftBody::Material *structuralMaterial, bool createBendLinks, btSoftBody::Material *bendMaterial )
\r
117 if( trianglesForLinks[ numVertices * vertex0 + vertex1 ] >= 0 && createBendLinks)
\r
119 // Already have link so find other triangle and generate cross link
\r
121 int otherTriangle = trianglesForLinks[numVertices * vertex0 + vertex1];
\r
122 int otherIndices[3] = {triangleVertexIndexArray[otherTriangle * 3], triangleVertexIndexArray[otherTriangle * 3 + 1], triangleVertexIndexArray[otherTriangle * 3 + 2]};
\r
125 // Test all links of the other triangle against this link. The one that's not part of it is what we want.
\r
126 if( otherIndices[0] != vertex0 && otherIndices[0] != vertex1 )
\r
127 nodeA = otherIndices[0];
\r
128 if( otherIndices[1] != vertex0 && otherIndices[1] != vertex1 )
\r
129 nodeA = otherIndices[1];
\r
130 if( otherIndices[2] != vertex0 && otherIndices[2] != vertex1 )
\r
131 nodeA = otherIndices[2];
\r
133 softBody->appendLink( nodeA, nonLinkVertex, bendMaterial );
\r
135 // Don't yet have link so create it
\r
136 softBody->appendLink( vertex0, vertex1, structuralMaterial );
\r
138 // If we added a new link, set the triangle array
\r
139 trianglesForLinks[numVertices * vertex0 + vertex1] = triangle;
\r
140 trianglesForLinks[numVertices * vertex1 + vertex0] = triangle;
\r
147 btSoftBody *createFromIndexedMesh( btVector3 *vertexArray, int numVertices, int *triangleVertexIndexArray, int numTriangles, bool createBendLinks )
\r
149 btSoftBody* softBody = new btSoftBody(&(m_dynamicsWorld->getWorldInfo()), numVertices, vertexArray, 0);
\r
150 btSoftBody::Material * structuralMaterial = softBody->appendMaterial();
\r
151 btSoftBody::Material * bendMaterial;
\r
152 if( createBendLinks )
\r
154 bendMaterial = softBody->appendMaterial();
\r
155 bendMaterial->m_kLST = 0.7;
\r
157 bendMaterial = NULL;
\r
159 structuralMaterial->m_kLST = 1.0;
\r
162 // List of values for each link saying which triangle is associated with that link
\r
163 // -1 to start. Once a value is entered we know the "other" triangle
\r
164 // and can add a link across the link
\r
165 btAlignedObjectArray<int> triangleForLinks;
\r
166 triangleForLinks.resize( numVertices * numVertices, -1 );
\r
167 // int numLinks = 0;
\r
168 for( int triangle = 0; triangle < numTriangles; ++triangle )
\r
170 int index[3] = {triangleVertexIndexArray[triangle * 3], triangleVertexIndexArray[triangle * 3 + 1], triangleVertexIndexArray[triangle * 3 + 2]};
\r
171 softBody->appendFace( index[0], index[1], index[2] );
\r
173 // Generate the structural links directly from the triangles
\r
174 testAndAddLink( triangleForLinks, softBody, triangle, triangleVertexIndexArray, numVertices, index[0], index[1], index[2], structuralMaterial, createBendLinks, bendMaterial );
\r
175 testAndAddLink( triangleForLinks, softBody, triangle, triangleVertexIndexArray, numVertices, index[1], index[2], index[0], structuralMaterial, createBendLinks, bendMaterial );
\r
176 testAndAddLink( triangleForLinks, softBody, triangle, triangleVertexIndexArray, numVertices, index[2], index[0], index[1], structuralMaterial, createBendLinks, bendMaterial);
\r
183 * Create a sequence of flag objects and add them to the world.
\r
185 void createFlag( btSoftBodySolver &solver, int width, int height, btAlignedObjectArray<btSoftBody *> &flags )
\r
187 // First create a triangle mesh to represent a flag
\r
189 using Vectormath::Aos::Matrix3;
\r
190 using Vectormath::Aos::Vector3;
\r
192 // Allocate a simple mesh consisting of a vertex array and a triangle index array
\r
193 btIndexedMesh mesh;
\r
194 mesh.m_numVertices = width*height;
\r
195 mesh.m_numTriangles = 2*(width-1)*(height-1);
\r
197 btVector3 *vertexArray = new btVector3[mesh.m_numVertices];
\r
199 mesh.m_vertexBase = reinterpret_cast<const unsigned char*>(vertexArray);
\r
200 int *triangleVertexIndexArray = new int[3*mesh.m_numTriangles];
\r
201 mesh.m_triangleIndexBase = reinterpret_cast<const unsigned char*>(triangleVertexIndexArray);
\r
202 mesh.m_triangleIndexStride = sizeof(int)*3;
\r
203 mesh.m_vertexStride = sizeof(Vector3);
\r
205 // Generate normalised object space vertex coordinates for a rectangular flag
\r
206 float zCoordinate = 0.0f;
\r
208 Matrix3 defaultScale(Vector3(5.f, 0.f, 0.f), Vector3(0.f, 20.f, 0.f), Vector3(0.f, 0.f, 1.f));
\r
209 for( int y = 0; y < height; ++y )
\r
211 float yCoordinate = y*2.0f/float(height) - 1.0f;
\r
212 for( int x = 0; x < width; ++x )
\r
214 float xCoordinate = x*2.0f/float(width) - 1.0f;
\r
216 Vector3 vertex(xCoordinate, yCoordinate, zCoordinate);
\r
217 Vector3 transformedVertex = defaultScale*vertex;
\r
219 vertexArray[y*width + x] = btVector3(transformedVertex.getX(), transformedVertex.getY(), transformedVertex.getZ() );
\r
224 // Generate vertex indices for triangles
\r
225 for( int y = 0; y < (height-1); ++y )
\r
227 for( int x = 0; x < (width-1); ++x )
\r
230 // Top left of square on mesh
\r
232 int vertex0 = y*width + x;
\r
233 int vertex1 = vertex0 + 1;
\r
234 int vertex2 = vertex0 + width;
\r
235 int triangleIndex = 2*y*(width-1) + 2*x;
\r
236 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex)/sizeof(int)] = vertex0;
\r
237 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex+1)/sizeof(int)+1] = vertex1;
\r
238 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex+2)/sizeof(int)+2] = vertex2;
\r
242 // Bottom right of square on mesh
\r
244 int vertex0 = y*width + x + 1;
\r
245 int vertex1 = vertex0 + width;
\r
246 int vertex2 = vertex1 - 1;
\r
247 int triangleIndex = 2*y*(width-1) + 2*x + 1;
\r
248 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex)/sizeof(int)] = vertex0;
\r
249 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex)/sizeof(int)+1] = vertex1;
\r
250 triangleVertexIndexArray[(mesh.m_triangleIndexStride*triangleIndex)/sizeof(int)+2] = vertex2;
\r
256 float rotateAngleRoundZ = 0.0;
\r
257 float rotateAngleRoundX = 0.5;
\r
258 btMatrix3x3 defaultRotate;
\r
259 defaultRotate[0] = btVector3(cos(rotateAngleRoundZ), sin(rotateAngleRoundZ), 0.f);
\r
260 defaultRotate[1] = btVector3(-sin(rotateAngleRoundZ), cos(rotateAngleRoundZ), 0.f);
\r
261 defaultRotate[2] = btVector3(0.f, 0.f, 1.f);
\r
262 btMatrix3x3 defaultRotateX;
\r
263 defaultRotateX[0] = btVector3(1.f, 0.f, 0.f);
\r
264 defaultRotateX[1] = btVector3( 0.f, cos(rotateAngleRoundX), sin(rotateAngleRoundX));
\r
265 defaultRotateX[2] = btVector3(0.f, -sin(rotateAngleRoundX), cos(rotateAngleRoundX));
\r
267 btMatrix3x3 defaultRotateAndScale( (defaultRotateX*defaultRotate) );
\r
270 // Construct the sequence flags applying a slightly different translation to each one to arrange them
\r
271 // appropriately in the scene.
\r
272 for( int i = 0; i < numFlags; ++i )
\r
274 float zTranslate = flagSpacing * (i-numFlags/2);
\r
276 btVector3 defaultTranslate(0.f, 20.f, zTranslate);
\r
278 btTransform transform( defaultRotateAndScale, defaultTranslate );
\r
279 transform.setOrigin(defaultTranslate);
\r
282 btSoftBody *softBody = createFromIndexedMesh( vertexArray, mesh.m_numVertices, triangleVertexIndexArray, mesh.m_numTriangles, true );
\r
285 for( int i = 0; i < mesh.m_numVertices; ++i )
\r
287 softBody->setMass(i, 10.f/mesh.m_numVertices);
\r
289 softBody->setMass((height-1)*(width), 0.f);
\r
290 softBody->setMass((height-1)*(width) + width - 1, 0.f);
\r
291 softBody->setMass((height-1)*width + width/2, 0.f);
\r
292 softBody->m_cfg.collisions = btSoftBody::fCollision::CL_SS+btSoftBody::fCollision::CL_RS;
\r
294 softBody->m_cfg.kLF = 0.0005f;
\r
295 softBody->m_cfg.kVCF = 0.001f;
\r
296 softBody->m_cfg.kDP = 0.f;
\r
297 softBody->m_cfg.kDG = 0.f;
\r
300 flags.push_back( softBody );
\r
302 softBody->transform( transform );
\r
304 m_dynamicsWorld->addSoftBody( softBody );
\r
307 delete [] vertexArray;
\r
308 delete [] triangleVertexIndexArray;
\r
312 void updatePhysicsWorld()
\r
314 static int counter = 1;
\r
316 // Change wind velocity a bit based on a frame counter
\r
317 if( (counter % 400) == 0 )
\r
319 _windAngle = (_windAngle + 0.05f);
\r
320 if( _windAngle > (2*3.141) )
\r
323 for( int flagIndex = 0; flagIndex < m_flags.size(); ++flagIndex )
\r
325 btSoftBody *cloth = 0;
\r
327 cloth = m_flags[flagIndex];
\r
329 float localWind = _windAngle + 0.5*(((float(rand())/RAND_MAX))-0.1);
\r
330 float xCoordinate = cos(localWind)*_windStrength;
\r
331 float zCoordinate = sin(localWind)*_windStrength;
\r
333 cloth->setWindVelocity( btVector3(xCoordinate, 0, zCoordinate) );
\r
337 //btVector3 origin( capCollider->getWorldTransform().getOrigin() );
\r
338 //origin.setX( origin.getX() + 0.05 );
\r
339 //capCollider->getWorldTransform().setOrigin( origin );
\r
344 void initBullet(void)
\r
347 #ifdef USE_GPU_SOLVER
\r
348 #ifdef USE_SIMDAWARE_SOLVER
\r
349 g_openCLSIMDSolver = new btOpenCLSoftBodySolverSIMDAware( g_cqCommandQue, g_cxMainContext);
\r
350 g_solver = g_openCLSIMDSolver;
\r
351 #ifdef USE_GPU_COPY
\r
352 g_softBodyOutput = new btSoftBodySolverOutputCLtoGL(g_cqCommandQue, g_cxMainContext);
\r
353 #else // #ifdef USE_GPU_COPY
\r
354 g_softBodyOutput = new btSoftBodySolverOutputCLtoCPU;
\r
355 #endif // #ifdef USE_GPU_COPY
\r
357 g_openCLSolver = new btOpenCLSoftBodySolver( g_cqCommandQue, g_cxMainContext );
\r
358 g_solver = g_openCLSolver;
\r
359 #ifdef USE_GPU_COPY
\r
360 g_softBodyOutput = new btSoftBodySolverOutputCLtoGL(g_cqCommandQue, g_cxMainContext);
\r
361 #else // #ifdef USE_GPU_COPY
\r
362 g_softBodyOutput = new btSoftBodySolverOutputCLtoCPU;
\r
363 #endif // #ifdef USE_GPU_COPY
\r
366 g_openCLSolver = new btOpenCLSoftBodySolver( g_cqCommandQue, g_cxMainContext );
\r
367 g_solver = g_openCLSolver;
\r
370 //m_collisionConfiguration = new btDefaultCollisionConfiguration();
\r
371 m_collisionConfiguration = new btSoftBodyRigidBodyCollisionConfiguration();
\r
374 m_dispatcher = new btCollisionDispatcher(m_collisionConfiguration);
\r
375 m_broadphase = new btDbvtBroadphase();
\r
376 btSequentialImpulseConstraintSolver* sol = new btSequentialImpulseConstraintSolver;
\r
379 m_dynamicsWorld = new btSoftRigidDynamicsWorld(m_dispatcher, m_broadphase, m_solver, m_collisionConfiguration, g_solver);
\r
381 m_dynamicsWorld->setGravity(btVector3(0,-10,0));
\r
382 btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(50.),btScalar(50.),btScalar(50.)));
\r
383 m_collisionShapes.push_back(groundShape);
\r
384 btTransform groundTransform;
\r
385 groundTransform.setIdentity();
\r
386 groundTransform.setOrigin(btVector3(0,-50,0));
\r
393 m_dynamicsWorld->getWorldInfo().air_density = (btScalar)1.2;
\r
394 m_dynamicsWorld->getWorldInfo().water_density = 0;
\r
395 m_dynamicsWorld->getWorldInfo().water_offset = 0;
\r
396 m_dynamicsWorld->getWorldInfo().water_normal = btVector3(0,0,0);
\r
397 m_dynamicsWorld->getWorldInfo().m_gravity.setValue(0,-10,0);
\r
405 //rigidbody is dynamic if and only if mass is non zero, otherwise static
\r
406 bool isDynamic = (mass != 0.f);
\r
408 btVector3 localInertia(0,0,0);
\r
410 groundShape->calculateLocalInertia(mass,localInertia);
\r
412 //using motionstate is recommended, it provides interpolation capabilities, and only synchronizes 'active' objects
\r
413 btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
\r
414 btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,groundShape,localInertia);
\r
415 btRigidBody* body = new btRigidBody(rbInfo);
\r
417 //add the body to the dynamics world
\r
418 m_dynamicsWorld->addRigidBody(body);
\r
427 //btScalar mass(1.);
\r
429 //rigidbody is dynamic if and only if mass is non zero, otherwise static
\r
430 bool isDynamic = (mass != 0.f);
\r
432 btCollisionShape *capsuleShape = new btCapsuleShape(5, 10);
\r
433 capsuleShape->setMargin( 0.5 );
\r
438 btVector3 localInertia(0,0,0);
\r
440 capsuleShape->calculateLocalInertia(mass,localInertia);
\r
442 m_collisionShapes.push_back(capsuleShape);
\r
443 btTransform capsuleTransform;
\r
444 capsuleTransform.setIdentity();
\r
446 capsuleTransform.setOrigin(btVector3(0, 10, -11));
\r
447 const btScalar pi = 3.141592654;
\r
448 capsuleTransform.setRotation(btQuaternion(0, 0, pi/2));
\r
450 capsuleTransform.setOrigin(btVector3(0, 0, 0));
\r
452 // const btScalar pi = 3.141592654;
\r
453 //capsuleTransform.setRotation(btQuaternion(0, 0, pi/2));
\r
454 capsuleTransform.setRotation(btQuaternion(0, 0, 0));
\r
456 btDefaultMotionState* myMotionState = new btDefaultMotionState(capsuleTransform);
\r
457 btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,myMotionState,capsuleShape,localInertia);
\r
458 btRigidBody* body = new btRigidBody(rbInfo);
\r
459 body->setFriction( 0.8f );
\r
461 m_dynamicsWorld->addRigidBody(body);
\r
462 //cap_1.collisionShape = body;
\r
463 capCollider = body;
\r
468 //#ifdef USE_GPU_SOLVER
\r
469 createFlag( *g_openCLSolver, clothWidth, clothHeight, m_flags );
\r
474 // Create output buffer descriptions for ecah flag
\r
475 // These describe where the simulation should send output data to
\r
476 for( int flagIndex = 0; flagIndex < m_flags.size(); ++flagIndex )
\r
478 // m_flags[flagIndex]->setWindVelocity( Vectormath::Aos::Vector3( 0.f, 0.f, 15.f ) );
\r
480 // In this case we have a DX11 output buffer with a vertex at index 0, 8, 16 and so on as well as a normal at 3, 11, 19 etc.
\r
481 // Copies will be performed GPU-side directly into the output buffer
\r
483 #ifdef USE_GPU_COPY
\r
484 GLuint targetVBO = cloths[flagIndex].getVBO();
\r
485 btOpenGLInteropVertexBufferDescriptor *vertexBufferDescriptor = new btOpenGLInteropVertexBufferDescriptor(g_cqCommandQue, g_cxMainContext, targetVBO, 0, 8, 3, 8);
\r
487 btCPUVertexBufferDescriptor *vertexBufferDescriptor = new btCPUVertexBufferDescriptor(reinterpret_cast< float* >(cloths[flagIndex].cpu_buffer), 0, 8, 3, 8);
\r
489 cloths[flagIndex].m_vertexBufferDescriptor = vertexBufferDescriptor;
\r
493 g_solver->optimize( m_dynamicsWorld->getSoftBodyArray() );
\r
495 if (!g_solver->checkInitialized())
\r
497 printf("OpenCL kernel initialization ?failed\n");
\r
511 //float ms = getDeltaTimeMicroseconds();
\r
512 btScalar dt = (btScalar)m_clock.getTimeMicroseconds();
\r
515 ///step the simulation
\r
516 if( m_dynamicsWorld )
\r
518 m_dynamicsWorld->stepSimulation(dt/1000000.);
\r
520 static int frameCount = 0;
\r
522 if (frameCount==100)
\r
524 m_dynamicsWorld->stepSimulation(1./60.,0);
\r
526 // Option to save a .bullet file
\r
527 // btDefaultSerializer* serializer = new btDefaultSerializer();
\r
528 // m_dynamicsWorld->serialize(serializer);
\r
529 // FILE* file = fopen("testFile.bullet","wb");
\r
530 // fwrite(serializer->getBufferPointer(),serializer->getCurrentBufferSize(),1, file);
\r
533 CProfileManager::dumpAll();
\r
535 updatePhysicsWorld();
\r
537 //m_dynamicsWorld->setDebugDrawer(&debugDraw);
\r
538 //debugDraw.setDebugMode(btIDebugDraw::DBG_DrawWireframe);
\r
539 //g_solver->copyBackToSoftBodies();
\r
541 m_dynamicsWorld->debugDrawWorld();
\r
546 for( int flagIndex = 0; flagIndex < m_flags.size(); ++flagIndex )
\r
548 if (g_softBodyOutput)
\r
549 g_softBodyOutput->copySoftBodyToVertexBuffer( m_flags[flagIndex], cloths[flagIndex].m_vertexBufferDescriptor );
\r
550 cloths[flagIndex].draw();
\r
555 int main(int argc, char *argv[])
\r
559 preInitGL(argc, argv);
\r
564 #ifdef USE_GPU_COPY
\r
566 HGLRC glCtx = wglGetCurrentContext();
\r
568 GLXContext glCtx = glXGetCurrentContext();
\r
570 HDC glDC = wglGetCurrentDC();
\r
572 initCL(glCtx, glDC);
\r
579 cloths.resize(numFlags);
\r
581 for( int flagIndex = 0; flagIndex < numFlags; ++flagIndex )
\r
583 cloths[flagIndex].create_buffers(clothWidth, clothHeight);
\r
587 m_dynamicsWorld->stepSimulation(1./60.,0);
\r
589 std::string flagTexs[] = {
\r
593 int numFlagTexs = 2;
\r
595 for( int flagIndex = 0; flagIndex < numFlags; ++flagIndex )
\r
597 cloths[flagIndex].create_texture(flagTexs[flagIndex % numFlagTexs]);
\r
598 cloths[flagIndex].x_offset = 0;
\r
599 cloths[flagIndex].y_offset = 0;
\r
600 cloths[flagIndex].z_offset = 0;
\r
605 if( g_openCLSolver )
\r
606 delete g_openCLSolver;
\r
607 if( g_openCLSIMDSolver )
\r
608 delete g_openCLSIMDSolver;
\r
609 if( g_softBodyOutput )
\r
610 delete g_softBodyOutput;
\r