TestScene.lua 23 KB

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