TestScene.lua 23 KB

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