2
0

TestScene.as 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610
  1. #include "Scripts/Utilities/Network.as"
  2. Scene@ testScene;
  3. Camera@ camera;
  4. Node@ cameraNode;
  5. float yaw = 0.0;
  6. float pitch = 0.0;
  7. int drawDebug = 0;
  8. Text@ downloadsText;
  9. Array<Node@> hitObjects;
  10. void Start()
  11. {
  12. if (!engine.headless)
  13. {
  14. InitConsole();
  15. InitUI();
  16. }
  17. else
  18. OpenConsoleWindow();
  19. ParseNetworkArguments();
  20. InitScene();
  21. SubscribeToEvent("Update", "HandleUpdate");
  22. SubscribeToEvent("KeyDown", "HandleKeyDown");
  23. SubscribeToEvent("MouseMove", "HandleMouseMove");
  24. SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown");
  25. SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp");
  26. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate");
  27. SubscribeToEvent("SpawnBox", "HandleSpawnBox");
  28. SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision");
  29. SubscribeToEvent("PhysicsPostStep", "HandlePhysicsPostStep");
  30. network.RegisterRemoteEvent("SpawnBox");
  31. if (runServer)
  32. {
  33. network.StartServer(serverPort);
  34. SubscribeToEvent("ClientConnected", "HandleClientConnected");
  35. // Disable physics interpolation to ensure clients get sent physically correct transforms
  36. testScene.physicsWorld.interpolation = false;
  37. // Test package download by adding all package files in the cache as requirements for the scene
  38. Array<PackageFile@> packages = cache.packageFiles;
  39. for (uint i = 0; i < packages.length; ++i)
  40. testScene.AddRequiredPackageFile(packages[i]);
  41. }
  42. if (runClient)
  43. {
  44. // Test package download. Remove existing Data.pak from resource cache so that it will be downloaded
  45. // However, be sure to add the Data directory so that resource requests do not fail in the meanwhile
  46. String packageName = fileSystem.programDir + "Data.pak";
  47. cache.RemovePackageFile(packageName, false);
  48. cache.AddResourceDir(fileSystem.programDir + "Data");
  49. network.packageCacheDir = fileSystem.programDir;
  50. network.Connect(serverAddress, serverPort, testScene);
  51. }
  52. }
  53. void InitConsole()
  54. {
  55. XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
  56. engine.CreateDebugHud();
  57. debugHud.style = uiStyle;
  58. debugHud.mode = DEBUGHUD_SHOW_ALL;
  59. engine.CreateConsole();
  60. console.style = uiStyle;
  61. }
  62. void InitUI()
  63. {
  64. XMLFile@ uiStyle = cache.GetResource("XMLFile", "UI/DefaultStyle.xml");
  65. Cursor@ newCursor = Cursor("Cursor");
  66. newCursor.style = uiStyle;
  67. newCursor.position = IntVector2(graphics.width / 2, graphics.height / 2);
  68. ui.cursor = newCursor;
  69. if (GetPlatform() == "Android")
  70. ui.cursor.visible = false;
  71. downloadsText = Text();
  72. downloadsText.SetAlignment(HA_CENTER, VA_CENTER);
  73. downloadsText.SetFont(cache.GetResource("Font", "Fonts/Anonymous Pro.ttf"), 20);
  74. ui.root.AddChild(downloadsText);
  75. }
  76. void InitScene()
  77. {
  78. testScene = Scene("TestScene");
  79. // Enable access to this script file & scene from the console
  80. script.defaultScene = testScene;
  81. script.defaultScriptFile = scriptFile;
  82. // Create the camera outside the scene so it is unaffected by scene load/save
  83. cameraNode = Node();
  84. camera = cameraNode.CreateComponent("Camera");
  85. cameraNode.position = Vector3(0, 2, 0);
  86. if (!engine.headless)
  87. {
  88. renderer.viewports[0] = Viewport(testScene, camera);
  89. // Add bloom & FXAA effects to the renderpath. Clone the default renderpath so that we don't affect it
  90. RenderPath@ newRenderPath = renderer.viewports[0].renderPath.Clone();
  91. newRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/Bloom.xml"));
  92. newRenderPath.Append(cache.GetResource("XMLFile", "PostProcess/EdgeFilter.xml"));
  93. newRenderPath.SetActive("Bloom", false);
  94. newRenderPath.SetActive("EdgeFilter", false);
  95. renderer.viewports[0].renderPath = newRenderPath;
  96. audio.listener = cameraNode.CreateComponent("SoundListener");
  97. }
  98. if (runClient)
  99. return;
  100. PhysicsWorld@ world = testScene.CreateComponent("PhysicsWorld");
  101. testScene.CreateComponent("Octree");
  102. testScene.CreateComponent("DebugRenderer");
  103. Node@ zoneNode = testScene.CreateChild("Zone");
  104. Zone@ zone = zoneNode.CreateComponent("Zone");
  105. zone.ambientColor = Color(0.15, 0.15, 0.15);
  106. zone.fogColor = Color(0.5, 0.5, 0.7);
  107. zone.fogStart = 100.0;
  108. zone.fogEnd = 300.0;
  109. zone.boundingBox = BoundingBox(-1000, 1000);
  110. {
  111. Node@ lightNode = testScene.CreateChild("GlobalLight");
  112. lightNode.direction = Vector3(0.3, -0.5, 0.425);
  113. Light@ light = lightNode.CreateComponent("Light");
  114. light.lightType = LIGHT_DIRECTIONAL;
  115. light.castShadows = true;
  116. light.shadowBias = BiasParameters(0.0001, 0.5);
  117. light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8);
  118. light.specularIntensity = 0.5;
  119. }
  120. {
  121. Node@ objectNode = testScene.CreateChild("Floor");
  122. objectNode.position = Vector3(0, -0.5, 0);
  123. objectNode.scale = Vector3(200, 1, 200);
  124. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  125. object.model = cache.GetResource("Model", "Models/Box.mdl");
  126. object.material = cache.GetResource("Material", "Materials/StoneTiled.xml");
  127. object.occluder = true;
  128. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  129. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  130. shape.SetBox(Vector3(1, 1, 1));
  131. }
  132. for (uint i = 0; i < 50; ++i)
  133. {
  134. Node@ objectNode = testScene.CreateChild("Box");
  135. objectNode.position = Vector3(Random() * 180 - 90, 1, Random() * 180 - 90);
  136. objectNode.SetScale(2);
  137. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  138. object.model = cache.GetResource("Model", "Models/Box.mdl");
  139. object.material = cache.GetResource("Material", "Materials/Stone.xml");
  140. object.castShadows = true;
  141. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  142. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  143. shape.SetBox(Vector3(1, 1, 1));
  144. }
  145. for (uint i = 0; i < 10; ++i)
  146. {
  147. Node@ objectNode = testScene.CreateChild("Box");
  148. objectNode.position = Vector3(Random() * 180 - 90, 10, Random() * 180 - 90);
  149. objectNode.SetScale(20);
  150. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  151. object.model = cache.GetResource("Model", "Models/Box.mdl");
  152. object.material = cache.GetResource("Material", "Materials/Stone.xml");
  153. object.castShadows = true;
  154. object.occluder = true;
  155. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  156. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  157. shape.SetBox(Vector3(1, 1, 1));
  158. }
  159. for (uint i = 0; i < 50; ++i)
  160. {
  161. Node@ objectNode = testScene.CreateChild("Mushroom");
  162. objectNode.position = Vector3(Random() * 180 - 90, 0, Random() * 180 - 90);
  163. objectNode.rotation = Quaternion(0, Random(360.0), 0);
  164. objectNode.SetScale(5);
  165. StaticModel@ object = objectNode.CreateComponent("StaticModel");
  166. object.model = cache.GetResource("Model", "Models/Mushroom.mdl");
  167. object.material = cache.GetResource("Material", "Materials/Mushroom.xml");
  168. object.castShadows = true;
  169. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  170. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  171. shape.SetTriangleMesh(cache.GetResource("Model", "Models/Mushroom.mdl"), 0);
  172. }
  173. for (uint i = 0; i < 50; ++i)
  174. {
  175. Node@ objectNode = testScene.CreateChild("Jack");
  176. objectNode.position = Vector3(Random() * 180 - 90, 0, Random() * 180 - 90);
  177. objectNode.rotation = Quaternion(0, Random() * 360, 0);
  178. AnimatedModel@ object = objectNode.CreateComponent("AnimatedModel");
  179. object.model = cache.GetResource("Model", "Models/Jack.mdl");
  180. object.material = cache.GetResource("Material", "Materials/Jack.xml");
  181. object.castShadows = true;
  182. // Create a capsule shape for detecting collisions
  183. RigidBody@ body = objectNode.CreateComponent("RigidBody");
  184. body.phantom = true;
  185. CollisionShape@ shape = objectNode.CreateComponent("CollisionShape");
  186. shape.SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0));
  187. AnimationController@ ctrl = objectNode.CreateComponent("AnimationController");
  188. ctrl.Play("Models/Jack_Walk.ani", 0, true, 0.0);
  189. }
  190. }
  191. void HandleUpdate(StringHash eventType, VariantMap& eventData)
  192. {
  193. float timeStep = eventData["TimeStep"].GetFloat();
  194. if (ui.focusElement is null)
  195. {
  196. float speedMultiplier = 1.0;
  197. if (input.keyDown[KEY_LSHIFT])
  198. speedMultiplier = 5.0;
  199. if (input.keyDown[KEY_LCTRL])
  200. speedMultiplier = 0.1;
  201. if (input.keyDown['W'])
  202. cameraNode.TranslateRelative(Vector3(0, 0, 10) * timeStep * speedMultiplier);
  203. if (input.keyDown['S'])
  204. cameraNode.TranslateRelative(Vector3(0, 0, -10) * timeStep * speedMultiplier);
  205. if (input.keyDown['A'])
  206. cameraNode.TranslateRelative(Vector3(-10, 0, 0) * timeStep * speedMultiplier);
  207. if (input.keyDown['D'])
  208. cameraNode.TranslateRelative(Vector3(10, 0, 0) * timeStep * speedMultiplier);
  209. }
  210. // Update package download status
  211. if (network.serverConnection !is null)
  212. {
  213. Connection@ connection = network.serverConnection;
  214. if (connection.numDownloads > 0)
  215. {
  216. downloadsText.text = "Downloads: " + connection.numDownloads + " Current download: " +
  217. connection.downloadName + " (" + int(connection.downloadProgress * 100.0) + "%)";
  218. }
  219. else if (!downloadsText.text.empty)
  220. downloadsText.text = "";
  221. }
  222. }
  223. void HandleKeyDown(StringHash eventType, VariantMap& eventData)
  224. {
  225. int key = eventData["Key"].GetInt();
  226. if (key == KEY_ESC)
  227. {
  228. if (ui.focusElement is null)
  229. engine.Exit();
  230. else
  231. console.visible = false;
  232. }
  233. if (key == KEY_F1)
  234. console.Toggle();
  235. if (ui.focusElement is null)
  236. {
  237. if (key == '1')
  238. {
  239. int quality = renderer.textureQuality;
  240. ++quality;
  241. if (quality > 2)
  242. quality = 0;
  243. renderer.textureQuality = quality;
  244. }
  245. if (key == '2')
  246. {
  247. int quality = renderer.materialQuality;
  248. ++quality;
  249. if (quality > 2)
  250. quality = 0;
  251. renderer.materialQuality = quality;
  252. }
  253. if (key == '3')
  254. renderer.specularLighting = !renderer.specularLighting;
  255. if (key == '4')
  256. renderer.drawShadows = !renderer.drawShadows;
  257. if (key == '5')
  258. {
  259. int size = renderer.shadowMapSize;
  260. size *= 2;
  261. if (size > 2048)
  262. size = 512;
  263. renderer.shadowMapSize = size;
  264. }
  265. if (key == '6')
  266. renderer.shadowQuality = renderer.shadowQuality + 1;
  267. if (key == '7')
  268. {
  269. bool occlusion = renderer.maxOccluderTriangles > 0;
  270. occlusion = !occlusion;
  271. renderer.maxOccluderTriangles = occlusion ? 5000 : 0;
  272. }
  273. if (key == '8')
  274. renderer.dynamicInstancing = !renderer.dynamicInstancing;
  275. if (key == ' ')
  276. {
  277. drawDebug++;
  278. if (drawDebug > 2)
  279. drawDebug = 0;
  280. }
  281. if (key == 'B')
  282. renderer.viewports[0].renderPath.ToggleActive("Bloom");
  283. if (key == 'F')
  284. renderer.viewports[0].renderPath.ToggleActive("EdgeFilter");
  285. if (key == 'O')
  286. camera.orthographic = !camera.orthographic;
  287. if (key == 'T')
  288. debugHud.Toggle(DEBUGHUD_SHOW_PROFILER);
  289. if (key == KEY_F5)
  290. {
  291. File@ xmlFile = File(fileSystem.programDir + "Data/Scenes/TestScene.xml", FILE_WRITE);
  292. testScene.SaveXML(xmlFile);
  293. }
  294. if (key == KEY_F7)
  295. {
  296. File@ xmlFile = File(fileSystem.programDir + "Data/Scenes/TestScene.xml", FILE_READ);
  297. if (xmlFile.open)
  298. testScene.LoadXML(xmlFile);
  299. }
  300. }
  301. }
  302. void HandleMouseMove(StringHash eventType, VariantMap& eventData)
  303. {
  304. if (eventData["Buttons"].GetInt() & MOUSEB_RIGHT != 0)
  305. {
  306. int mousedx = eventData["DX"].GetInt();
  307. int mousedy = eventData["DY"].GetInt();
  308. yaw += mousedx / 10.0;
  309. pitch += mousedy / 10.0;
  310. if (pitch < -90.0)
  311. pitch = -90.0;
  312. if (pitch > 90.0)
  313. pitch = 90.0;
  314. cameraNode.rotation = Quaternion(pitch, yaw, 0);
  315. }
  316. }
  317. void HandleMouseButtonDown(StringHash eventType, VariantMap& eventData)
  318. {
  319. int button = eventData["Button"].GetInt();
  320. if (button == MOUSEB_RIGHT)
  321. ui.cursor.visible = false;
  322. // Test either creating a new physics object or painting a decal (SHIFT down)
  323. if (button == MOUSEB_LEFT && ui.GetElementAt(ui.cursorPosition, true) is null && ui.focusElement is null)
  324. {
  325. if (!input.qualifierDown[QUAL_SHIFT])
  326. {
  327. VariantMap eventData;
  328. eventData["Pos"] = cameraNode.position;
  329. eventData["Rot"] = cameraNode.rotation;
  330. // If we are the client, send the spawn command as a remote event, else send locally
  331. if (runClient)
  332. {
  333. if (network.serverConnection !is null)
  334. network.serverConnection.SendRemoteEvent("SpawnBox", true, eventData);
  335. }
  336. else
  337. SendEvent("SpawnBox", eventData);
  338. }
  339. else
  340. {
  341. IntVector2 pos = ui.cursorPosition;
  342. if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
  343. {
  344. Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
  345. RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
  346. if (result.drawable !is null)
  347. {
  348. Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
  349. DecalSet@ decal = result.drawable.node.GetComponent("DecalSet");
  350. if (decal is null)
  351. {
  352. decal = result.drawable.node.CreateComponent("DecalSet");
  353. decal.material = cache.GetResource("Material", "Materials/UrhoDecal.xml");
  354. // Increase max. vertices/indices if the target is skinned
  355. if (result.drawable.typeName == "AnimatedModel")
  356. {
  357. decal.maxVertices = 2048;
  358. decal.maxIndices = 4096;
  359. }
  360. }
  361. decal.AddDecal(result.drawable, rayHitPos, cameraNode.worldRotation, 0.5, 1.0, 1.0, Vector2(0, 0),
  362. Vector2(1, 1));
  363. }
  364. }
  365. }
  366. }
  367. }
  368. void HandleSpawnBox(StringHash eventType, VariantMap& eventData)
  369. {
  370. Vector3 position = eventData["Pos"].GetVector3();
  371. Quaternion rotation = eventData["Rot"].GetQuaternion();
  372. Node@ newNode = testScene.CreateChild();
  373. newNode.position = position;
  374. newNode.rotation = rotation;
  375. newNode.SetScale(0.2);
  376. RigidBody@ body = newNode.CreateComponent("RigidBody");
  377. body.mass = 1.0;
  378. body.friction = 1.0;
  379. body.linearVelocity = rotation * Vector3(0.0, 1.0, 10.0);
  380. CollisionShape@ shape = newNode.CreateComponent("CollisionShape");
  381. shape.SetBox(Vector3(1, 1, 1));
  382. StaticModel@ object = newNode.CreateComponent("StaticModel");
  383. object.model = cache.GetResource("Model", "Models/Box.mdl");
  384. object.material = cache.GetResource("Material", "Materials/StoneSmall.xml");
  385. object.castShadows = true;
  386. object.shadowDistance = 150.0;
  387. object.drawDistance = 200.0;
  388. }
  389. void HandleMouseButtonUp(StringHash eventType, VariantMap& eventData)
  390. {
  391. if (eventData["Button"].GetInt() == MOUSEB_RIGHT)
  392. ui.cursor.visible = true;
  393. }
  394. void HandlePostRenderUpdate()
  395. {
  396. if (engine.headless)
  397. return;
  398. // Draw rendering debug geometry without depth test to see the effect of occlusion
  399. if (drawDebug == 1)
  400. renderer.DrawDebugGeometry(false);
  401. if (drawDebug == 2)
  402. testScene.physicsWorld.DrawDebugGeometry(true);
  403. IntVector2 pos = ui.cursorPosition;
  404. if (ui.GetElementAt(pos, true) is null && testScene.octree !is null)
  405. {
  406. Ray cameraRay = camera.GetScreenRay(float(pos.x) / graphics.width, float(pos.y) / graphics.height);
  407. RayQueryResult result = testScene.octree.RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY);
  408. if (result.drawable !is null)
  409. {
  410. Vector3 rayHitPos = cameraRay.origin + cameraRay.direction * result.distance;
  411. testScene.debugRenderer.AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
  412. Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true);
  413. }
  414. }
  415. }
  416. void HandleClientConnected(StringHash eventType, VariantMap& eventData)
  417. {
  418. Connection@ connection = eventData["Connection"].GetConnection();
  419. connection.scene = testScene; // Begin scene replication to the client
  420. connection.logStatistics = true;
  421. }
  422. void HandlePhysicsCollision(StringHash eventType, VariantMap& eventData)
  423. {
  424. // Check if either of the nodes has an AnimatedModel component
  425. Node@ nodeA = eventData["NodeA"].GetNode();
  426. Node@ nodeB = eventData["NodeB"].GetNode();
  427. if (nodeA.HasComponent("AnimatedModel"))
  428. hitObjects.Push(nodeA);
  429. else if (nodeB.HasComponent("AnimatedModel"))
  430. hitObjects.Push(nodeB);
  431. }
  432. void HandlePhysicsPostStep()
  433. {
  434. if (hitObjects.empty)
  435. return;
  436. for (uint i = 0; i < hitObjects.length; ++i)
  437. {
  438. Node@ node = hitObjects[i];
  439. // Remove the trigger physics shape, and create the ragdoll
  440. node.RemoveComponent("RigidBody");
  441. node.RemoveComponent("CollisionShape");
  442. CreateRagdoll(node.GetComponent("AnimatedModel"));
  443. }
  444. hitObjects.Clear();
  445. }
  446. void CreateRagdoll(AnimatedModel@ model)
  447. {
  448. Node@ root = model.node;
  449. CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_CAPSULE, Vector3(0.3, 0.3, 0.3), Vector3(0.0, 0, 0), Quaternion(0, 0, 0));
  450. CreateRagdollBone(root, "Bip01_Spine1", SHAPE_CAPSULE, Vector3(0.3, 0.4, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  451. CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  452. CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  453. CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  454. CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90));
  455. CreateRagdollBone(root, "Bip01_Head", SHAPE_SPHERE, Vector3(0.25, 0.25, 0.25), Vector3(0.1, 0, 0), Quaternion(0, 0, 0));
  456. CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
  457. CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.125, 0.35, 0.125), Vector3(0.1, 0, 0), Quaternion(0, 0, 90));
  458. CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  459. CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.1, 0.3, 0.1), Vector3(0.15, 0, 0), Quaternion(0, 0, 90));
  460. CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  461. CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  462. CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  463. CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  464. CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(90, 0), Vector2(-25, 0));
  465. CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 25), Vector2(0, 0));
  466. CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
  467. CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0));
  468. CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  469. CreateRagdollConstraint(root, "Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0));
  470. // Disable animation from all bones (both physical and non-physical) to not interfere
  471. Skeleton@ skel = model.skeleton;
  472. for (uint i = 0; i < skel.numBones; ++i)
  473. skel.bones[i].animated = false;
  474. }
  475. void CreateRagdollBone(Node@ root, const String&in boneName, ShapeType type, const Vector3&in size, const Vector3&in position,
  476. const Quaternion&in rotation)
  477. {
  478. Node@ boneNode = root.GetChild(boneName, true);
  479. if (boneNode is null || boneNode.HasComponent("RigidBody"))
  480. return;
  481. // In networked operation both client and server detect collisions separately, and create ragdolls on their own
  482. // (bones are not synced over network.) To prevent replicated component ID range clashes when the client creates
  483. // any components, it is important that the LOCAL creation mode is specified.
  484. RigidBody@ body = boneNode.CreateComponent("RigidBody", LOCAL);
  485. body.mass = 1.0;
  486. body.linearDamping = 0.05;
  487. body.angularDamping = 0.85;
  488. body.linearRestThreshold = 1.5;
  489. body.angularRestThreshold = 2.5;
  490. CollisionShape@ shape = boneNode.CreateComponent("CollisionShape", LOCAL);
  491. shape.shapeType = type;
  492. shape.size = size;
  493. shape.position = position;
  494. shape.rotation = rotation;
  495. }
  496. void CreateRagdollConstraint(Node@ root, const String&in boneName, const String&in parentName, ConstraintType type,
  497. const Vector3&in axis, const Vector3&in parentAxis, const Vector2&in highLimit, const Vector2&in lowLimit)
  498. {
  499. Node@ boneNode = root.GetChild(boneName, true);
  500. Node@ parentNode = root.GetChild(parentName, true);
  501. if (boneNode is null || parentNode is null || boneNode.HasComponent("Constraint"))
  502. return;
  503. Constraint@ constraint = boneNode.CreateComponent("Constraint", LOCAL);
  504. constraint.constraintType = type;
  505. constraint.disableCollision = true;
  506. // The connected body must be specified before setting the world position
  507. constraint.otherBody = parentNode.GetComponent("RigidBody");
  508. constraint.worldPosition = boneNode.worldPosition;
  509. constraint.axis = axis;
  510. constraint.otherAxis = parentAxis;
  511. constraint.highLimit = highLimit;
  512. constraint.lowLimit = lowLimit;
  513. }