TestScene.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. require "LuaScripts/Utilities/Network"
  2. local testScene
  3. local camera
  4. local cameraNode
  5. local yaw = 0
  6. local pitch = 0
  7. local drawDebug = 0
  8. function Start()
  9. if not engine:IsHeadless() then
  10. InitConsole()
  11. InitUI()
  12. else
  13. OpenConsoleWindow()
  14. end
  15. ParseNetworkArguments()
  16. InitScene()
  17. SubscribeToEvent("Update", "HandleUpdate")
  18. SubscribeToEvent("KeyDown", "HandleKeyDown")
  19. SubscribeToEvent("MouseMove", "HandleMouseMove")
  20. SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown")
  21. SubscribeToEvent("MouseButtonUp", "HandleMouseButtonUp")
  22. SubscribeToEvent("PostRenderUpdate", "HandlePostRenderUpdate")
  23. SubscribeToEvent("SpawnBox", "HandleSpawnBox")
  24. SubscribeToEvent("PhysicsCollision", "HandlePhysicsCollision")
  25. network:RegisterRemoteEvent("SpawnBox")
  26. if runServer then
  27. network:StartServer(serverPort)
  28. SubscribeToEvent("ClientConnected", "HandleClientConnected")
  29. -- Disable physics interpolation to ensure clients get sent physically correct transforms
  30. testScene:GetComponent("PhysicsWorld"):SetInterpolation(false)
  31. end
  32. if runClient then
  33. network:Connect(serverAddress, serverPort, testScene)
  34. end
  35. end
  36. function Stop()
  37. testScene = nil
  38. camera = nil
  39. cameraNode = nil
  40. end
  41. function InitConsole()
  42. local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
  43. engine:CreateDebugHud()
  44. debugHud.defaultStyle = uiStyle
  45. debugHud.mode = DEBUGHUD_SHOW_ALL
  46. engine:CreateConsole()
  47. console.defaultStyle = uiStyle
  48. end
  49. function InitUI()
  50. local uiStyle = cache:GetResource("XMLFile", "UI/DefaultStyle.xml")
  51. local newCursor = Cursor:new()
  52. newCursor.styleAuto = uiStyle
  53. newCursor.position = IntVector2(graphics:GetWidth()/ 2, graphics:GetHeight() / 2)
  54. ui.cursor = newCursor
  55. if GetPlatform() == "Android" or GetPlatform() == "iOS" then
  56. ui.cursor.visible = false
  57. end
  58. end
  59. function InitScene()
  60. testScene = Scene()
  61. -- Create the camera outside the scene so it is unaffected by scene load/save
  62. cameraNode = Node()
  63. camera = cameraNode:CreateComponent("Camera")
  64. cameraNode.position = Vector3(0, 2, 0)
  65. if not engine:IsHeadless() then
  66. renderer:SetViewport(0, Viewport:new(testScene, camera))
  67. -- Add bloom & FXAA effects to the renderpath. Clone the default renderpath so that we don't affect it
  68. -- local newRenderPathPtr = renderer:GetViewport(0):GetRenderPath():Clone()
  69. -- local newRenderPath = newRenderPathPtr:Get()
  70. local newRenderPath = renderer:GetViewport(0):GetRenderPath():Clone()
  71. newRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/Bloom.xml"))
  72. newRenderPath:Append(cache:GetResource("XMLFile", "PostProcess/EdgeFilter.xml"))
  73. newRenderPath:SetEnabled("Bloom", false)
  74. newRenderPath:SetEnabled("EdgeFilter", false)
  75. renderer:GetViewport(0):SetRenderPath(newRenderPath)
  76. audio:SetListener(cameraNode:CreateComponent("SoundListener"))
  77. end
  78. if runClient then
  79. return
  80. end
  81. local world = testScene:CreateComponent("PhysicsWorld")
  82. testScene:CreateComponent("Octree")
  83. testScene:CreateComponent("DebugRenderer")
  84. local zoneNode = testScene:CreateChild("Zone")
  85. local zone = zoneNode:CreateComponent("Zone")
  86. zone.ambientColor = Color(0.15, 0.15, 0.15)
  87. zone.fogColor = Color(0.5, 0.5, 0.7)
  88. zone.fogStart = 100.0
  89. zone.fogEnd = 300.0
  90. zone.boundingBox = BoundingBox(-1000, 1000)
  91. if true then
  92. local lightNode = testScene:CreateChild("GlobalLight")
  93. lightNode.direction = Vector3(0.3, -0.5, 0.425)
  94. local light = lightNode:CreateComponent("Light")
  95. light.lightType = LIGHT_DIRECTIONAL
  96. light.castShadows = true
  97. light.shadowBias = BiasParameters(0.00025, 0.5)
  98. light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
  99. light.specularIntensity = 0.5
  100. end
  101. if true then
  102. local objectNode = testScene:CreateChild("Floor")
  103. objectNode.position = Vector3(0, -0.5, 0)
  104. objectNode.scale = Vector3(200, 1, 200)
  105. local object = objectNode:CreateComponent("StaticModel")
  106. object.model = cache:GetResource("Model", "Models/Box.mdl")
  107. object.material = cache:GetResource("Material", "Materials/StoneTiled.xml")
  108. object.occluder = true
  109. local body = objectNode:CreateComponent("RigidBody")
  110. local shape = objectNode:CreateComponent("CollisionShape")
  111. shape:SetBox(Vector3(1, 1, 1))
  112. end
  113. for i = 1, 50 do
  114. local objectNode = testScene:CreateChild("Box")
  115. objectNode.position = Vector3(Random() * 180 - 90, 1, Random() * 180 - 90)
  116. objectNode:SetScale(2)
  117. local object = objectNode:CreateComponent("StaticModel")
  118. object.model = cache:GetResource("Model", "Models/Box.mdl")
  119. object.material = cache:GetResource("Material", "Materials/Stone.xml")
  120. object.castShadows = true
  121. local body = objectNode:CreateComponent("RigidBody")
  122. local shape = objectNode:CreateComponent("CollisionShape")
  123. shape:SetBox(Vector3(1, 1, 1))
  124. end
  125. for i = 1, 10 do
  126. local objectNode = testScene:CreateChild("Box")
  127. objectNode.position = Vector3(Random() * 180 - 90, 10, Random() * 180 - 90)
  128. objectNode:SetScale(20)
  129. local object = objectNode:CreateComponent("StaticModel")
  130. object.model = cache:GetResource("Model", "Models/Box.mdl")
  131. object.material = cache:GetResource("Material", "Materials/Stone.xml")
  132. object.castShadows = true
  133. object.occluder = true
  134. local body = objectNode:CreateComponent("RigidBody")
  135. local shape = objectNode:CreateComponent("CollisionShape")
  136. shape:SetBox(Vector3(1, 1, 1))
  137. end
  138. for i = 1, 50 do
  139. local objectNode = testScene:CreateChild("Mushroom")
  140. objectNode.position = Vector3(Random() * 180 - 90, 0, Random() * 180 - 90)
  141. objectNode.rotation = Quaternion(0, Random(360.0), 0)
  142. objectNode:SetScale(5)
  143. local object = objectNode:CreateComponent("StaticModel")
  144. object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
  145. object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
  146. object.castShadows = true
  147. local body = objectNode:CreateComponent("RigidBody")
  148. local shape = objectNode:CreateComponent("CollisionShape")
  149. shape:SetTriangleMesh(object:GetModel())
  150. end
  151. for i = 1, 50 do
  152. local objectNode = testScene:CreateChild("Jack")
  153. objectNode:SetPosition(Vector3(Random() * 180 - 90, 0, Random() * 180 - 90))
  154. objectNode:SetRotation(Quaternion(0, Random() * 360, 0))
  155. local object = objectNode:CreateComponent("AnimatedModel")
  156. object.model = cache:GetResource("Model", "Models/Jack.mdl")
  157. object.material = cache:GetResource("Material", "Materials/Jack.xml")
  158. object.castShadows = true
  159. -- Create a capsule shape for detecting collisions
  160. local body = objectNode:CreateComponent("RigidBody")
  161. body.trigger = true
  162. local shape = objectNode:CreateComponent("CollisionShape")
  163. shape:SetCapsule(0.7, 1.8, Vector3(0.0, 0.9, 0.0))
  164. local ctrl = objectNode:CreateComponent("AnimationController")
  165. ctrl:Play("Models/Jack_Walk.ani", 0, true, 0.0)
  166. end
  167. end
  168. function HandleUpdate(eventType, eventData)
  169. local timeStep = eventData:GetFloat("TimeStep")
  170. if ui:GetFocusElement() == nil then
  171. local speedMultiplier = 1.0
  172. if input:GetKeyDown(KEY_LSHIFT) then
  173. speedMultiplier = 5.0
  174. end
  175. if input:GetKeyDown(KEY_LCTRL) then
  176. speedMultiplier = 0.1
  177. end
  178. local speed = timeStep * speedMultiplier
  179. if input:GetKeyDown(KEY_W) then
  180. cameraNode:Translate(Vector3(0, 0, 10) * speed)
  181. end
  182. if input:GetKeyDown(KEY_S) then
  183. cameraNode:Translate(Vector3(0, 0, -10) * speed)
  184. end
  185. if input:GetKeyDown(KEY_A) then
  186. cameraNode:Translate(Vector3(-10, 0, 0) * speed)
  187. end
  188. if input:GetKeyDown(KEY_D) then
  189. cameraNode:Translate(Vector3(10, 0, 0) * speed)
  190. end
  191. end
  192. end
  193. function HandleKeyDown(eventType, eventData)
  194. local key = eventData:GetInt("Key")
  195. if key == KEY_ESC then
  196. if ui:GetFocusElement() == nil then
  197. engine:Exit()
  198. else
  199. console:SetVisible(false)
  200. end
  201. end
  202. if key == KEY_F1 then
  203. console:Toggle()
  204. end
  205. if ui:GetFocusElement() == nil then
  206. if key == KEY_1 then
  207. local quality = renderer:GetTextureQuality()
  208. quality = quality + 1
  209. if quality > 2 then
  210. quality = 0
  211. end
  212. renderer:SetTextureQuality(quality)
  213. end
  214. if key == KEY_2 then
  215. local quality = renderer:GetMaterialQuality()
  216. quality = quality + 1
  217. if quality > 2 then
  218. quality = 0
  219. end
  220. renderer:SetMaterialQuality(quality)
  221. end
  222. if key == KEY_3 then
  223. renderer:SetSpecularLighting(not renderer:GetSpecularLighting())
  224. end
  225. if key == KEY_4 then
  226. renderer:SetDrawShadows(not renderer:GetDrawShadows())
  227. end
  228. if key == KEY_5 then
  229. local size = renderer:GetShadowMapSize()
  230. size = size * 2
  231. if size > 2048 then
  232. size = 512
  233. end
  234. renderer:SetShadowMapSize(size)
  235. end
  236. if key == KEY_6 then
  237. renderer:SetShadowQuality(renderer:GetShadowQuality() + 1)
  238. end
  239. if key == KEY_7 then
  240. local occlusion = renderer:GetMaxOccluderTriangles() > 0
  241. occlusion = not occlusion
  242. if occlusion then
  243. renderer:SetMaxOccluderTriangles(5000)
  244. else
  245. renderer:SetMaxOccluderTriangles(0)
  246. end
  247. end
  248. if key == KEY_8 then
  249. renderer:SetDynamicInstancing(not renderer:GetDynamicInstancing())
  250. end
  251. if key == KEY_SPACE then
  252. drawDebug = drawDebug + 1
  253. if drawDebug > 2 then
  254. drawDebug = 0
  255. end
  256. end
  257. if key == KEY_B then
  258. renderer:GetViewport(0):GetRenderPath():ToggleEnabled("Bloom")
  259. end
  260. if key == KEY_F then
  261. renderer:GetViewport(0):GetRenderPath():ToggleEnabled("EdgeFilter")
  262. end
  263. if key == KEY_O then
  264. camera:SetOrthographic(not camera:IsOrthographic())
  265. end
  266. if key == KEY_T then
  267. debugHud:Toggle(DEBUGHUD_SHOW_PROFILER)
  268. end
  269. if key == KEY_F5 then
  270. testScene:SaveXML(fileSystem:GetProgramDir() + "Data/Scenes/LuaTestScene.xml")
  271. end
  272. if key == KEY_F7 then
  273. testScene:LoadXML(fileSystem:GetProgramDir() + "Data/Scenes/LuaTestScene.xml")
  274. end
  275. end
  276. end
  277. function HandleMouseMove(eventType, eventData)
  278. local buttons = eventData:GetInt("Buttons")
  279. if buttons == MOUSEB_RIGHT then
  280. local mousedx = eventData:GetInt("DX")
  281. local mousedy = eventData:GetInt("DY")
  282. yaw = yaw + (mousedx / 10.0)
  283. pitch = pitch + (mousedy / 10.0)
  284. if pitch < -90.0 then
  285. pitch = -90.0
  286. end
  287. if pitch > 90.0 then
  288. pitch = 90.0
  289. end
  290. cameraNode:SetRotation(Quaternion(pitch, yaw, 0))
  291. end
  292. end
  293. function HandleMouseButtonDown(eventType, eventData)
  294. local button = eventData:GetInt("Button")
  295. if button == MOUSEB_RIGHT then
  296. local cursor = ui:GetCursor()
  297. cursor:SetVisible(false)
  298. end
  299. -- Test either creating a new physics object or painting a decal (SHIFT down)
  300. if button == MOUSEB_LEFT and ui:GetElementAt(ui:GetCursorPosition(), true) == nil and ui:GetFocusElement() == nil then
  301. if not input:GetQualifierDown(QUAL_SHIFT) then
  302. local eventData = VariantMap()
  303. eventData:SetVector3("Pos", cameraNode:GetPosition())
  304. eventData:SetQuaternion("Rot", cameraNode:GetRotation())
  305. -- If we are the client, send the spawn command as a remote event, else send locally
  306. if runClient then
  307. if network:GetServerConnection() ~= nil then
  308. network:GetServerConnection():SendRemoteEvent("SpawnBox", true, eventData)
  309. end
  310. else
  311. SendEvent("SpawnBox", eventData)
  312. end
  313. else
  314. local pos = ui:GetCursorPosition()
  315. if ui:GetElementAt(pos, true) == nil and testScene:GetComponent("Octree") ~= nil then
  316. local cameraRay = camera:GetScreenRay(pos.x / graphics:GetWidth(), pos.y / graphics:GetHeight())
  317. local result = testScene:GetComponent("Octree"):RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY)
  318. if result.drawable ~= nil then
  319. local rayHitPos = cameraRay.origin + cameraRay.direction * result.distance
  320. local decal = result.drawable:GetNode():GetComponent("DecalSet")
  321. if decal == nil then
  322. decal = result.drawable:GetNode():CreateComponent("DecalSet")
  323. decal.material = cache:GetResource("Material", "Materials/UrhoDecal.xml")
  324. -- Increase max. vertices/indices if the target is skinned
  325. if result.drawable:GetTypeName() == "AnimatedModel" then
  326. decal.maxVertices = 2048
  327. decal.maxIndices = 4096
  328. end
  329. end
  330. decal:AddDecal(result.drawable, rayHitPos, cameraNode:GetWorldRotation(), 0.5, 1.0, 1.0, Vector2(0, 0), Vector2(1, 1))
  331. end
  332. end
  333. end
  334. end
  335. end
  336. function HandleSpawnBox(eventType, eventData)
  337. local position = eventData:GetVector3("Pos")
  338. local rotation = eventData:GetQuaternion("Rot")
  339. local newNode = testScene:CreateChild("")
  340. newNode.position = position
  341. newNode.rotation =rotation
  342. newNode:SetScale(0.2)
  343. local body = newNode:CreateComponent("RigidBody")
  344. body.mass = 1.0
  345. body.friction = 1.0
  346. body.linearVelocity = rotation * Vector3(0.0, 1.0, 10.0)
  347. local shape = newNode:CreateComponent("CollisionShape")
  348. shape:SetBox(Vector3(1, 1, 1))
  349. local object = newNode:CreateComponent("StaticModel")
  350. object.model = cache:GetResource("Model", "Models/Box.mdl")
  351. object.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
  352. object.castShadows = true
  353. object.shadowDistance = 150.0
  354. object.drawDistance = 200.0
  355. end
  356. function HandleMouseButtonUp(eventType, eventData)
  357. if eventData:GetInt("Button") == MOUSEB_RIGHT then
  358. ui:GetCursor():SetVisible(true)
  359. end
  360. end
  361. function HandlePostRenderUpdate()
  362. if engine.headless then
  363. return
  364. end
  365. -- Draw rendering debug geometry without depth test to see the effect of occlusion
  366. if drawDebug == 1 then
  367. renderer:DrawDebugGeometry(true)
  368. end
  369. if drawDebug == 2 then
  370. testScene:GetComponent("PhysicsWorld"):DrawDebugGeometry(true)
  371. end
  372. local pos = ui.cursorPosition
  373. if ui:GetElementAt(pos, true) == nil and testScene:GetComponent("Octree") ~= nil then
  374. local cameraRay = camera:GetScreenRay(pos.x / graphics:GetWidth(), pos.y / graphics:GetHeight())
  375. local result = testScene:GetComponent("Octree"):RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY)
  376. if result.drawable ~= nil then
  377. local rayHitPos = cameraRay.origin + cameraRay.direction * result.distance
  378. testScene:GetComponent("DebugRenderer"):AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
  379. Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true)
  380. end
  381. end
  382. end
  383. function HandleClientConnected(eventType, eventData)
  384. local connection = eventData:GetPtr("Connection", "Connection")
  385. connection.scene = testScene -- Begin scene replication to the client
  386. connection.logStatistics = true
  387. end
  388. function HandlePhysicsCollision(eventType, eventData)
  389. -- Check if either of the nodes has an AnimatedModel component
  390. local nodeA = eventData:GetPtr("Node", "NodeA")
  391. local nodeB = eventData:GetPtr("Node", "NodeB")
  392. if nodeA:HasComponent("AnimatedModel") then
  393. HandleHit(nodeA)
  394. elseif nodeB:HasComponent("AnimatedModel") then
  395. HandleHit(nodeB)
  396. end
  397. end
  398. function HandleHit(node)
  399. -- Remove the trigger physics shape, and create the ragdoll
  400. node:RemoveComponent("RigidBody")
  401. node:RemoveComponent("CollisionShape")
  402. CreateRagdoll(node:GetComponent("AnimatedModel"))
  403. end
  404. function CreateRagdoll(model)
  405. local root = model:GetNode()
  406. CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_BOX, Vector3(0.3, 0.2, 0.25), Vector3(0, 0, 0), Quaternion(0, 0, 0))
  407. CreateRagdollBone(root, "Bip01_Spine1", SHAPE_BOX, Vector3(0.35, 0.2, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 0))
  408. CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  409. CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  410. CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  411. CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  412. CreateRagdollBone(root, "Bip01_Head", SHAPE_BOX, Vector3(0.2, 0.2, 0.2), Vector3(0.1, 0, 0), Quaternion(0, 0, 0))
  413. CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0, 0), Quaternion(0, 0, 90))
  414. CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0, 0), Quaternion(0, 0, 90))
  415. CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0, 0), Quaternion(0, 0, 90))
  416. CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0, 0), Quaternion(0, 0, 90))
  417. CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 45), Vector2(0, 0), true)
  418. CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 45), Vector2(0, 0), true)
  419. CreateRagdollConstraint(root, "Bip01_L_Calf", "Bip01_L_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0), true)
  420. CreateRagdollConstraint(root, "Bip01_R_Calf", "Bip01_R_Thigh", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0), true)
  421. CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 0), Vector2(-10, 0), true)
  422. CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(1, 0, 0), Vector3(1, 0, 0), Vector2(0, 30), Vector2(0, 0), true)
  423. CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0), false)
  424. CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0), false)
  425. CreateRagdollConstraint(root, "Bip01_L_Forearm", "Bip01_L_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0), true)
  426. CreateRagdollConstraint(root, "Bip01_R_Forearm", "Bip01_R_UpperArm", CONSTRAINT_HINGE, Vector3(0, 0, -1), Vector3(0, 0, -1), Vector2(90, 0), Vector2(0, 0), true)
  427. -- Disable animation from all bones (both physical and non-physical) to not interfere
  428. local skel = model:GetSkeleton()
  429. for i = 1, skel:GetNumBones() do
  430. skel:GetBone(i-1).animated = false
  431. end
  432. end
  433. function CreateRagdollBone(root, boneName, type, size, position, rotation)
  434. local boneNode = root:GetChild(boneName, true)
  435. if boneNode == nil or boneNode:HasComponent("RigidBody") then
  436. return
  437. end
  438. -- In networked operation both client and server detect collisions separately, and create ragdolls on their own
  439. -- (bones are not synced over network.) To prevent replicated component ID range clashes when the client creates
  440. -- any components, it is important that the LOCAL creation mode is specified.
  441. local body = boneNode:CreateComponent("RigidBody", LOCAL)
  442. body.mass = 1.0
  443. body.linearDamping = 0.05
  444. body.angularDamping = 0.85
  445. body.linearRestThreshold = 1.5
  446. body.angularRestThreshold = 2.5
  447. local shape = boneNode:CreateComponent("CollisionShape", LOCAL)
  448. shape.shapeType = type
  449. shape.size = size
  450. shape.position = position
  451. shape.rotation = rotation
  452. end
  453. function CreateRagdollConstraint(root, boneName, parentName, type, axis, parentAxis, highLimit, lowLimit, disableCollision)
  454. local boneNode = root:GetChild(boneName, true)
  455. local parentNode = root:GetChild(parentName, true)
  456. if boneNode == nil or parentNode == nil or boneNode:HasComponent("Constraint") then
  457. return
  458. end
  459. local constraint = boneNode:CreateComponent("Constraint", LOCAL)
  460. constraint.constraintType = type
  461. constraint.disableCollision = disableCollision
  462. -- The connected body must be specified before setting the world position
  463. constraint.otherBody = parentNode:GetComponent("RigidBody")
  464. constraint.worldPosition = boneNode.worldPosition
  465. constraint:SetAxis(axis)
  466. constraint:SetOtherAxis(parentAxis)
  467. constraint.highLimit = highLimit
  468. constraint.lowLimit = lowLimit
  469. end