1 #include "GpuConvexScene.h"
2
3 #include "GpuRigidBodyDemo.h"
4 #include "../OpenGLWindow/ShapeData.h"
5
6 #include "../OpenGLWindow/GLInstancingRenderer.h"
7 #include "Bullet3Common/b3Quaternion.h"
8 #include "../CommonInterfaces/CommonWindowInterface.h"
9 #include "Bullet3OpenCL/BroadphaseCollision/b3GpuSapBroadphase.h"
10 #include "../CommonOpenCL/GpuDemoInternalData.h"
11 #include "Bullet3OpenCL/Initialize/b3OpenCLUtils.h"
12 #include "../OpenGLWindow/OpenGLInclude.h"
13 #include "../OpenGLWindow/GLInstanceRendererInternalData.h"
14 #include "Bullet3OpenCL/ParallelPrimitives/b3LauncherCL.h"
15 #include "Bullet3OpenCL/RigidBody/b3GpuRigidBodyPipeline.h"
16 #include "Bullet3OpenCL/RigidBody/b3GpuNarrowPhase.h"
17 #include "Bullet3Collision/NarrowPhaseCollision/b3Config.h"
18 #include "GpuRigidBodyDemoInternalData.h"
19
20 #include "Bullet3Dynamics/ConstraintSolver/b3Point2PointConstraint.h"
21 #include "../OpenGLWindow/GLPrimitiveRenderer.h"
22 #include "Bullet3OpenCL/Raycast/b3GpuRaycast.h"
23 #include "Bullet3Collision/NarrowPhaseCollision/b3ConvexUtility.h"
24 #include "Bullet3Dynamics/ConstraintSolver/b3FixedConstraint.h"
25
26 #include "../OpenGLWindow/GLRenderToTexture.h"
27
28 static bool gUseInstancedCollisionShapes = true;
29 extern int gGpuArraySizeX;
30 extern int gGpuArraySizeY;
31 extern int gGpuArraySizeZ;
32
33 #include "GpuRigidBodyDemo.h"
34 #include "Bullet3Common/b3AlignedObjectArray.h"
35 #include "Bullet3Collision/NarrowPhaseCollision/b3RaycastInfo.h"
36
37 class GpuConvexScene : public GpuRigidBodyDemo
38 {
39 protected:
40 class b3GpuRaycast* m_raycaster;
41
42 public:
GpuConvexScene(GUIHelperInterface * helper)43 GpuConvexScene(GUIHelperInterface* helper)
44 : GpuRigidBodyDemo(helper), m_raycaster(0)
45 {
46 }
~GpuConvexScene()47 virtual ~GpuConvexScene() {}
getName()48 virtual const char* getName()
49 {
50 return "Tetrahedra";
51 }
52
53 virtual void setupScene();
54
55 virtual void destroyScene();
56
57 virtual int createDynamicsObjects();
58
59 virtual int createDynamicsObjects2(const float* vertices, int numVertices, const int* indices, int numIndices);
60
61 virtual void createStaticEnvironment();
62 };
63
64 class GpuConvexPlaneScene : public GpuConvexScene
65 {
66 public:
GpuConvexPlaneScene(GUIHelperInterface * helper)67 GpuConvexPlaneScene(GUIHelperInterface* helper)
68 : GpuConvexScene(helper) {}
~GpuConvexPlaneScene()69 virtual ~GpuConvexPlaneScene() {}
getName()70 virtual const char* getName()
71 {
72 return "ConvexOnPlane";
73 }
74
75 virtual void createStaticEnvironment();
76 };
77
78 class GpuBoxPlaneScene : public GpuConvexPlaneScene
79 {
80 public:
GpuBoxPlaneScene(GUIHelperInterface * helper)81 GpuBoxPlaneScene(GUIHelperInterface* helper) : GpuConvexPlaneScene(helper) {}
~GpuBoxPlaneScene()82 virtual ~GpuBoxPlaneScene() {}
getName()83 virtual const char* getName()
84 {
85 return "BoxBox";
86 }
87
88 virtual int createDynamicsObjects();
89 };
90
91 class GpuTetraScene : public GpuConvexScene
92 {
93 protected:
94 void createFromTetGenData(const char* ele, const char* node);
95
96 public:
getName()97 virtual const char* getName()
98 {
99 return "TetraBreakable";
100 }
101
102 virtual int createDynamicsObjects();
103 };
104
105 b3Vector4 colors[4] =
106 {
107 b3MakeVector4(1, 0, 0, 1),
108 b3MakeVector4(0, 1, 0, 1),
109 b3MakeVector4(0, 1, 1, 1),
110 b3MakeVector4(1, 1, 0, 1),
111 };
112
setupScene()113 void GpuConvexScene::setupScene()
114 {
115 m_raycaster = new b3GpuRaycast(m_clData->m_clContext, m_clData->m_clDevice, m_clData->m_clQueue);
116
117 int index = 0;
118 createStaticEnvironment();
119
120 index += createDynamicsObjects();
121
122 m_data->m_rigidBodyPipeline->writeAllInstancesToGpu();
123
124 float camPos[4] = {0, 0, 0, 0}; //ci.arraySizeX,ci.arraySizeY/2,ci.arraySizeZ,0};
125 //float camPos[4]={1,12.5,1.5,0};
126
127 m_guiHelper->getRenderInterface()->getActiveCamera()->setCameraTargetPosition(camPos[0], camPos[1], camPos[2]);
128 m_guiHelper->getRenderInterface()->getActiveCamera()->setCameraDistance(150);
129 //m_instancingRenderer->setCameraYaw(85);
130 m_guiHelper->getRenderInterface()->getActiveCamera()->setCameraYaw(225);
131 m_guiHelper->getRenderInterface()->getActiveCamera()->setCameraPitch(-30);
132
133 m_guiHelper->getRenderInterface()->updateCamera(1); //>updateCamera();
134
135 char msg[1024];
136 int numInstances = index;
137 sprintf(msg, "Num objects = %d", numInstances);
138 b3Printf(msg);
139
140 //if (ci.m_gui)
141 // ci.m_gui->setStatusBarMessage(msg,true);
142 }
143
destroyScene()144 void GpuConvexScene::destroyScene()
145 {
146 delete m_raycaster;
147 m_raycaster = 0;
148 }
149
createDynamicsObjects()150 int GpuConvexScene::createDynamicsObjects()
151 {
152 int strideInBytes = 9 * sizeof(float);
153 /*int numVertices = sizeof(barrel_vertices)/strideInBytes;
154 int numIndices = sizeof(barrel_indices)/sizeof(int);
155 return createDynamicsObjects2(ci,barrel_vertices,numVertices,barrel_indices,numIndices);
156 */
157
158 int numVertices = sizeof(tetra_vertices) / strideInBytes;
159 int numIndices = sizeof(tetra_indices) / sizeof(int);
160 return createDynamicsObjects2(tetra_vertices, numVertices, tetra_indices, numIndices);
161 }
162
createDynamicsObjects()163 int GpuBoxPlaneScene::createDynamicsObjects()
164 {
165 int strideInBytes = 9 * sizeof(float);
166 int numVertices = sizeof(cube_vertices) / strideInBytes;
167 int numIndices = sizeof(cube_indices) / sizeof(int);
168 return createDynamicsObjects2(cube_vertices_textured, numVertices, cube_indices, numIndices);
169 }
170
createDynamicsObjects2(const float * vertices,int numVertices,const int * indices,int numIndices)171 int GpuConvexScene::createDynamicsObjects2(const float* vertices, int numVertices, const int* indices, int numIndices)
172 {
173 int strideInBytes = 9 * sizeof(float);
174 int textureIndex = -1;
175 if (0)
176 {
177 int width, height, n;
178
179 const char* filename = "data/cube.png";
180 const unsigned char* image = 0;
181
182 const char* prefix[] = {"./", "../", "../../", "../../../", "../../../../"};
183 int numprefix = sizeof(prefix) / sizeof(const char*);
184
185 for (int i = 0; !image && i < numprefix; i++)
186 {
187 char relativeFileName[1024];
188 sprintf(relativeFileName, "%s%s", prefix[i], filename);
189 image = loadImage(relativeFileName, width, height, n);
190 }
191
192 b3Assert(image);
193 if (image)
194 {
195 textureIndex = m_instancingRenderer->registerTexture(image, width, height);
196 }
197 }
198
199 int shapeId = m_guiHelper->getRenderInterface()->registerShape(&vertices[0], numVertices, indices, numIndices, B3_GL_TRIANGLES, textureIndex);
200 //int group=1;
201 //int mask=1;
202 int index = 0;
203
204 {
205 int curColor = 0;
206 float scaling[4] = {1, 1, 1, 1};
207 int prevBody = -1;
208 //int insta = 0;
209
210 b3ConvexUtility* utilPtr = new b3ConvexUtility();
211
212 {
213 b3AlignedObjectArray<b3Vector3> verts;
214
215 unsigned char* vts = (unsigned char*)vertices;
216 for (int i = 0; i < numVertices; i++)
217 {
218 float* vertex = (float*)&vts[i * strideInBytes];
219 verts.push_back(b3MakeVector3(vertex[0] * scaling[0], vertex[1] * scaling[1], vertex[2] * scaling[2]));
220 }
221
222 bool merge = true;
223 if (numVertices)
224 {
225 utilPtr->initializePolyhedralFeatures(&verts[0], verts.size(), merge);
226 }
227 }
228
229 int colIndex = -1;
230 if (gUseInstancedCollisionShapes)
231 colIndex = m_data->m_np->registerConvexHullShape(utilPtr);
232
233 //int colIndex = m_data->m_np->registerSphereShape(1);
234 for (int i = 0; i < gGpuArraySizeX; i++)
235 {
236 //printf("%d of %d\n", i, ci.arraySizeX);
237 for (int j = 0; j < gGpuArraySizeY; j++)
238 {
239 for (int k = 0; k < gGpuArraySizeZ; k++)
240 {
241 //int colIndex = m_data->m_np->registerConvexHullShape(&vertices[0],strideInBytes,numVertices, scaling);
242 if (!gUseInstancedCollisionShapes)
243 colIndex = m_data->m_np->registerConvexHullShape(utilPtr);
244
245 float mass = 1.f;
246 if (j == 0) //ci.arraySizeY-1)
247 {
248 //mass=0.f;
249 }
250 b3Vector3 position = b3MakeVector3(((j + 1) & 1) + i * 2.2, 1 + j * 2., ((j + 1) & 1) + k * 2.2);
251 //b3Vector3 position = b3MakeVector3(i*2,1+j*2,k*2);
252 //b3Vector3 position=b3MakeVector3(1,0.9,1);
253 b3Quaternion orn(0, 0, 0, 1);
254
255 b3Vector4 color = colors[curColor];
256 curColor++;
257 curColor &= 3;
258 // b3Vector4 scaling=b3MakeVector4(1,1,1,1);
259 int id;
260 id = m_guiHelper->getRenderInterface()->registerGraphicsInstance(shapeId, position, orn, color, scaling);
261 int pid;
262 pid = m_data->m_rigidBodyPipeline->registerPhysicsInstance(mass, position, orn, colIndex, index, false);
263
264 if (prevBody >= 0)
265 {
266 //b3Point2PointConstraint* p2p = new b3Point2PointConstraint(pid,prevBody,b3Vector3(0,-1.1,0),b3Vector3(0,1.1,0));
267 // m_data->m_rigidBodyPipeline->addConstraint(p2p);//,false);
268 }
269 prevBody = pid;
270
271 index++;
272 }
273 }
274 }
275 delete utilPtr;
276 }
277 return index;
278 }
279
createStaticEnvironment()280 void GpuConvexScene::createStaticEnvironment()
281 {
282 int strideInBytes = 9 * sizeof(float);
283 int numVertices = sizeof(cube_vertices) / strideInBytes;
284 int numIndices = sizeof(cube_indices) / sizeof(int);
285 //int shapeId = ci.m_instancingRenderer->registerShape(&cube_vertices[0],numVertices,cube_indices,numIndices);
286 int shapeId = m_instancingRenderer->registerShape(&cube_vertices[0], numVertices, cube_indices, numIndices);
287 //int group=1;
288 //int mask=1;
289 int index = 0;
290
291 {
292 b3Vector4 scaling = b3MakeVector4(400, 400, 400, 1);
293 int colIndex = m_data->m_np->registerConvexHullShape(&cube_vertices[0], strideInBytes, numVertices, scaling);
294 b3Vector3 position = b3MakeVector3(0, -400, 0);
295 b3Quaternion orn(0, 0, 0, 1);
296
297 b3Vector4 color = b3MakeVector4(0, 0, 1, 1);
298
299 int id;
300 id = m_instancingRenderer->registerGraphicsInstance(shapeId, position, orn, color, scaling);
301 int pid;
302 pid = m_data->m_rigidBodyPipeline->registerPhysicsInstance(0.f, position, orn, colIndex, index, false);
303 }
304 }
305
createStaticEnvironment()306 void GpuConvexPlaneScene::createStaticEnvironment()
307 {
308 int strideInBytes = 9 * sizeof(float);
309 int numVertices = sizeof(cube_vertices) / strideInBytes;
310 int numIndices = sizeof(cube_indices) / sizeof(int);
311 //int shapeId = ci.m_instancingRenderer->registerShape(&cube_vertices[0],numVertices,cube_indices,numIndices);
312 int shapeId = m_guiHelper->getRenderInterface()->registerShape(&cube_vertices[0], numVertices, cube_indices, numIndices);
313 // int group=1;
314 // int mask=1;
315 int index = 0;
316
317 {
318 b3Vector4 scaling = b3MakeVector4(400, 400, 400, 1);
319 int colIndex = m_data->m_np->registerConvexHullShape(&cube_vertices[0], strideInBytes, numVertices, scaling);
320 b3Vector3 position = b3MakeVector3(0, -400, 0);
321 b3Quaternion orn(0, 0, 0, 1);
322
323 b3Vector4 color = b3MakeVector4(0, 0, 1, 1);
324
325 int id;
326 id = m_guiHelper->getRenderInterface()->registerGraphicsInstance(shapeId, position, orn, color, scaling);
327 int pid;
328 pid = m_data->m_rigidBodyPipeline->registerPhysicsInstance(0.f, position, orn, colIndex, index, false);
329 }
330 }
331
332 /*
333 void GpuConvexPlaneScene::createStaticEnvironment(const ConstructionInfo& ci)
334 {
335 int strideInBytes = 9*sizeof(float);
336 int numVertices = sizeof(cube_vertices)/strideInBytes;
337 int numIndices = sizeof(cube_indices)/sizeof(int);
338 //int shapeId = ci.m_instancingRenderer->registerShape(&cube_vertices[0],numVertices,cube_indices,numIndices);
339 int shapeId = ci.m_instancingRenderer->registerShape(&cube_vertices[0],numVertices,cube_indices,numIndices);
340 int group=1;
341 int mask=1;
342 int index=0;
343
344
345 {
346 b3Vector4 scaling=b3MakeVector4(100,0.001,100,1);
347
348
349 //int colIndex = m_data->m_np->registerConvexHullShape(&cube_vertices[0],strideInBytes,numVertices, scaling);
350 b3Vector3 normal=b3MakeVector3(0,1,0);
351 float constant=0.f;
352 int colIndex = m_data->m_np->registerPlaneShape(normal,constant);//>registerConvexHullShape(&cube_vertices[0],strideInBytes,numVertices, scaling);
353 b3Vector3 position=b3MakeVector3(0,0,0);
354
355
356
357 b3Quaternion orn(0,0,0,1);
358
359 b3Vector4 color=b3MakeVector4(0,0,1,1);
360
361 int id = ci.m_instancingRenderer->registerGraphicsInstance(shapeId,position,orn,color,scaling);
362 int pid = m_data->m_rigidBodyPipeline->registerPhysicsInstance(0.f,position,orn,colIndex,index,false);
363
364 }
365
366 }
367 */
368
369 struct TetraBunny
370 {
371 #include "bunny.inl"
372 };
373
374 struct TetraCube
375 {
376 #include "cube.inl"
377 };
378
nextLine(const char * buffer)379 static int nextLine(const char* buffer)
380 {
381 int numBytesRead = 0;
382
383 while (*buffer != '\n')
384 {
385 buffer++;
386 numBytesRead++;
387 }
388
389 if (buffer[0] == 0x0a)
390 {
391 buffer++;
392 numBytesRead++;
393 }
394 return numBytesRead;
395 }
396
397 static float mytetra_vertices[] =
398 {
399 -1.f, 0, -1.f, 0.5f, 0, 1, 0, 0, 0,
400 -1.f, 0, 1.f, 0.5f, 0, 1, 0, 1, 0,
401 1.f, 0, 1.f, 0.5f, 0, 1, 0, 1, 1,
402 1.f, 0, -1.f, 0.5f, 0, 1, 0, 0, 1};
403
404 static int mytetra_indices[] =
405 {
406 0, 1, 2,
407 3, 1, 2, 3, 2, 0,
408 3, 0, 1};
409
410 /* Create from TetGen .ele, .face, .node data */
createFromTetGenData(const char * ele,const char * node)411 void GpuTetraScene::createFromTetGenData(const char* ele,
412 const char* node)
413 {
414 b3Scalar scaling(10);
415
416 b3AlignedObjectArray<b3Vector3> pos;
417 int nnode = 0;
418 int ndims = 0;
419 int nattrb = 0;
420 int hasbounds = 0;
421 int result = sscanf(node, "%d %d %d %d", &nnode, &ndims, &nattrb, &hasbounds);
422 result = sscanf(node, "%d %d %d %d", &nnode, &ndims, &nattrb, &hasbounds);
423 node += nextLine(node);
424
425 //b3AlignedObjectArray<b3Vector3> rigidBodyPositions;
426 //b3AlignedObjectArray<int> rigidBodyIds;
427
428 pos.resize(nnode);
429 for (int i = 0; i < pos.size(); ++i)
430 {
431 int index = 0;
432 //int bound=0;
433 float x, y, z;
434 sscanf(node, "%d %f %f %f", &index, &x, &y, &z);
435
436 // sn>>index;
437 // sn>>x;sn>>y;sn>>z;
438 node += nextLine(node);
439
440 //for(int j=0;j<nattrb;++j)
441 // sn>>a;
442
443 //if(hasbounds)
444 // sn>>bound;
445
446 pos[index].setX(b3Scalar(x) * scaling);
447 pos[index].setY(b3Scalar(y) * scaling);
448 pos[index].setZ(b3Scalar(z) * scaling);
449 }
450
451 if (ele && ele[0])
452 {
453 int ntetra = 0;
454 int ncorner = 0;
455 int neattrb = 0;
456 sscanf(ele, "%d %d %d", &ntetra, &ncorner, &neattrb);
457 ele += nextLine(ele);
458
459 //se>>ntetra;se>>ncorner;se>>neattrb;
460 for (int i = 0; i < ntetra; ++i)
461 {
462 int index = 0;
463 int ni[4];
464
465 //se>>index;
466 //se>>ni[0];se>>ni[1];se>>ni[2];se>>ni[3];
467 sscanf(ele, "%d %d %d %d %d", &index, &ni[0], &ni[1], &ni[2], &ni[3]);
468 ele += nextLine(ele);
469
470 b3Vector3 average = b3MakeVector3(0, 0, 0);
471
472 for (int v = 0; v < 4; v++)
473 {
474 average += pos[ni[v]];
475 }
476 average /= 4;
477
478 for (int v = 0; v < 4; v++)
479 {
480 b3Vector3 shiftedPos = pos[ni[v]] - average;
481 mytetra_vertices[0 + v * 9] = shiftedPos.getX();
482 mytetra_vertices[1 + v * 9] = shiftedPos.getY();
483 mytetra_vertices[2 + v * 9] = shiftedPos.getZ();
484 }
485 //todo: subtract average
486
487 int strideInBytes = 9 * sizeof(float);
488 int numVertices = sizeof(mytetra_vertices) / strideInBytes;
489 int numIndices = sizeof(mytetra_indices) / sizeof(int);
490 int shapeId = m_instancingRenderer->registerShape(&mytetra_vertices[0], numVertices, mytetra_indices, numIndices);
491 // int group=1;
492 // int mask=1;
493
494 {
495 b3Vector4 scaling = b3MakeVector4(1, 1, 1, 1);
496 int colIndex = m_data->m_np->registerConvexHullShape(&mytetra_vertices[0], strideInBytes, numVertices, scaling);
497 b3Vector3 position = b3MakeVector3(0, 150, 0);
498 // position+=average;//*1.2;//*2;
499 position += average * 1.2; //*2;
500 //rigidBodyPositions.push_back(position);
501 b3Quaternion orn(0, 0, 0, 1);
502
503 static int curColor = 0;
504 b3Vector4 color = colors[curColor++];
505 curColor &= 3;
506
507 int id;
508 id = m_instancingRenderer->registerGraphicsInstance(shapeId, position, orn, color, scaling);
509 int pid;
510 pid = m_data->m_rigidBodyPipeline->registerPhysicsInstance(1.f, position, orn, colIndex, 0, false);
511 //rigidBodyIds.push_back(pid);
512 }
513
514 //for(int j=0;j<neattrb;++j)
515 // se>>a;
516 //psb->appendTetra(ni[0],ni[1],ni[2],ni[3]);
517 }
518 // printf("Nodes: %u\r\n",psb->m_nodes.size());
519 // printf("Links: %u\r\n",psb->m_links.size());
520 // printf("Faces: %u\r\n",psb->m_faces.size());
521 // printf("Tetras: %u\r\n",psb->m_tetras.size());
522 }
523
524 m_data->m_rigidBodyPipeline->writeAllInstancesToGpu();
525 m_data->m_np->writeAllBodiesToGpu();
526 m_data->m_bp->writeAabbsToGpu();
527 m_data->m_rigidBodyPipeline->setupGpuAabbsFull();
528 m_data->m_bp->calculateOverlappingPairs(m_data->m_config.m_maxBroadphasePairs);
529
530 int numPairs = m_data->m_bp->getNumOverlap();
531 cl_mem pairs = m_data->m_bp->getOverlappingPairBuffer();
532 b3OpenCLArray<b3Int2> clPairs(m_clData->m_clContext, m_clData->m_clQueue);
533 clPairs.setFromOpenCLBuffer(pairs, numPairs);
534 b3AlignedObjectArray<b3Int2> allPairs;
535 clPairs.copyToHost(allPairs);
536
537 for (int p = 0; p < allPairs.size(); p++)
538 {
539 b3Vector3 posA, posB;
540 b3Quaternion ornA, ornB;
541 int bodyIndexA = allPairs[p].x;
542 int bodyIndexB = allPairs[p].y;
543
544 m_data->m_np->getObjectTransformFromCpu(posA, ornA, bodyIndexA);
545 m_data->m_np->getObjectTransformFromCpu(posB, ornB, bodyIndexB);
546
547 b3Vector3 pivotWorld = (posA + posB) * 0.5f;
548 b3Transform transA, transB;
549 transA.setIdentity();
550 transA.setOrigin(posA);
551 transA.setRotation(ornA);
552 transB.setIdentity();
553 transB.setOrigin(posB);
554 transB.setRotation(ornB);
555 b3Vector3 pivotInA = transA.inverse() * pivotWorld;
556 b3Vector3 pivotInB = transB.inverse() * pivotWorld;
557
558 b3Transform frameInA, frameInB;
559 frameInA.setIdentity();
560 frameInB.setIdentity();
561 frameInA.setOrigin(pivotInA);
562 frameInB.setOrigin(pivotInB);
563 b3Quaternion relTargetAB = frameInA.getRotation() * frameInB.getRotation().inverse();
564
565 //c = new b3FixedConstraint(pid,prevBody,frameInA,frameInB);
566 float breakingThreshold = 45; //37.f;
567 //c->setBreakingImpulseThreshold(37.1);
568 bool useGPU = true;
569 if (useGPU)
570 {
571 int cid;
572 cid = m_data->m_rigidBodyPipeline->createFixedConstraint(bodyIndexA, bodyIndexB, pivotInA, pivotInB, relTargetAB, breakingThreshold);
573 }
574 else
575 {
576 b3FixedConstraint* c = new b3FixedConstraint(bodyIndexA, bodyIndexB, frameInA, frameInB);
577 c->setBreakingImpulseThreshold(breakingThreshold);
578 m_data->m_rigidBodyPipeline->addConstraint(c);
579 }
580 }
581
582 printf("numPairs = %d\n", numPairs);
583 }
584
createDynamicsObjects()585 int GpuTetraScene::createDynamicsObjects()
586 {
587 //createFromTetGenData(TetraCube::getElements(),TetraCube::getNodes());
588 createFromTetGenData(TetraBunny::getElements(), TetraBunny::getNodes());
589
590 return 0;
591 }
592
OpenCLBoxBoxCreateFunc(struct CommonExampleOptions & options)593 class CommonExampleInterface* OpenCLBoxBoxCreateFunc(struct CommonExampleOptions& options)
594 {
595 return new GpuBoxPlaneScene(options.m_guiHelper);
596 }
597