TestScene.lua 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  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 decal = result.drawable:GetNode():GetComponent("DecalSet")
  320. if decal == nil then
  321. decal = result.drawable:GetNode():CreateComponent("DecalSet")
  322. decal.material = cache:GetResource("Material", "Materials/UrhoDecal.xml")
  323. -- Increase max. vertices/indices if the target is skinned
  324. if result.drawable:GetTypeName() == "AnimatedModel" then
  325. decal.maxVertices = 2048
  326. decal.maxIndices = 4096
  327. end
  328. end
  329. decal:AddDecal(result.drawable, result.position, cameraNode:GetWorldRotation(), 0.5, 1.0, 1.0, Vector2(0, 0), Vector2(1, 1))
  330. end
  331. end
  332. end
  333. end
  334. end
  335. function HandleSpawnBox(eventType, eventData)
  336. local position = eventData:GetVector3("Pos")
  337. local rotation = eventData:GetQuaternion("Rot")
  338. local newNode = testScene:CreateChild("")
  339. newNode.position = position
  340. newNode.rotation =rotation
  341. newNode:SetScale(0.2)
  342. local body = newNode:CreateComponent("RigidBody")
  343. body.mass = 1.0
  344. body.friction = 1.0
  345. body.linearVelocity = rotation * Vector3(0.0, 1.0, 10.0)
  346. local shape = newNode:CreateComponent("CollisionShape")
  347. shape:SetBox(Vector3(1, 1, 1))
  348. local object = newNode:CreateComponent("StaticModel")
  349. object.model = cache:GetResource("Model", "Models/Box.mdl")
  350. object.material = cache:GetResource("Material", "Materials/StoneSmall.xml")
  351. object.castShadows = true
  352. object.shadowDistance = 150.0
  353. object.drawDistance = 200.0
  354. end
  355. function HandleMouseButtonUp(eventType, eventData)
  356. if eventData:GetInt("Button") == MOUSEB_RIGHT then
  357. ui:GetCursor():SetVisible(true)
  358. end
  359. end
  360. function HandlePostRenderUpdate()
  361. if engine.headless then
  362. return
  363. end
  364. -- Draw rendering debug geometry without depth test to see the effect of occlusion
  365. if drawDebug == 1 then
  366. renderer:DrawDebugGeometry(true)
  367. end
  368. if drawDebug == 2 then
  369. testScene:GetComponent("PhysicsWorld"):DrawDebugGeometry(true)
  370. end
  371. local pos = ui.cursorPosition
  372. if ui:GetElementAt(pos, true) == nil and testScene:GetComponent("Octree") ~= nil then
  373. local cameraRay = camera:GetScreenRay(pos.x / graphics:GetWidth(), pos.y / graphics:GetHeight())
  374. local result = testScene:GetComponent("Octree"):RaycastSingle(cameraRay, RAY_TRIANGLE, 250.0, DRAWABLE_GEOMETRY)
  375. if result.drawable ~= nil then
  376. local rayHitPos = cameraRay.origin + cameraRay.direction * result.distance
  377. testScene:GetComponent("DebugRenderer"):AddBoundingBox(BoundingBox(rayHitPos + Vector3(-0.01, -0.01, -0.01), rayHitPos +
  378. Vector3(0.01, 0.01, 0.01)), Color(1.0, 1.0, 1.0), true)
  379. end
  380. end
  381. end
  382. function HandleClientConnected(eventType, eventData)
  383. local connection = eventData:GetPtr("Connection", "Connection")
  384. connection.scene = testScene -- Begin scene replication to the client
  385. connection.logStatistics = true
  386. end
  387. function HandlePhysicsCollision(eventType, eventData)
  388. -- Check if either of the nodes has an AnimatedModel component
  389. local nodeA = eventData:GetPtr("Node", "NodeA")
  390. local nodeB = eventData:GetPtr("Node", "NodeB")
  391. if nodeA:HasComponent("AnimatedModel") then
  392. HandleHit(nodeA)
  393. elseif nodeB:HasComponent("AnimatedModel") then
  394. HandleHit(nodeB)
  395. end
  396. end
  397. function HandleHit(node)
  398. -- Remove the trigger physics shape, and create the ragdoll
  399. node:RemoveComponent("RigidBody")
  400. node:RemoveComponent("CollisionShape")
  401. CreateRagdoll(node:GetComponent("AnimatedModel"))
  402. end
  403. function CreateRagdoll(model)
  404. local root = model:GetNode()
  405. CreateRagdollBone(root, "Bip01_Pelvis", SHAPE_BOX, Vector3(0.3, 0.2, 0.25), Vector3(0, 0, 0), Quaternion(0, 0, 0))
  406. CreateRagdollBone(root, "Bip01_Spine1", SHAPE_BOX, Vector3(0.35, 0.2, 0.3), Vector3(0.15, 0, 0), Quaternion(0, 0, 0))
  407. CreateRagdollBone(root, "Bip01_L_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  408. CreateRagdollBone(root, "Bip01_R_Thigh", SHAPE_CAPSULE, Vector3(0.175, 0.45, 0.175), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  409. CreateRagdollBone(root, "Bip01_L_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  410. CreateRagdollBone(root, "Bip01_R_Calf", SHAPE_CAPSULE, Vector3(0.15, 0.55, 0.15), Vector3(0.25, 0, 0), Quaternion(0, 0, 90))
  411. CreateRagdollBone(root, "Bip01_Head", SHAPE_BOX, Vector3(0.2, 0.2, 0.2), Vector3(0.1, 0, 0), Quaternion(0, 0, 0))
  412. CreateRagdollBone(root, "Bip01_L_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0, 0), Quaternion(0, 0, 90))
  413. CreateRagdollBone(root, "Bip01_R_UpperArm", SHAPE_CAPSULE, Vector3(0.15, 0.35, 0.15), Vector3(0.1, 0, 0), Quaternion(0, 0, 90))
  414. CreateRagdollBone(root, "Bip01_L_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0, 0), Quaternion(0, 0, 90))
  415. CreateRagdollBone(root, "Bip01_R_Forearm", SHAPE_CAPSULE, Vector3(0.125, 0.4, 0.125), Vector3(0.2, 0, 0), Quaternion(0, 0, 90))
  416. CreateRagdollConstraint(root, "Bip01_L_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 45), Vector2(0, 0), true)
  417. CreateRagdollConstraint(root, "Bip01_R_Thigh", "Bip01_Pelvis", CONSTRAINT_CONETWIST, Vector3(0, 0, -1), Vector3(0, 0, 1), Vector2(45, 45), Vector2(0, 0), true)
  418. 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)
  419. 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)
  420. CreateRagdollConstraint(root, "Bip01_Spine1", "Bip01_Pelvis", CONSTRAINT_HINGE, Vector3(0, 0, 1), Vector3(0, 0, 1), Vector2(45, 0), Vector2(-10, 0), true)
  421. CreateRagdollConstraint(root, "Bip01_Head", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(1, 0, 0), Vector3(1, 0, 0), Vector2(0, 30), Vector2(0, 0), true)
  422. CreateRagdollConstraint(root, "Bip01_L_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0), false)
  423. CreateRagdollConstraint(root, "Bip01_R_UpperArm", "Bip01_Spine1", CONSTRAINT_CONETWIST, Vector3(0, -1, 0), Vector3(0, 1, 0), Vector2(45, 45), Vector2(0, 0), false)
  424. 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)
  425. 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)
  426. -- Disable animation from all bones (both physical and non-physical) to not interfere
  427. local skel = model:GetSkeleton()
  428. for i = 1, skel:GetNumBones() do
  429. skel:GetBone(i-1).animated = false
  430. end
  431. end
  432. function CreateRagdollBone(root, boneName, type, size, position, rotation)
  433. local boneNode = root:GetChild(boneName, true)
  434. if boneNode == nil or boneNode:HasComponent("RigidBody") then
  435. return
  436. end
  437. -- In networked operation both client and server detect collisions separately, and create ragdolls on their own
  438. -- (bones are not synced over network.) To prevent replicated component ID range clashes when the client creates
  439. -- any components, it is important that the LOCAL creation mode is specified.
  440. local body = boneNode:CreateComponent("RigidBody", LOCAL)
  441. body.mass = 1.0
  442. body.linearDamping = 0.05
  443. body.angularDamping = 0.85
  444. body.linearRestThreshold = 1.5
  445. body.angularRestThreshold = 2.5
  446. local shape = boneNode:CreateComponent("CollisionShape", LOCAL)
  447. shape.shapeType = type
  448. shape.size = size
  449. shape.position = position
  450. shape.rotation = rotation
  451. end
  452. function CreateRagdollConstraint(root, boneName, parentName, type, axis, parentAxis, highLimit, lowLimit, disableCollision)
  453. local boneNode = root:GetChild(boneName, true)
  454. local parentNode = root:GetChild(parentName, true)
  455. if boneNode == nil or parentNode == nil or boneNode:HasComponent("Constraint") then
  456. return
  457. end
  458. local constraint = boneNode:CreateComponent("Constraint", LOCAL)
  459. constraint.constraintType = type
  460. constraint.disableCollision = disableCollision
  461. -- The connected body must be specified before setting the world position
  462. constraint.otherBody = parentNode:GetComponent("RigidBody")
  463. constraint.worldPosition = boneNode.worldPosition
  464. constraint:SetAxis(axis)
  465. constraint:SetOtherAxis(parentAxis)
  466. constraint.highLimit = highLimit
  467. constraint.lowLimit = lowLimit
  468. end