2
0

TestSceneOld.as 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. // GraphicsTest rewrite from Urho3D 1.0
  2. const uint NUM_OBJECTS = 250;
  3. const uint NUM_LIGHTS = 20;
  4. const uint NUM_INSTANCENODES = 20;
  5. const uint NUM_INSTANCES = 50;
  6. const uint NUM_BILLBOARDNODES = 20;
  7. const uint NUM_BILLBOARDS = 15;
  8. Scene@ testScene;
  9. Node@ cameraNode;
  10. Camera@ camera;
  11. Node@ cameraLightNode;
  12. Light@ cameraLight;
  13. float yaw = 0.0;
  14. float pitch = 0.0;
  15. float objectangle = 0.0;
  16. bool paused = true;
  17. int drawDebug = 0;
  18. Array<Node@> animatingObjects;
  19. Array<Node@> billboards;
  20. Array<Node@> lights;
  21. Array<Node@> hitObjects;
  22. void Start()
  23. {
  24. if (engine.headless)
  25. {
  26. ErrorDialog("GraphicsTest", "Headless mode is not supported. The program will now exit.");
  27. engine.Exit();
  28. return;
  29. }
  30. InitConsole();
  31. InitScene();
  32. InitUI();
  33. CreateCamera();
  34. SubscribeToEvent("Update", "HandleUpdate");
  35. SubscribeToEvent("KeyDown", "HandleKeyDown");
  36. SubscribeToEvent("MouseMove", "HandleMouseMove");
  37. SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
  38. SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
  39. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
  40. SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision");
  41. SubscribeToEvent("PhysicsPostStep", "HandlePhysicsPostStep");
  42. }
  43. void InitScene()
  44. {
  45. testScene = Scene("GraphicsTest");
  46. // Make the scene directly accessible from the console
  47. script.defaultScene = testScene;
  48. testScene.CreateComponent("Octree");
  49. testScene.CreateComponent("DebugRenderer");
  50. PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
  51. // Create the directional light
  52. Node@ sunNode = testScene.CreateChild();
  53. sunNode.direction = Vector3(0.5, -1, 0.5);
  54. Light@ sunLight = sunNode.CreateComponent("Light");
  55. sunLight.lightType = LIGHT_DIRECTIONAL;
  56. sunLight.color = Color(0.2, 0.2, 0.2);
  57. sunLight.specularIntensity = 1;
  58. // Create a zone to control the ambient lighting
  59. Node@ zoneNode = testScene.CreateChild();
  60. Zone@ zone = zoneNode.CreateComponent("Zone");
  61. zone.boundingBox = BoundingBox(-1000, 1000);
  62. zone.ambientColor = Color(0.1, 0.1, 0.1);
  63. // Create the "floor"
  64. for (int y = -5; y <= 5; y++)
  65. {
  66. for (int x = -5; x <= 5; x++)
  67. {
  68. Node@ newNode = testScene.CreateChild();
  69. newNode.position = Vector3(x * 20.5, -0.5, y * 20.5);
  70. newNode.scale = Vector3(20, 1, 20);
  71. RigidBody@ body = newNode.CreateComponent("RigidBody");
  72. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  73. shape.SetBox(Vector3(1, 1, 1));
  74. StaticModel@ object = newNode.CreateComponent("StaticModel");
  75. object.model = cache.GetResource("Model", "Models/Box.mdl");
  76. object.material = cache.GetResource("Material", "Materials/Stone.xml");
  77. }
  78. }
  79. // Create 2 occluder walls
  80. for (int x = 0; x < 2; x++)
  81. {
  82. Node@ newNode = testScene.CreateChild();
  83. newNode.position = Vector3(0, 5, 0);
  84. newNode.rotation = Quaternion(x * 90, Vector3(0, 1, 0));
  85. newNode.scale = Vector3(224, 10, 1);
  86. RigidBody@ body = newNode.CreateComponent("RigidBody");
  87. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  88. shape.SetBox(Vector3(1, 1, 1));
  89. StaticModel@ object = newNode.CreateComponent("StaticModel");
  90. object.model = cache.GetResource("Model", "Models/Box.mdl");
  91. object.material = cache.GetResource("Material", "Materials/StoneTiledH.xml");
  92. object.castShadows = true;
  93. object.occluder = true;
  94. }
  95. // Create static mushroom with physics
  96. {
  97. Node@ newNode = testScene.CreateChild();
  98. newNode.position = Vector3(50, 0, 50);
  99. newNode.SetScale(10);
  100. RigidBody@ body = newNode.CreateComponent("RigidBody");
  101. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  102. shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
  103. StaticModel@ object = newNode.CreateComponent("StaticModel");
  104. object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
  105. object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
  106. object.castShadows = true;
  107. object.occluder = true;
  108. }
  109. // Create mushroom groups
  110. for (uint i = 0; i < NUM_INSTANCENODES; ++i)
  111. {
  112. Node@ newNode = testScene.CreateChild();
  113. newNode.position = Vector3(Random() * 160 - 80, 0, Random() * 160 - 80);
  114. for (uint j = 0; j < NUM_INSTANCES; ++j)
  115. {
  116. Vector3 position = Vector3(Random() * 20 - 10, 0, Random() * 20 - 10);
  117. float angle = Random() * 360;
  118. float size = 1 + Random() * 2;
  119. Node@ instance = newNode.CreateChild();
  120. instance.position = position;
  121. instance.rotation = Quaternion(angle, Vector3(0, 1, 0));
  122. instance.SetScale(size);
  123. StaticModel@ object = instance.CreateComponent("StaticModel");
  124. object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
  125. object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
  126. object.castShadows = true;
  127. }
  128. }
  129. // Create animated models
  130. for (uint i = 0; i < NUM_OBJECTS; ++i)
  131. {
  132. Node@ newNode = testScene.CreateChild("Jack");
  133. newNode.position = Vector3(Random() * 180 - 90, 0, Random() * 180 - 90);
  134. newNode.rotation = Quaternion(Random() * 360, Vector3(0, 1, 0));
  135. newNode.SetScale(1 + Random() * 0.25);
  136. AnimatedModel@ object = newNode.CreateComponent("AnimatedModel");
  137. object.model = cache.GetResource("Model", "Models/Jack.mdl");
  138. object.material = cache.GetResource("Material", "Materials/Jack.xml");
  139. object.drawDistance = 300;
  140. object.castShadows = true;
  141. object.maxLights = 2;
  142. // Because there are many animated models in the scene, test reducing animation LOD for less CPU use
  143. object.animationLodBias = 0.75;
  144. // Create a capsule shape for detecting collisions
  145. RigidBody@ body = newNode.CreateComponent("RigidBody");
  146. body.phantom = true;
  147. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  148. shape.SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0));
  149. AnimationState@ anim = object.AddAnimationState(cache.GetResource("Animation", "Models/Jack_Walk.ani"));
  150. anim.looped = true;
  151. anim.weight = 1.0;
  152. animatingObjects.Push(newNode);
  153. }
  154. // Create floating smoke clouds
  155. for (uint i = 0; i < NUM_BILLBOARDNODES; ++i)
  156. {
  157. Node@ newNode = testScene.CreateChild("Smoke");
  158. newNode.position = Vector3(Random() * 200 - 100, Random() * 15 + 5, Random() * 200 - 100);
  159. BillboardSet@ billboard = newNode.CreateComponent("BillboardSet");
  160. billboard.numBillboards = NUM_BILLBOARDS;
  161. billboard.material = cache.GetResource("Material", "Materials/LitSmoke.xml");
  162. billboard.sorted = true;
  163. for (uint j = 0; j < NUM_BILLBOARDS; ++j)
  164. {
  165. Billboard@ bb = billboard.billboards[j];
  166. bb.position = Vector3(Random() * 15 - 7.5, Random() * 8 - 4, Random() * 15 - 7.5);
  167. bb.size = Vector2(Random() * 2 + 3, Random() * 2 + 3);
  168. bb.rotation = Random() * 360;
  169. bb.enabled = true;
  170. }
  171. billboard.Updated();
  172. billboards.Push(newNode);
  173. }
  174. // Create lights
  175. for (uint i = 0; i < NUM_LIGHTS; ++i)
  176. {
  177. Node@ newNode = testScene.CreateChild("Light");
  178. Light@ light = newNode.CreateComponent("Light");
  179. Vector3 position(
  180. Random() * 150 - 75,
  181. Random() * 30 + 30,
  182. Random() * 150 - 75
  183. );
  184. Color color((RandomInt() & 1) * 0.5 + 0.5, (RandomInt() & 1) * 0.5 + 0.5, (RandomInt() & 1) * 0.5 + 0.5);
  185. if (color.r == 0.5 && color.g == 0.5 && color.b == 0.5)
  186. color = Color(1, 1, 1);
  187. float angle = Random() * 360;
  188. newNode.position = position;
  189. newNode.direction = Vector3(Sin(angle), -1, Cos(angle));
  190. light.lightType = LIGHT_SPOT;
  191. light.range = 75;
  192. light.rampTexture = cache.GetResource("Texture2D", "Textures/RampExtreme.png");
  193. light.fov = 15;
  194. light.color = color;
  195. light.specularIntensity = 1;
  196. light.castShadows = true;
  197. light.shadowBias = BiasParameters(0.00002, 0.0);
  198. light.shadowDistance = 200;
  199. light.shadowFadeDistance = 150;
  200. light.shadowResolution = 0.5;
  201. // The spot lights will not have anything near them, so move the near plane of the shadow camera farther
  202. // for better shadow depth resolution
  203. light.shadowNearFarRatio = 0.01;
  204. // Store the original rotation as a node property
  205. newNode.vars["rotation"] = newNode.rotation;
  206. lights.Push(newNode);
  207. }
  208. }
  209. void AnimateScene(float timeStep)
  210. {
  211. objectangle += 10 * timeStep;
  212. for (uint i = 0; i < lights.length; ++i)
  213. lights[i].rotation = Quaternion(0, objectangle * 2, 0) * lights[i].vars["rotation"].GetQuaternion();
  214. for (uint i = 0; i < animatingObjects.length; ++i)
  215. {
  216. AnimatedModel@ model = animatingObjects[i].GetComponent("AnimatedModel");
  217. model.animationStates["Walk"].AddTime(timeStep);
  218. }
  219. for (uint i = 0; i < billboards.length; ++i)
  220. {
  221. BillboardSet@ billboard = billboards[i].GetComponent("BillboardSet");
  222. for (uint j = 0; j < billboard.numBillboards; ++j)
  223. billboard.billboards[j].rotation += 50 * timeStep;
  224. billboard.Updated();
  225. }
  226. }
  227. void InitConsole()
  228. {
  229. XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
  230. Console@ console = engine.CreateConsole();
  231. console.style = uiStyle;
  232. console.numRows = 16;
  233. DebugHud@ hud = engine.CreateDebugHud();
  234. debugHud.style = uiStyle;
  235. debugHud.mode = DEBUGHUD_SHOW_ALL;
  236. }
  237. void InitUI()
  238. {
  239. XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
  240. Cursor@ cursor = Cursor("Cursor");
  241. cursor.style = uiStyle;
  242. cursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
  243. ui.cursor = cursor;
  244. if (GetPlatform() == "Android")
  245. ui.cursor.visible = false;
  246. }
  247. void CreateCamera()
  248. {
  249. cameraNode = testScene.CreateChild("Camera");
  250. camera = cameraNode.CreateComponent("Camera");
  251. cameraNode.position = Vector3(-50, 2, -50);
  252. cameraLightNode = cameraNode.CreateChild("CameraLight");
  253. cameraLight = cameraLightNode.CreateComponent("Light");
  254. cameraLight.lightType = LIGHT_SPOT;
  255. cameraLight.range = 50;
  256. cameraLight.color = Color(2, 2, 2);
  257. cameraLight.specularIntensity = 2;
  258. cameraLight.castShadows = true;
  259. cameraLight.shadowDistance = 200;
  260. cameraLight.shadowFadeDistance = 150;
  261. cameraLight.shadowResolution = 0.5;
  262. cameraLight.rampTexture = cache.GetResource("Texture2D", "Textures/RampWide.png");
  263. cameraLight.shapeTexture = cache.GetResource("Texture2D", "Textures/SpotWide.png");
  264. if (!engine.headless)
  265. {
  266. renderer.viewports[0] = Viewport(testScene, camera);
  267. // Add bloom & FXAA effects to the renderpath. Clone the default renderpath so that we don't affect it
  268. RenderPath@ newRenderPath = renderer.viewports[0].renderPath.Clone();
  269. newRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/Bloom.xml"));
  270. newRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/EdgeFilter.xml"));
  271. newRenderPath.SetActive("Bloom", false);
  272. newRenderPath.SetActive("EdgeFilter", false);
  273. renderer.viewports[0].renderPath = newRenderPath;
  274. audio.listener = cameraNode.CreateComponent("SoundListener");
  275. }
  276. }
  277. void HandleUpdate(StringHash eventType, VariantMap& eventData)
  278. {
  279. float timeStep = eventData["TimeStep"].GetFloat();
  280. if (!paused)
  281. AnimateScene(timeStep);
  282. if (ui.focusElement is null)
  283. {
  284. float speedMultiplier = 1.0;
  285. if (input.keyDown[KEY_LSHIFT])
  286. speedMultiplier = 5.0;
  287. if (input.keyDown[KEY_LCTRL])
  288. speedMultiplier = 0.1;
  289. if (input.keyDown['W'])
  290. cameraNode.TranslateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);
  291. if (input.keyDown['S'])
  292. cameraNode.TranslateRelative(Vector3(0, 0, -10) * timeStep * speedMultiplier);
  293. if (input.keyDown['A'])
  294. cameraNode.TranslateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
  295. if (input.keyDown['D'])
  296. cameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
  297. }
  298. }
  299. void HandleKeyDown(StringHash eventType, VariantMap& eventData)
  300. {
  301. int key = eventData["Key"].GetInt();
  302. if (key == KEY_ESC)
  303. {
  304. if (ui.focusElement is null)
  305. engine.Exit();
  306. else
  307. console.visible = false;
  308. }
  309. if (key == KEY_F1)
  310. console.Toggle();
  311. if (ui.focusElement is null)
  312. {
  313. if (key == '1')
  314. {
  315. int quality = renderer.textureQuality;
  316. ++quality;
  317. if (quality > 2)
  318. quality = 0;
  319. renderer.textureQuality = quality;
  320. }
  321. if (key == '2')
  322. {
  323. int quality = renderer.materialQuality;
  324. ++quality;
  325. if (quality > 2)
  326. quality = 0;
  327. renderer.materialQuality = quality;
  328. }
  329. if (key == '3')
  330. renderer.specularLighting = !renderer.specularLighting;
  331. if (key == '4')
  332. renderer.drawShadows = !renderer.drawShadows;
  333. if (key == '5')
  334. {
  335. int size = renderer.shadowMapSize;
  336. size *= 2;
  337. if (size > 2048)
  338. size = 512;
  339. renderer.shadowMapSize = size;
  340. }
  341. if (key == '6')
  342. renderer.shadowQuality = renderer.shadowQuality + 1;
  343. if (key == '7')
  344. {
  345. bool occlusion = renderer.maxOccluderTriangles > 0;
  346. occlusion = !occlusion;
  347. renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
  348. }
  349. if (key == '8')
  350. renderer.dynamicInstancing = !renderer.dynamicInstancing;
  351. if (key == ' ')
  352. {
  353. drawDebug++;
  354. if (drawDebug > 2)
  355. drawDebug = 0;
  356. }
  357. if (key == 'P')
  358. paused = !paused;
  359. if (key == 'L')
  360. {
  361. if (cameraLightNode.parent is testScene)
  362. {
  363. cameraLightNode.parent = cameraNode;
  364. cameraLightNode.position = Vector3(0, 0, 0);
  365. cameraLightNode.rotation = Quaternion();
  366. }
  367. else
  368. cameraLightNode.parent = testScene;
  369. }
  370. if (key == 'B')
  371. renderer.viewports[0].renderPath.ToggleActive("Bloom");
  372. if (key == 'F')
  373. renderer.viewports[0].renderPath.ToggleActive("EdgeFilter");
  374. if (key == 'O')
  375. camera.orthographic = !camera.orthographic;
  376. if (key == 'T')
  377. debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
  378. if (key == KEY_F5)
  379. {
  380. File@ xmlFile = File("Data/Scenes/TestSceneOld.xml", FILE_WRITE);
  381. testScene.SaveXML(xmlFile);
  382. }
  383. if (key == KEY_F7)
  384. {
  385. File@ xmlFile = File("Data/Scenes/TestSceneOld.xml", FILE_READ);
  386. if (xmlFile.open)
  387. {
  388. testScene.LoadXML(xmlFile);
  389. // Reacquire camera, as it is part of the scene
  390. cameraNode = testScene.GetChild("Camera", true);
  391. camera = cameraNode.GetComponent("Camera");
  392. cameraLightNode = cameraNode.GetChild("CameraLight");
  393. renderer.viewports[0] = Viewport(testScene, camera);
  394. audio.listener = cameraNode.GetComponent("SoundListener");
  395. // Reacquire animating objects
  396. animatingObjects.Clear();
  397. billboards.Clear();
  398. lights.Clear();
  399. for (uint i = 0; i < testScene.numChildren; ++i)
  400. {
  401. Node@ node = testScene.children[i];
  402. if (node.name == "Jack")
  403. animatingObjects.Push(node);
  404. else if (node.name == "Smoke")
  405. billboards.Push(node);
  406. else if (node.name == "Light")
  407. lights.Push(node);
  408. }
  409. }
  410. }
  411. }
  412. }
  413. void HandleMouseMove(StringHash eventType, VariantMap& eventData)
  414. {
  415. if (eventData["Buttons"].GetInt() & MOUSEB_RIGHT != 0)
  416. {
  417. int mousedx = eventData["DX"].GetInt();
  418. int mousedy = eventData["DY"].GetInt();
  419. yaw += mousedx / 10.0;
  420. pitch += mousedy / 10.0;
  421. if (pitch < -90.0)
  422. pitch = -90.0;
  423. if (pitch > 90.0)
  424. pitch = 90.0;
  425. cameraNode.rotation = Quaternion(pitch, yaw, 0);
  426. }
  427. }
  428. void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
  429. {
  430. int button = eventData["Button"].GetInt();
  431. if (button == MOUSEB_RIGHT)
  432. ui.cursor.visible = false;
  433. // Test either creating a new physics object or painting a decal (SHIFT down)
  434. if (button == MOUSEB_LEFT && ui.GetElementAt(ui.cursorPosition, true) is null && ui.focusElement is null)
  435. {
  436. if (!input.qualifierDown[QUAL_SHIFT])
  437. {
  438. Node@ newNode = testScene.CreateChild();
  439. newNode.position = cameraNode.position;
  440. newNode.rotation = cameraNode.rotation;
  441. newNode.SetScale(0.2);
  442. RigidBody@ body = newNode.CreateComponent("RigidBody");
  443. body.mass = 1.0;
  444. body.friction = 1.0;
  445. body.linearVelocity = cameraNode.rotation * Vector3(0.0, 1.0, 10.0);
  446. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  447. shape.SetBox(Vector3(1, 1, 1));
  448. StaticModel@ object = newNode.CreateComponent("StaticModel");
  449. object.model = cache.GetResource("Model", "Models/Box.mdl");
  450. object.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
  451. object.castShadows = true;
  452. object.shadowDistance = 150.0;
  453. object.drawDistance = 200.0;
  454. }
  455. else
  456. {
  457. IntVector2 pos = ui.cursorPosition;
  458. if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
  459. {
  460. Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
  461. RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
  462. if (result.drawable !is null)
  463. {
  464. Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
  465. DecalSet@ decal = result.drawable.node.GetComponent("DecalSet");
  466. if (decal is null)
  467. {
  468. decal = result.drawable.node.CreateComponent("DecalSet");
  469. decal.material = cache.GetResource("Material", "Materials/UrhoDecal.xml");
  470. // Increase max. vertices/indices if the target is skinned
  471. if (result.drawable.typeName == "AnimatedModel")
  472. {
  473. decal.maxVertices = 2048;
  474. decal.maxIndices = 4096;
  475. }
  476. }
  477. decal.AddDecal(result.drawable, rayHitPos, cameraNode.worldRotation, 0.5, 1.0, 1.0, Vector2(0, 0),
  478. Vector2(1, 1));
  479. }
  480. }
  481. }
  482. }
  483. }
  484. void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
  485. {
  486. if (eventData["Button"].GetInt() == MOUSEB_RIGHT)
  487. ui.cursor.visible = true;
  488. }
  489. void HandlePostRenderUpdate()
  490. {
  491. if (engine.headless)
  492. return;
  493. // Draw rendering debug geometry without depth test to see the effect of occlusion
  494. if (drawDebug == 1)
  495. renderer.DrawDebugGeometry(false);
  496. if (drawDebug == 2)
  497. testScene.physicsWorld.DrawDebugGeometry(true);
  498. IntVector2 pos = ui.cursorPosition;
  499. if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
  500. {
  501. Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
  502. RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
  503. if (result.drawable !is null)
  504. {
  505. Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
  506. testScene.debugRenderer.AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
  507. Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
  508. }
  509. }
  510. }
  511. void HandlePhysicsCollision(StringHash eventType, VariantMap& eventData)
  512. {
  513. // Check if either of the nodes has an AnimatedModel component
  514. Node@ nodeA = eventData["NodeA"].GetNode();
  515. Node@ nodeB = eventData["NodeB"].GetNode();
  516. if (nodeA.HasComponent("AnimatedModel"))
  517. hitObjects.Push(nodeA);
  518. else if (nodeB.HasComponent("AnimatedModel"))
  519. hitObjects.Push(nodeB);
  520. }
  521. void HandlePhysicsPostStep()
  522. {
  523. if (hitObjects.empty)
  524. return;
  525. for (uint i = 0; i < hitObjects.length; ++i)
  526. {
  527. Node@ node = hitObjects[i];
  528. // Remove the trigger physics shape, and create the ragdoll
  529. node.RemoveComponent("RigidBody");
  530. node.RemoveComponent("CollisionShape");
  531. CreateRagdoll(node.GetComponent("AnimatedModel"));
  532. }
  533. hitObjects.Clear();
  534. }
  535. void CreateRagdoll(AnimatedModel@ model)
  536. {
  537. Node@ root = model.node;
  538. CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_CAPSULE, Vector3(0.3, 0.3, 0.3), Vector3(0.0, 0, 0), Quaternion(0, 0, 0));
  539. CreateRagdollBone(root, "Bip01_Spine1", SHAPE_CAPSULE, Vector3(0.3, 0.4, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  540. CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  541. CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  542. CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  543. CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  544. CreateRagdollBone(root, "Bip01_Head", SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
  545. CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
  546. CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
  547. CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  548. CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  549. CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  550. CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  551. CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  552. CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  553. CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(90, 0), Vector2(-25, 0));
  554. CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  555. CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
  556. CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
  557. CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  558. CreateRagdollConstraint(root, "Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  559. // Disable animation from all bones (both physical and non-physical) to not interfere
  560. Skeleton@ skel = model.skeleton;
  561. for (uint i = 0; i < skel.numBones; ++i)
  562. skel.bones[i].animated = false;
  563. }
  564. void CreateRagdollBone(Node@ root, const String&in boneName, ShapeType type, const Vector3&in size, const Vector3&in position,
  565. const Quaternion&in rotation)
  566. {
  567. Node@ boneNode = root.GetChild(boneName, true);
  568. if (boneNode is null || boneNode.HasComponent("RigidBody"))
  569. return;
  570. RigidBody@ body = boneNode.CreateComponent("RigidBody", LOCAL);
  571. body.mass = 1.0;
  572. body.linearDamping = 0.05;
  573. body.angularDamping = 0.85;
  574. body.linearRestThreshold = 1.5;
  575. body.angularRestThreshold = 2.5;
  576. CollisionShape@ shape = boneNode.CreateComponent("CollisionShape", LOCAL);
  577. shape.shapeType = type;
  578. shape.size = size;
  579. shape.position = position;
  580. shape.rotation = rotation;
  581. }
  582. void CreateRagdollConstraint(Node@ root, const String&in boneName, const String&in parentName, ConstraintType type,
  583. const Vector3&in axis, const Vector3&in parentAxis, const Vector2&in highLimit, const Vector2&in lowLimit)
  584. {
  585. Node@ boneNode = root.GetChild(boneName, true);
  586. Node@ parentNode = root.GetChild(parentName, true);
  587. if (boneNode is null || parentNode is null || boneNode.HasComponent("Constraint"))
  588. return;
  589. Constraint@ constraint = boneNode.CreateComponent("Constraint", LOCAL);
  590. constraint.constraintType = type;
  591. constraint.disableCollision = true;
  592. // The connected body must be specified before setting the world position
  593. constraint.otherBody = parentNode.GetComponent("RigidBody");
  594. constraint.worldPosition = boneNode.worldPosition;
  595. constraint.axis = axis;
  596. constraint.otherAxis = parentAxis;
  597. constraint.highLimit = highLimit;
  598. constraint.lowLimit = lowLimit;
  599. }