]> git.lizzy.rs Git - irrlicht.git/blob - examples/07.Collision/main.cpp
Merging r6173 through r6179 from trunk to ogl-es branch
[irrlicht.git] / examples / 07.Collision / main.cpp
1 /** Example 007 Collision\r
2 \r
3 We will describe 2 methods: Automatic collision detection for moving through\r
4 3d worlds with stair climbing and sliding, and manual scene node and triangle\r
5 picking using a ray.  In this case, we will use a ray coming out from the\r
6 camera, but you can use any ray.\r
7 \r
8 To start, we take the program from tutorial 2, which loads and displays a\r
9 quake 3 level. We will use the level to walk in it and to pick triangles from.\r
10 In addition we'll place 3 animated models into it for triangle picking. The\r
11 following code starts up the engine and loads the level, as per tutorial 2.\r
12 */\r
13 #include <irrlicht.h>\r
14 #include "driverChoice.h"\r
15 #include "exampleHelper.h"\r
16 \r
17 using namespace irr;\r
18 \r
19 #ifdef _MSC_VER\r
20 #pragma comment(lib, "Irrlicht.lib")\r
21 #endif\r
22 \r
23 enum\r
24 {\r
25         // I use this ISceneNode ID to indicate a scene node that is\r
26         // not pickable by getSceneNodeAndCollisionPointFromRay()\r
27         ID_IsNotPickable = 0,\r
28 \r
29         // I use this flag in ISceneNode IDs to indicate that the\r
30         // scene node can be picked by ray selection.\r
31         IDFlag_IsPickable = 1 << 0,\r
32 \r
33         // I use this flag in ISceneNode IDs to indicate that the\r
34         // scene node can be highlighted.  In this example, the\r
35         // homonids can be highlighted, but the level mesh can't.\r
36         IDFlag_IsHighlightable = 1 << 1\r
37 };\r
38 \r
39 int main()\r
40 {\r
41         // ask user for driver\r
42         video::E_DRIVER_TYPE driverType=driverChoiceConsole();\r
43         if (driverType==video::EDT_COUNT)\r
44                 return 1;\r
45 \r
46         // create device\r
47 \r
48         IrrlichtDevice *device =\r
49                 createDevice(driverType, core::dimension2d<u32>(640, 480), 16, false);\r
50 \r
51         if (device == 0)\r
52                 return 1; // could not create selected driver.\r
53 \r
54         /*\r
55         If we want to receive information about the material of a hit triangle we have to get \r
56         collisions per meshbuffer. The only disadvantage of this is that getting them per \r
57         meshbuffer can be a little bit slower than per mesh, but usually that's not noticeable.\r
58         If you set this to false you will no longer get material names in the title bar.\r
59         */\r
60         const bool separateMeshBuffers = true;\r
61 \r
62         video::IVideoDriver* driver = device->getVideoDriver();\r
63         scene::ISceneManager* smgr = device->getSceneManager();\r
64 \r
65         const io::path mediaPath = getExampleMediaPath();\r
66 \r
67         device->getFileSystem()->addFileArchive(mediaPath + "map-20kdm2.pk3");\r
68 \r
69         scene::IAnimatedMesh* q3levelmesh = smgr->getMesh("20kdm2.bsp");\r
70         scene::IMeshSceneNode* q3node = 0;\r
71 \r
72         // The Quake mesh is pickable, but doesn't get highlighted.\r
73         if (q3levelmesh)\r
74                 q3node = smgr->addOctreeSceneNode(q3levelmesh->getMesh(0), 0, IDFlag_IsPickable);\r
75 \r
76         /*\r
77         So far so good, we've loaded the quake 3 level like in tutorial 2. Now,\r
78         here comes something different: We create a triangle selector. A\r
79         triangle selector is a class which can fetch the triangles from scene\r
80         nodes for doing different things with them, for example collision\r
81         detection. There are different triangle selectors, and all can be\r
82         created with the ISceneManager. In this example, we create an\r
83         OctreeTriangleSelector, which optimizes the triangle output a little\r
84         bit by reducing it like an octree. This is very useful for huge meshes\r
85         like quake 3 levels. After we created the triangle selector, we attach\r
86         it to the q3node. This is not necessary, but in this way, we do not\r
87         need to care for the selector, for example dropping it after we do not\r
88         need it anymore.\r
89         */\r
90 \r
91         scene::ITriangleSelector* selector = 0;\r
92 \r
93         if (q3node)\r
94         {\r
95                 q3node->setPosition(core::vector3df(-1350,-130,-1400));\r
96 \r
97                 /*\r
98                         There is currently no way to split an octree by material.\r
99                         So if we need material infos we have to create one octree per \r
100                         meshbuffer and put them together in a MetaTriangleSelector.\r
101                 */\r
102                 if ( separateMeshBuffers && q3node->getMesh()->getMeshBufferCount() > 1)\r
103                 {\r
104                         scene::IMetaTriangleSelector * metaSelector = smgr->createMetaTriangleSelector();\r
105                         for ( irr::u32 m=0; m < q3node->getMesh()->getMeshBufferCount(); ++m )\r
106                         {\r
107                                 scene::ITriangleSelector*\r
108                                         bufferSelector = smgr->createOctreeTriangleSelector(\r
109                                         q3node->getMesh()->getMeshBuffer(m), m, q3node);\r
110                                 if ( bufferSelector )\r
111                                 {\r
112                                         metaSelector->addTriangleSelector( bufferSelector );\r
113                                         bufferSelector->drop();\r
114                                 }\r
115                         }\r
116                         selector = metaSelector;\r
117                 }\r
118                 else\r
119                 {\r
120                         // If you don't need material infos just create one octree for the \r
121                         // whole mesh.\r
122                         selector = smgr->createOctreeTriangleSelector(\r
123                                         q3node->getMesh(), q3node, 128);\r
124                 }\r
125                 q3node->setTriangleSelector(selector);\r
126                 // We're not done with this selector yet, so don't drop it.\r
127         }\r
128 \r
129 \r
130         /*\r
131         We add a first person shooter camera to the scene so that we can see and\r
132         move in the quake 3 level like in tutorial 2. But this, time, we add a\r
133         special animator to the camera: A collision response animator. This\r
134         animator modifies the scene node to which it is attached in order to\r
135         prevent it from moving through walls and to add gravity to the node. The\r
136         only things we have to tell the animator is how the world looks like,\r
137         how big the scene node is, how much gravity to apply and so on. After the\r
138         collision response animator is attached to the camera, we do not have to do\r
139         anything else for collision detection, it's all done automatically.\r
140         The rest of the collision detection code below is for picking. And please\r
141         note another cool feature: The collision response animator can be\r
142         attached also to all other scene nodes, not only to cameras. And it can\r
143         be mixed with other scene node animators. In this way, collision\r
144         detection and response in the Irrlicht engine is really easy.\r
145 \r
146         Now we'll take a closer look on the parameters of\r
147         createCollisionResponseAnimator(). The first parameter is the\r
148         TriangleSelector, which specifies how the world, against which collision\r
149         detection is done, looks like. The second parameter is the scene node,\r
150         which is the object which is affected by collision detection - in our\r
151         case it is the camera. The third defines how big the object is, it is\r
152         the radius of an ellipsoid. Try it out and change the radius to smaller\r
153         values, the camera will be able to move closer to walls after this. The\r
154         next parameter is the direction and speed of gravity.  We'll set it to\r
155         (0, -1000, 0), which approximates realistic gravity (depends on the units \r
156         which are used in the scene model). You could set it to (0,0,0) to disable \r
157         gravity. And the last value is just an offset: Without it the ellipsoid with \r
158         which collision detection is done would be around the camera and the camera \r
159         would be in the middle of the ellipsoid. But as human beings, we are used to \r
160         have our eyes on top of the body, not in the middle of it. So we place the \r
161         scene node 50 units over the center of the ellipsoid with this parameter. \r
162         And that's it, collision detection works now.\r
163         */\r
164 \r
165         // Set a jump speed of 300 units per second, which gives a fairly realistic jump\r
166         // when used with the gravity of (0, -1000, 0) in the collision response animator.\r
167         scene::ICameraSceneNode* camera =\r
168                 smgr->addCameraSceneNodeFPS(0, 100.0f, .3f, ID_IsNotPickable, 0, 0, true, 300.f);\r
169         camera->setPosition(core::vector3df(50,50,-60));\r
170         camera->setTarget(core::vector3df(-70,30,-60));\r
171 \r
172         if (selector)\r
173         {\r
174                 scene::ISceneNodeAnimatorCollisionResponse * anim = smgr->createCollisionResponseAnimator(\r
175                         selector, camera, core::vector3df(30,50,30),\r
176                         core::vector3df(0,-1000,0), core::vector3df(0,30,0));\r
177                 selector->drop(); // As soon as we're done with the selector, drop it.\r
178                 camera->addAnimator(anim);\r
179                 anim->drop();  // And likewise, drop the animator when we're done referring to it.\r
180         }\r
181 \r
182         // Now I create three animated characters which we can pick, a dynamic light for\r
183         // lighting them, and a billboard for drawing where we found an intersection.\r
184 \r
185         // First, let's get rid of the mouse cursor.  We'll use a billboard to show\r
186         // what we're looking at.\r
187         device->getCursorControl()->setVisible(false);\r
188 \r
189         // Add the billboard.\r
190         scene::IBillboardSceneNode * bill = smgr->addBillboardSceneNode();\r
191         bill->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );\r
192         bill->setMaterialTexture(0, driver->getTexture(mediaPath + "particle.bmp"));\r
193         bill->setMaterialFlag(video::EMF_LIGHTING, false);\r
194         bill->setMaterialFlag(video::EMF_ZBUFFER, false);\r
195         bill->setSize(core::dimension2d<f32>(20.0f, 20.0f));\r
196         bill->setID(ID_IsNotPickable); // This ensures that we don't accidentally ray-pick it\r
197 \r
198         /* Add 3 animated hominids, which we can pick using a ray-triangle intersection.\r
199         They all animate quite slowly, to make it easier to see that accurate triangle\r
200         selection is being performed. */\r
201         scene::IAnimatedMeshSceneNode* node = 0;\r
202 \r
203         video::SMaterial material;\r
204 \r
205         // Add an MD2 node, which uses vertex-based animation.\r
206         node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "faerie.md2"),\r
207                                                 0, IDFlag_IsPickable | IDFlag_IsHighlightable);\r
208         node->setPosition(core::vector3df(-90,-15,-140)); // Put its feet on the floor.\r
209         node->setScale(core::vector3df(1.6f)); // Make it appear realistically scaled\r
210         node->setMD2Animation(scene::EMAT_POINT);\r
211         node->setAnimationSpeed(20.f);\r
212         material.setTexture(0, driver->getTexture(mediaPath + "faerie2.bmp"));\r
213         material.Lighting = true;\r
214         material.NormalizeNormals = true;\r
215         node->getMaterial(0) = material;\r
216 \r
217         // Now create a triangle selector for it.  The selector will know that it\r
218         // is associated with an animated node, and will update itself as necessary.\r
219         selector = smgr->createTriangleSelector(node, separateMeshBuffers);\r
220         node->setTriangleSelector(selector);\r
221         selector->drop(); // We're done with this selector, so drop it now.\r
222 \r
223         // And this B3D file uses skinned skeletal animation.\r
224         node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "ninja.b3d"),\r
225                                                 0, IDFlag_IsPickable | IDFlag_IsHighlightable);\r
226         node->setScale(core::vector3df(10));\r
227         node->setPosition(core::vector3df(-75,-66,-80));\r
228         node->setRotation(core::vector3df(0,90,0));\r
229         node->setAnimationSpeed(8.f);\r
230         node->getMaterial(0).NormalizeNormals = true;\r
231         node->getMaterial(0).Lighting = true;\r
232         // Just do the same as we did above.\r
233         selector = smgr->createTriangleSelector(node, separateMeshBuffers);\r
234         node->setTriangleSelector(selector);\r
235         selector->drop();\r
236 \r
237         // This X files uses skeletal animation, but without skinning.\r
238         node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "dwarf.x"),\r
239                                                 0, IDFlag_IsPickable | IDFlag_IsHighlightable);\r
240         node->setPosition(core::vector3df(-70,-66,-30)); // Put its feet on the floor.\r
241         node->setRotation(core::vector3df(0,-90,0)); // And turn it towards the camera.\r
242         node->setAnimationSpeed(20.f);\r
243         node->getMaterial(0).Lighting = true;\r
244         selector = smgr->createTriangleSelector(node, separateMeshBuffers);\r
245         node->setTriangleSelector(selector);\r
246         selector->drop();\r
247 \r
248         // And this mdl file uses skinned skeletal animation.\r
249         node = smgr->addAnimatedMeshSceneNode(smgr->getMesh(mediaPath + "yodan.mdl"),\r
250                                                 0, IDFlag_IsPickable | IDFlag_IsHighlightable);\r
251         node->setPosition(core::vector3df(-90,-25,20));\r
252         node->setScale(core::vector3df(0.8f));\r
253         node->getMaterial(0).Lighting = true;\r
254         node->setAnimationSpeed(20.f);\r
255 \r
256         // Just do the same as we did above.\r
257         selector = smgr->createTriangleSelector(node, separateMeshBuffers);\r
258         node->setTriangleSelector(selector);\r
259         selector->drop();\r
260 \r
261         material.setTexture(0, 0);\r
262         material.Lighting = false;\r
263 \r
264         // Add a light, so that the unselected nodes aren't completely dark.\r
265         scene::ILightSceneNode * light = smgr->addLightSceneNode(0, core::vector3df(-60,100,400),\r
266                 video::SColorf(1.0f,1.0f,1.0f,1.0f), 600.0f);\r
267         light->setID(ID_IsNotPickable); // Make it an invalid target for selection.\r
268 \r
269         // Remember which scene node is highlighted\r
270         scene::ISceneNode* highlightedSceneNode = 0;\r
271         scene::ISceneCollisionManager* collMan = smgr->getSceneCollisionManager();\r
272 \r
273         // draw the selection triangle only as wireframe\r
274         material.Wireframe=true;\r
275 \r
276         while(device->run())\r
277         if (device->isWindowActive())\r
278         {\r
279                 driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(0));\r
280                 smgr->drawAll();\r
281 \r
282                 // Unlight any currently highlighted scene node\r
283                 if (highlightedSceneNode)\r
284                 {\r
285                         highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, true);\r
286                         highlightedSceneNode = 0;\r
287                 }\r
288 \r
289                 // All intersections in this example are done with a ray cast out from the camera to\r
290                 // a distance of 1000.  You can easily modify this to check (e.g.) a bullet\r
291                 // trajectory or a sword's position, or create a ray from a mouse click position using\r
292                 // ISceneCollisionManager::getRayFromScreenCoordinates()\r
293                 core::line3d<f32> ray;\r
294                 ray.start = camera->getPosition();\r
295                 ray.end = ray.start + (camera->getTarget() - ray.start).normalize() * 1000.0f;\r
296 \r
297 \r
298                 // This call is all you need to perform ray/triangle collision on every scene node\r
299                 // that has a triangle selector, including the Quake level mesh.  It finds the nearest\r
300                 // collision point/triangle, and returns the scene node containing that point.\r
301                 // Irrlicht provides other types of selection, including ray/triangle selector,\r
302                 // ray/box and ellipse/triangle selector, plus associated helpers.\r
303                 // You might also want to check the other methods of ISceneCollisionManager.\r
304 \r
305                 irr::io::SNamedPath hitTextureName;\r
306                 scene::SCollisionHit hitResult;\r
307                 scene::ISceneNode * selectedSceneNode =collMan->getSceneNodeAndCollisionPointFromRay(\r
308                                         hitResult,      // Returns all kind of info about the collision\r
309                                         ray,\r
310                                         IDFlag_IsPickable, // This ensures that only nodes that we have\r
311                                                         // set up to be pickable are considered\r
312                                         0); // Check the entire scene (this is actually the implicit default)\r
313 \r
314 \r
315                 // If the ray hit anything, move the billboard to the collision position\r
316                 // and draw the triangle that was hit.\r
317                 if(selectedSceneNode)\r
318                 {\r
319                         bill->setPosition(hitResult.Intersection);      // Show the current intersection point with the level or a mesh\r
320 \r
321                         // We need to reset the transform before doing our own rendering.\r
322                         driver->setTransform(video::ETS_WORLD, core::matrix4());\r
323                         driver->setMaterial(material);\r
324                         driver->draw3DTriangle(hitResult.Triangle, video::SColor(0,255,0,0));   // Show which triangle has been hit\r
325 \r
326                         // We can check the flags for the scene node that was hit to see if it should be\r
327                         // highlighted. The animated nodes can be highlighted, but not the Quake level mesh\r
328                         if((selectedSceneNode->getID() & IDFlag_IsHighlightable) == IDFlag_IsHighlightable)\r
329                         {\r
330                                 highlightedSceneNode = selectedSceneNode;\r
331 \r
332                                 // Highlighting in this case means turning lighting OFF for this node,\r
333                                 // which means that it will be drawn with full brightness.\r
334                                 highlightedSceneNode->setMaterialFlag(video::EMF_LIGHTING, false);\r
335                         }\r
336 \r
337                         // When separateMeshBuffers is set to true we can now find out which material was hit\r
338                         if ( hitResult.MeshBuffer && hitResult.Node && hitResult.Node->getMaterial(hitResult.MaterialIndex).TextureLayer[0].Texture )\r
339                         {\r
340                                 // Note we are interested in the node material and not in the meshbuffer material.\r
341                                 // Otherwise we wouldn't get the fairy2 texture which is only set on the node.\r
342                                 hitTextureName = hitResult.Node->getMaterial(hitResult.MaterialIndex).TextureLayer[0].Texture->getName();\r
343                         }\r
344                 }\r
345 \r
346                 // We're all done drawing, so end the scene.\r
347                 driver->endScene();\r
348 \r
349                 // Show some info in title-bar\r
350                 int fps = driver->getFPS();\r
351                 static core::stringw lastString;\r
352                 core::stringw str = L"Collision detection example - Irrlicht Engine [";\r
353                 str += driver->getName();\r
354                 str += "] FPS:";\r
355                 str += fps;\r
356                 if ( !hitTextureName.getInternalName().empty() )\r
357                 {\r
358                         str += " ";\r
359                         irr::io::path texName(hitTextureName.getInternalName());\r
360                         str += core::deletePathFromFilename(texName);\r
361                 }\r
362                 if ( str != lastString )        // changing caption is somewhat expensive, so don't when nothing changed\r
363                 {\r
364                         device->setWindowCaption(str.c_str());\r
365                         lastString = str;\r
366                 }\r
367         }\r
368 \r
369         device->drop();\r
370 \r
371         return 0;\r
372 }\r
373 \r
374 /*\r
375 **/\r