2
0

19_VehicleDemo.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. -- Vehicle example.
  2. -- This sample demonstrates:
  3. -- - Creating a heightmap terrain with collision
  4. -- - Constructing a physical vehicle with rigid bodies for the hull and the wheels, joined with constraints
  5. require "LuaScripts/Utilities/Sample"
  6. local CTRL_FORWARD = 1
  7. local CTRL_BACK = 2
  8. local CTRL_LEFT = 4
  9. local CTRL_RIGHT = 8
  10. local CAMERA_DISTANCE = 10.0
  11. local YAW_SENSITIVITY = 0.1
  12. local ENGINE_POWER = 10.0
  13. local DOWN_FORCE = 10.0
  14. local MAX_WHEEL_ANGLE = 22.5
  15. local scene_ = nil
  16. local cameraNode = nil
  17. local vehicleNode = nil
  18. local context = GetContext()
  19. local cache = GetCache()
  20. local fileSystem = GetFileSystem()
  21. local input = GetInput()
  22. local renderer = GetRenderer()
  23. local ui = GetUI()
  24. function Start()
  25. -- Execute the common startup for samples
  26. SampleStart()
  27. -- Create static scene content
  28. CreateScene()
  29. -- Create the controllable vehicle
  30. CreateVehicle()
  31. -- Create the UI content
  32. CreateInstructions()
  33. -- Subscribe to necessary events
  34. SubscribeToEvents()
  35. end
  36. function CreateScene()
  37. scene_ = Scene(context)
  38. -- Create scene subsystem components
  39. scene_:CreateComponent("Octree")
  40. scene_:CreateComponent("PhysicsWorld")
  41. -- Create camera and define viewport. Camera does not necessarily have to belong to the scene
  42. cameraNode = Node(context)
  43. local camera = cameraNode:CreateComponent("Camera")
  44. camera.farClip = 500.0
  45. renderer:SetViewport(0, Viewport:new(context, scene_, camera))
  46. -- Create static scene content. First create a zone for ambient lighting and fog control
  47. local zoneNode = scene_:CreateChild("Zone")
  48. local zone = zoneNode:CreateComponent("Zone")
  49. zone.ambientColor = Color(0.15, 0.15, 0.15)
  50. zone.fogColor = Color(0.5, 0.5, 0.7)
  51. zone.fogStart = 300.0
  52. zone.fogEnd = 500.0
  53. zone.boundingBox = BoundingBox(-2000.0, 2000.0)
  54. -- Create a directional light to the world. Enable cascaded shadows on it
  55. local lightNode = scene_:CreateChild("DirectionalLight")
  56. lightNode.direction = Vector3(0.3, -0.5, 0.425)
  57. local light = lightNode:CreateComponent("Light")
  58. light.lightType = LIGHT_DIRECTIONAL
  59. light.castShadows = true
  60. light.shadowBias = BiasParameters(0.00025, 0.5)
  61. light.shadowCascade = CascadeParameters(10.0, 50.0, 200.0, 0.0, 0.8)
  62. light.specularIntensity = 0.5
  63. -- Create heightmap terrain with collision
  64. local terrainNode = scene_:CreateChild("Terrain")
  65. terrainNode.position = Vector3(0.0, 0.0, 0.0)
  66. local terrain = terrainNode:CreateComponent("Terrain")
  67. terrain.patchSize = 64
  68. terrain.spacing = Vector3(2.0, 0.1, 2.0) -- Spacing between vertices and vertical resolution of the height map
  69. terrain.smoothing = true
  70. terrain.heightMap = cache:GetResource("Image", "Textures/HeightMap.png")
  71. terrain.material = cache:GetResource("Material", "Materials/Terrain.xml")
  72. -- The terrain consists of large triangles, which fits well for occlusion rendering, as a hill can occlude all
  73. -- terrain patches and other objects behind it
  74. terrain.occluder = true
  75. local body = terrainNode:CreateComponent("RigidBody")
  76. body.collisionLayer = 2 -- Use layer bitmask 2 for static geometry
  77. local shape = terrainNode:CreateComponent("CollisionShape")
  78. shape:SetTerrain()
  79. -- Create 1000 mushrooms in the terrain. Always face outward along the terrain normal
  80. local NUM_MUSHROOMS = 1000
  81. for i = 1, NUM_MUSHROOMS do
  82. local objectNode = scene_:CreateChild("Mushroom")
  83. local position = Vector3(Random(2000.0) - 1000.0, 0.0, Random(2000.0) - 1000.0)
  84. position.y = terrain:GetHeight(position) - 0.1
  85. objectNode.position = position
  86. -- Create a rotation quaternion from up vector to terrain normal
  87. objectNode.rotation = Quaternion(Vector3(0.0, 1.0, 0.0), terrain:GetNormal(position))
  88. objectNode:SetScale(3.0)
  89. local object = objectNode:CreateComponent("StaticModel")
  90. object.model = cache:GetResource("Model", "Models/Mushroom.mdl")
  91. object.material = cache:GetResource("Material", "Materials/Mushroom.xml")
  92. object.castShadows = true
  93. local body = objectNode:CreateComponent("RigidBody")
  94. body.collisionLayer = 2
  95. local shape = objectNode:CreateComponent("CollisionShape")
  96. shape:SetTriangleMesh(object.model, 0)
  97. end
  98. end
  99. function CreateVehicle()
  100. vehicleNode = scene_:CreateChild("Vehicle")
  101. vehicleNode.position = Vector3(0.0, 5.0, 0.0)
  102. -- Create the vehicle logic script object
  103. local vehicle = vehicleNode:CreateScriptObject("Vehicle")
  104. -- Create the rendering and physics components
  105. vehicle:Init()
  106. end
  107. function CreateInstructions()
  108. -- Construct new Text object, set string to display and font to use
  109. local instructionText = ui.root:CreateChild("Text")
  110. instructionText.text = "Use WASD keys to drive, mouse to rotate camera\n"..
  111. "F5 to save scene, F7 to load"
  112. instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
  113. -- The text has multiple rows. Center them in relation to each other
  114. instructionText.textAlignment = HA_CENTER
  115. -- Position the text relative to the screen center
  116. instructionText.horizontalAlignment = HA_CENTER
  117. instructionText.verticalAlignment = VA_CENTER
  118. instructionText:SetPosition(0, ui.root.height / 4)
  119. end
  120. function SubscribeToEvents()
  121. -- Subscribe to Update event for setting the vehicle controls before physics simulation
  122. SubscribeToEvent("Update", "HandleUpdate")
  123. -- Subscribe to PostUpdate event for updating the camera position after physics simulation
  124. SubscribeToEvent("PostUpdate", "HandlePostUpdate")
  125. end
  126. function HandleUpdate(eventType, eventData)
  127. if vehicleNode == nil then
  128. return
  129. end
  130. local vehicle = vehicleNode:GetScriptObject()
  131. if vehicle == nil then
  132. return
  133. end
  134. -- Get movement controls and assign them to the vehicle component. If UI has a focused element, clear controls
  135. if ui.focusElement == nil then
  136. vehicle.controls:Set(CTRL_FORWARD, input:GetKeyDown(KEY_W))
  137. vehicle.controls:Set(CTRL_BACK, input:GetKeyDown(KEY_S))
  138. vehicle.controls:Set(CTRL_LEFT, input:GetKeyDown(KEY_A))
  139. vehicle.controls:Set(CTRL_RIGHT, input:GetKeyDown(KEY_D))
  140. -- Add yaw & pitch from the mouse motion. Used only for the camera, does not affect motion
  141. vehicle.controls.yaw = vehicle.controls.yaw + input.mouseMoveX * YAW_SENSITIVITY
  142. vehicle.controls.pitch = vehicle.controls.pitch + input.mouseMoveY * YAW_SENSITIVITY
  143. -- Limit pitch
  144. vehicle.controls.pitch = Clamp(vehicle.controls.pitch, 0.0, 80.0)
  145. -- Check for loading / saving the scene
  146. if input:GetKeyPress(KEY_F5) then
  147. scene_:SaveXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
  148. end
  149. if input:GetKeyPress(KEY_F7) then
  150. scene_:LoadXML(fileSystem:GetProgramDir() .. "Data/Scenes/VehicleDemo.xml")
  151. -- After loading we have to reacquire the vehicle scene node, as it has been recreated
  152. -- Simply find by name as there's only one of them
  153. vehicleNode = scene_:GetChild("Vehicle", true)
  154. vehicleNode:GetScriptObject():PostInit()
  155. end
  156. else
  157. vehicle.controls:Set(CTRL_FORWARD + CTRL_BACK + CTRL_LEFT + CTRL_RIGHT, false)
  158. end
  159. end
  160. function HandlePostUpdate(eventType, eventData)
  161. if vehicleNode == nil then
  162. return
  163. end
  164. local vehicle = vehicleNode:GetScriptObject()
  165. if vehicle == nil then
  166. return
  167. end
  168. -- Physics update has completed. Position camera behind vehicle
  169. local dir = Quaternion(vehicleNode.rotation:YawAngle(), Vector3(0.0, 1.0, 0.0))
  170. dir = dir * Quaternion(vehicle.controls.yaw, Vector3(0.0, 1.0, 0.0))
  171. dir = dir * Quaternion(vehicle.controls.pitch, Vector3(1.0, 0.0, 0.0))
  172. local cameraTargetPos = vehicleNode.position - dir * Vector3(0.0, 0.0, CAMERA_DISTANCE)
  173. local cameraStartPos = vehicleNode.position
  174. -- Raycast camera against static objects (physics collision mask 2)
  175. -- and move it closer to the vehicle if something in between
  176. local cameraRay = Ray(cameraStartPos, (cameraTargetPos - cameraStartPos):Normalized())
  177. local cameraRayLength = (cameraTargetPos - cameraStartPos):Length();
  178. local physicsWorld = scene_:GetComponent("PhysicsWorld")
  179. local result = physicsWorld:RaycastSingle(cameraRay, cameraRayLength, 2)
  180. if result.body ~= nil then
  181. cameraTargetPos = cameraStartPos + cameraRay.direction * (result.distance - 0.5)
  182. end
  183. cameraNode.position = cameraTargetPos
  184. cameraNode.rotation = dir
  185. end
  186. -- Vehicle script object class
  187. --
  188. -- When saving, the node and component handles are automatically converted into nodeID or componentID attributes
  189. -- and are acquired from the scene when loading. The steering member variable will likewise be saved automatically.
  190. -- The Controls object can not be automatically saved, so handle it manually in the Load() and Save() methods
  191. Vehicle = ScriptObject()
  192. function Vehicle:Start()
  193. -- Current left/right steering amount (-1 to 1.)
  194. self.steering = 0.0
  195. -- Vehicle controls.
  196. self.controls = Controls()
  197. end
  198. function Vehicle:Load(deserializer)
  199. self.controls.yaw = deserializer:ReadFloat()
  200. self.controls.pitch = deserializer:ReadFloat()
  201. end
  202. function Vehicle:Save(serializer)
  203. serializer:WriteFloat(self.controls.yaw)
  204. serializer:WriteFloat(self.controls.pitch)
  205. end
  206. function Vehicle:Init()
  207. -- This function is called only from the main program when initially creating the vehicle, not on scene load
  208. local node = self.node
  209. local hullObject = node:CreateComponent("StaticModel")
  210. self.hullBody = node:CreateComponent("RigidBody")
  211. local hullShape = node:CreateComponent("CollisionShape")
  212. node.scale = Vector3(1.5, 1.0, 3.0)
  213. hullObject.model = cache:GetResource("Model", "Models/Box.mdl")
  214. hullObject.material = cache:GetResource("Material", "Materials/Stone.xml")
  215. hullObject.castShadows = true
  216. hullShape:SetBox(Vector3(1.0, 1.0, 1.0))
  217. self.hullBody.mass = 4.0
  218. self.hullBody.linearDamping = 0.2 -- Some air resistance
  219. self.hullBody.angularDamping = 0.5
  220. self.hullBody.collisionLayer = 1
  221. self.frontLeft = self:InitWheel("FrontLeft", Vector3(-0.6, -0.4, 0.3))
  222. self.frontRight = self:InitWheel("FrontRight", Vector3(0.6, -0.4, 0.3))
  223. self.rearLeft = self:InitWheel("RearLeft", Vector3(-0.6, -0.4, -0.3))
  224. self.rearRight = self:InitWheel("RearRight", Vector3(0.6, -0.4, -0.3))
  225. self:PostInit()
  226. end
  227. function Vehicle:PostInit()
  228. self.frontLeft = scene_:GetChild("FrontLeft")
  229. self.frontRight = scene_:GetChild("FrontRight")
  230. self.rearLeft = scene_:GetChild("RearLeft")
  231. self.rearRight = scene_:GetChild("RearRight")
  232. self.frontLeftAxis = self.frontLeft:GetComponent("Constraint")
  233. self.frontRightAxis = self.frontRight:GetComponent("Constraint")
  234. self.hullBody = self.node:GetComponent("RigidBody")
  235. self.frontLeftBody = self.frontLeft:GetComponent("RigidBody")
  236. self.frontRightBody = self.frontRight:GetComponent("RigidBody")
  237. self.rearLeftBody = self.rearLeft:GetComponent("RigidBody")
  238. self.rearRightBody = self.rearRight:GetComponent("RigidBody")
  239. end
  240. function Vehicle:InitWheel(name, offset)
  241. -- Note: do not parent the wheel to the hull scene node. Instead create it on the root level and let the physics
  242. -- constraint keep it together
  243. local wheelNode = scene_:CreateChild(name)
  244. local node = self.node
  245. wheelNode.position = node:LocalToWorld(offset)
  246. if offset.x >= 0.0 then
  247. wheelNode.rotation = node.worldRotation * Quaternion(0.0, 0.0, -90.0)
  248. else
  249. wheelNode.rotation = node.worldRotation * Quaternion(0.0, 0.0, 90.0)
  250. end
  251. wheelNode.scale = Vector3(0.8, 0.5, 0.8)
  252. local wheelObject = wheelNode:CreateComponent("StaticModel")
  253. local wheelBody = wheelNode:CreateComponent("RigidBody")
  254. local wheelShape = wheelNode:CreateComponent("CollisionShape")
  255. local wheelConstraint = wheelNode:CreateComponent("Constraint")
  256. wheelObject.model = cache:GetResource("Model", "Models/Cylinder.mdl")
  257. wheelObject.material = cache:GetResource("Material", "Materials/Stone.xml")
  258. wheelObject.castShadows = true
  259. wheelShape:SetSphere(1.0)
  260. wheelBody.friction = 1
  261. wheelBody.mass = 1
  262. wheelBody.linearDamping = 0.2 -- Some air resistance
  263. wheelBody.angularDamping = 0.75 -- Could also use rolling friction
  264. wheelBody.collisionLayer = 1
  265. wheelConstraint.constraintType = CONSTRAINT_HINGE
  266. wheelConstraint.otherBody = node:GetComponent("RigidBody")
  267. wheelConstraint.worldPosition = wheelNode.worldPosition -- Set constraint's both ends at wheel's location
  268. wheelConstraint.axis = Vector3(0.0, 1.0, 0.0) -- Wheel rotates around its local Y-axis
  269. if offset.x >= 0.0 then -- Wheel's hull axis points either left or right
  270. wheelConstraint.otherAxis = Vector3(1.0, 0.0, 0.0)
  271. else
  272. wheelConstraint.otherAxis = Vector3(-1.0, 0.0, 0.0)
  273. end
  274. wheelConstraint.lowLimit = Vector2(-180.0, 0.0) -- Let the wheel rotate freely around the axis
  275. wheelConstraint.highLimit = Vector2(180.0, 0.0)
  276. wheelConstraint.disableCollision = true -- Let the wheel intersect the vehicle hull
  277. return wheelNode
  278. end
  279. function Vehicle:FixedUpdate(timeStep)
  280. local newSteering = 0.0
  281. local accelerator = 0.0
  282. if self.controls:IsDown(CTRL_LEFT) then
  283. newSteering = -1.0
  284. end
  285. if self.controls:IsDown(CTRL_RIGHT) then
  286. newSteering = 1.0
  287. end
  288. if self.controls:IsDown(CTRL_FORWARD) then
  289. accelerator = 1.0
  290. end
  291. if self.controls:IsDown(CTRL_BACK) then
  292. accelerator = -0.5
  293. end
  294. -- When steering, wake up the wheel rigidbodies so that their orientation is updated
  295. if newSteering ~= 0.0 then
  296. self.frontLeftBody:Activate()
  297. self.frontRightBody:Activate()
  298. self.steering = self.steering * 0.95 + newSteering * 0.05
  299. else
  300. self.steering = self.steering * 0.8 + newSteering * 0.2
  301. end
  302. local steeringRot = Quaternion(0.0, self.steering * MAX_WHEEL_ANGLE, 0.0)
  303. self.frontLeftAxis.otherAxis = steeringRot * Vector3(-1.0, 0.0, 0.0)
  304. self.frontRightAxis.otherAxis = steeringRot * Vector3(1.0, 0.0, 0.0)
  305. if accelerator ~= 0.0 then
  306. -- Torques are applied in world space, so need to take the vehicle & wheel rotation into account
  307. local torqueVec = Vector3(ENGINE_POWER * accelerator, 0.0, 0.0)
  308. local node = self.node
  309. self.frontLeftBody:ApplyTorque(node.rotation * steeringRot * torqueVec)
  310. self.frontRightBody:ApplyTorque(node.rotation * steeringRot * torqueVec)
  311. self.rearLeftBody:ApplyTorque(node.rotation * torqueVec)
  312. self.rearRightBody:ApplyTorque(node.rotation * torqueVec)
  313. end
  314. -- Apply downforce proportional to velocity
  315. local localVelocity = self.hullBody.rotation:Inverse() * self.hullBody.linearVelocity
  316. self.hullBody:ApplyForce(self.hullBody.rotation * Vector3(0.0, -1.0, 0.0) * Abs(localVelocity.z) * DOWN_FORCE)
  317. end