LUADOC - Farming Simulator 22

Script v1_7_1_0

Engine v1_7_1_0

Foundation Reference

Suspensions

Description
Specialization for non-wheel suspensions e.g. cabin, seat or player character suspension
Functions

getIsSuspensionNodeActive

Description
Definition
getIsSuspensionNodeActive()
Code
294function Suspensions:getIsSuspensionNodeActive(suspensionNode)
295 return suspensionNode.node ~= nil and suspensionNode.component ~= nil
296end

getSuspensionModfier

Description
Definition
getSuspensionModfier()
Code
350function Suspensions:getSuspensionModfier()
351 local spec = self.spec_suspensions
352 local index = 1
353 -- try to get the seat suspension, normally on index 2 (if no cabin suspension on index 1)
354 if #spec.suspensionNodes >= 2 and not spec.suspensionNodes[2].useCharacterTorso and not spec.suspensionNodes[2].isRotational then
355 index = 2
356 end
357
358 local suspensionNode = spec.suspensionNodes[index]
359 if suspensionNode ~= nil then
360 if not suspensionNode.isRotational then
361 return suspensionNode.curTranslation[2]
362 end
363 end
364
365 return 0
366end

getSuspensionNodeFromIndex

Description
Definition
getSuspensionNodeFromIndex()
Code
285function Suspensions:getSuspensionNodeFromIndex(suspensionIndex)
286 local spec = self.spec_suspensions
287 if spec.suspensionAvailable then
288 return self.spec_suspensions.suspensionNodes[suspensionIndex]
289 end
290end

initSpecialization

Description
Definition
initSpecialization()
Code
23function Suspensions.initSpecialization()
24 local schema = Vehicle.xmlSchema
25 schema:setXMLSpecializationType("Suspensions")
26
27 schema:register(XMLValueType.NODE_INDEX, "vehicle.suspensions.suspension(?)#node", "Suspension node")
28 schema:register(XMLValueType.BOOL, "vehicle.suspensions.suspension(?)#useCharacterTorso", "Use character torso instead of node")
29 schema:register(XMLValueType.FLOAT, "vehicle.suspensions.suspension(?)#weight", "Weight in kg", 500)
30 schema:register(XMLValueType.VECTOR_ROT, "vehicle.suspensions.suspension(?)#minRotation", "Min. rotation")
31 schema:register(XMLValueType.VECTOR_ROT, "vehicle.suspensions.suspension(?)#maxRotation", "Max. rotation")
32 schema:register(XMLValueType.VECTOR_TRANS, "vehicle.suspensions.suspension(?)#startTranslationOffset", "Custom translation offset")
33 schema:register(XMLValueType.VECTOR_TRANS, "vehicle.suspensions.suspension(?)#minTranslation", "Min. translation")
34 schema:register(XMLValueType.VECTOR_TRANS, "vehicle.suspensions.suspension(?)#maxTranslation", "Max. translation")
35 schema:register(XMLValueType.FLOAT, "vehicle.suspensions.suspension(?)#maxVelocityDifference", "Max. velocity difference", 0.1)
36 schema:register(XMLValueType.VECTOR_2, "vehicle.suspensions.suspension(?)#suspensionParametersX", "Suspension parameters X", "0 0")
37 schema:register(XMLValueType.VECTOR_2, "vehicle.suspensions.suspension(?)#suspensionParametersY", "Suspension parameters Y", "0 0")
38 schema:register(XMLValueType.VECTOR_2, "vehicle.suspensions.suspension(?)#suspensionParametersZ", "Suspension parameters Z", "0 0")
39 schema:register(XMLValueType.BOOL, "vehicle.suspensions.suspension(?)#inverseMovement", "Invert movement", false)
40 schema:register(XMLValueType.BOOL, "vehicle.suspensions.suspension(?)#serverOnly", "Suspension is only calculated on server side", false)
41
42 schema:register(XMLValueType.FLOAT, "vehicle.suspensions#maxUpdateDistance", "Max. distance to vehicle root to update suspension nodes", Suspensions.DEFAULT_MAX_UPDATE_DISTANCE)
43
44 schema:setXMLSpecializationType()
45end

onEnterVehicle

Description
Definition
onEnterVehicle()
Code
316function Suspensions:onEnterVehicle(isControlling)
317 if self.getVehicleCharacter ~= nil then
318 local vehicleCharacter = self:getVehicleCharacter()
319 if vehicleCharacter ~= nil then
320 local spec = self.spec_suspensions
321 for _, suspensionNode in ipairs(spec.suspensionNodes) do
322 self:setSuspensionNodeCharacter(suspensionNode, vehicleCharacter)
323 end
324 end
325 end
326end

onLeaveVehicle

Description
Definition
onLeaveVehicle()
Code
330function Suspensions:onLeaveVehicle()
331 local spec = self.spec_suspensions
332 for _,suspension in ipairs(spec.suspensionNodes) do
333 if suspension.useCharacterTorso then
334 suspension.node = nil
335 end
336 end
337end

onLoad

Description
Definition
onLoad()
Code
67function Suspensions:onLoad(savegame)
68 if self.isClient then
69 local spec = self.spec_suspensions
70
71 spec.suspensionNodes = {}
72
73 local i = 0
74 while true do
75 local key = string.format("vehicle.suspensions.suspension(%d)", i)
76 if not self.xmlFile:hasProperty(key) then
77 break
78 end
79
80 local entry = {}
81 entry.node = self.xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings)
82 entry.refNodeOffset = {0, 0, 0}
83 if entry.node ~= nil then
84 local component = self:getParentComponent(entry.node)
85 if component ~= nil then
86 entry.component = component
87 entry.refNodeOffset = {localToLocal(entry.node, component, 0, 0, 0)}
88 end
89 end
90
91 entry.useCharacterTorso = self.xmlFile:getValue(key .. "#useCharacterTorso", false)
92 if (entry.node ~= nil and entry.component ~= nil) or entry.useCharacterTorso then
93 entry.weight = self.xmlFile:getValue(key .. "#weight", 500)
94
95 entry.minRotation = self.xmlFile:getValue(key .. "#minRotation", nil, true)
96 entry.maxRotation = self.xmlFile:getValue(key .. "#maxRotation", nil, true)
97 entry.isRotational = entry.minRotation ~= nil and entry.maxRotation ~= nil
98
99 if not entry.isRotational and not entry.useCharacterTorso then
100 entry.baseTranslation = {getTranslation(entry.node)}
101 entry.startTranslationOffset = self.xmlFile:getValue(key.."#startTranslationOffset", "0 0 0", true)
102 for j=1, 3 do
103 entry.baseTranslation[j] = entry.baseTranslation[j] + entry.startTranslationOffset[j]
104 end
105
106 entry.minTranslation = self.xmlFile:getValue(key.."#minTranslation", nil, true)
107 entry.maxTranslation = self.xmlFile:getValue(key.."#maxTranslation", nil, true)
108 end
109
110 entry.maxVelocityDifference = self.xmlFile:getValue(key .. "#maxVelocityDifference", 0.1)
111
112 local suspensionParametersX = self.xmlFile:getValue(key .. "#suspensionParametersX", "0 0", true)
113 local suspensionParametersY = self.xmlFile:getValue(key .. "#suspensionParametersY", "0 0", true)
114 local suspensionParametersZ = self.xmlFile:getValue(key .. "#suspensionParametersZ", "0 0", true)
115 entry.suspensionParameters = {}
116 entry.suspensionParameters[1] = {}
117 entry.suspensionParameters[2] = {}
118 entry.suspensionParameters[3] = {}
119 for j=1,2 do
120 entry.suspensionParameters[1][j] = suspensionParametersX[j] * 1000
121 entry.suspensionParameters[2][j] = suspensionParametersY[j] * 1000
122 entry.suspensionParameters[3][j] = suspensionParametersZ[j] * 1000
123 end
124
125 entry.inverseMovement = self.xmlFile:getValue(key .. "#inverseMovement", false)
126 entry.serverOnly = self.xmlFile:getValue(key .. "#serverOnly", false)
127
128 entry.lastRefNodePosition = nil
129 entry.lastRefNodeVelocity = nil
130
131 entry.curRotation = {0, 0, 0}
132 entry.curRotationSpeed = {0, 0, 0}
133
134 entry.curTranslation = {0, 0, 0}
135 entry.curTranslationSpeed = {0, 0, 0}
136
137 entry.curAcc = {0, 0, 0}
138 end
139
140 if not entry.serverOnly or self.isServer then
141 table.insert(spec.suspensionNodes, entry)
142 end
143
144 i = i + 1
145 end
146
147 spec.maxUpdateDistance = self.xmlFile:getValue("vehicle.suspensions#maxUpdateDistance", Suspensions.DEFAULT_MAX_UPDATE_DISTANCE)
148
149 if #spec.suspensionNodes > 0 then
150 spec.suspensionAvailable = true
151 end
152 end
153
154 if not self.spec_suspensions.suspensionAvailable then
155 SpecializationUtil.removeEventListener(self, "onUpdate", Suspensions)
156 SpecializationUtil.removeEventListener(self, "onEnterVehicle", Suspensions)
157 SpecializationUtil.removeEventListener(self, "onLeaveVehicle", Suspensions)
158 SpecializationUtil.removeEventListener(self, "onVehicleCharacterChanged", Suspensions)
159 end
160end

onUpdate

Description
Definition
onUpdate()
Code
164function Suspensions:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected)
165 local spec = self.spec_suspensions
166 if self.currentUpdateDistance < spec.maxUpdateDistance then
167 local timeDelta = 0.001 * g_physicsDt
168
169 for _,suspension in ipairs(spec.suspensionNodes) do
170 if suspension.node ~= nil and entityExists(suspension.node) then
171 -- calc velocity diff
172 suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = 0, 0, 0
173
174 if self:getIsSuspensionNodeActive(suspension) then
175 local wx, wy, wz = localToWorld(suspension.component, unpack(suspension.refNodeOffset))
176
177 if suspension.lastRefNodePosition == nil then
178 suspension.lastRefNodePosition = {wx, wy, wz}
179 suspension.lastRefNodeVelocity = {0, 0, 0}
180 end
181
182 local direction = (suspension.inverseMovement and -1) or 1
183
184 local newVelX, newVelY, newVelZ = (wx - suspension.lastRefNodePosition[1]) / timeDelta * direction,
185 (wy - suspension.lastRefNodePosition[2]) / timeDelta * direction,
186 (wz - suspension.lastRefNodePosition[3]) / timeDelta * direction
187
188 local oldVelX, oldVelY, oldVelZ = unpack(suspension.lastRefNodeVelocity)
189
190 local velDiffX, velDiffY, velDiffZ = worldDirectionToLocal(getParent(suspension.node), newVelX-oldVelX, newVelY-oldVelY, newVelZ-oldVelZ)
191
192 velDiffX = MathUtil.clamp(velDiffX, -suspension.maxVelocityDifference, suspension.maxVelocityDifference)
193 velDiffY = MathUtil.clamp(velDiffY, -suspension.maxVelocityDifference, suspension.maxVelocityDifference)
194 velDiffZ = MathUtil.clamp(velDiffZ, -suspension.maxVelocityDifference, suspension.maxVelocityDifference)
195
196 if suspension.isRotational then
197 if suspension.useCharacterTorso then
198 suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = MathUtil.crossProduct(velDiffX/timeDelta, velDiffY/timeDelta, velDiffZ/timeDelta, 1,0,0)
199 else
200 suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = MathUtil.crossProduct(velDiffX/timeDelta, velDiffY/timeDelta, velDiffZ/timeDelta, 0,1,0)
201 end
202 else
203 suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = -velDiffX/timeDelta, -velDiffY/timeDelta, -velDiffZ/timeDelta
204 end
205
206 -- prepare for next tick
207 suspension.lastRefNodePosition[1] = wx
208 suspension.lastRefNodePosition[2] = wy
209 suspension.lastRefNodePosition[3] = wz
210
211 suspension.lastRefNodeVelocity[1] = newVelX
212 suspension.lastRefNodeVelocity[2] = newVelY
213 suspension.lastRefNodeVelocity[3] = newVelZ
214 end
215
216 -- update spring/damper system, F = F_ext - k*x - c*(dx/dt)
217 -- using implicit euler for spring and damper, explicit euler for external force
218 for i=1, 3 do
219 local suspensionParameter = suspension.suspensionParameters[i]
220 if suspensionParameter[1] > 0 and suspensionParameter[2] > 0 then
221 local f = suspension.weight * suspension.curAcc[i]
222
223 local k = suspensionParameter[1]
224 local c = suspensionParameter[2]
225
226 if suspension.isRotational then
227 local x = suspension.curRotation[i]
228 local vx = suspension.curRotationSpeed[i]
229
230 local force = f - (k*x) - (c*vx)
231
232 -- 'Implicit Methods for Differential Equations' (Baraff), formula (4-6)
233 local m = suspension.weight
234 local h = timeDelta
235 local numerator = h * (force + h * (-k) * vx) / m
236 local denumerator = 1 - ((-c) + h * (-k)) * h / m
237 local curRotationSpeed = vx + numerator / denumerator
238
239 local newRotation = x + (curRotationSpeed * timeDelta)
240 newRotation = MathUtil.clamp(newRotation, suspension.minRotation[i], suspension.maxRotation[i])
241
242 suspension.curRotationSpeed[i] = (newRotation - x) / timeDelta
243 suspension.curRotation[i] = newRotation
244 else
245 local x = suspension.curTranslation[i]
246 local vx = suspension.curTranslationSpeed[i]
247
248 local force = f - (k*x) - (c*vx)
249
250 -- 'Implicit Methods for Differential Equations' (Baraff), formula (4-6)
251 local m = suspension.weight
252 local h = timeDelta
253 local numerator = h * (force + h * (-k) * vx) / m
254 local denumerator = 1 - ((-c) + h * (-k)) * h / m
255 local curTranslationSpeed = vx + numerator / denumerator
256
257 local newTranslation = x + (curTranslationSpeed * timeDelta)
258 newTranslation = MathUtil.clamp(newTranslation, suspension.minTranslation[i], suspension.maxTranslation[i])
259
260 suspension.curTranslationSpeed[i] = (newTranslation - x) / timeDelta
261 suspension.curTranslation[i] = newTranslation
262 end
263 end
264 end
265
266 if suspension.isRotational then
267 setRotation(suspension.node, suspension.curRotation[1], suspension.curRotation[2], suspension.curRotation[3])
268 else
269 setTranslation(suspension.node, suspension.baseTranslation[1] + suspension.curTranslation[1], suspension.baseTranslation[2] + suspension.curTranslation[2], suspension.baseTranslation[3] + suspension.curTranslation[3])
270 end
271
272 if self.setMovingToolDirty ~= nil then
273 self:setMovingToolDirty(suspension.node)
274 end
275 elseif suspension.node ~= nil then
276 Logging.xmlError(self.xmlFile, "Failed to update suspension node %d. Node does not exist anymore!", suspension.node)
277 suspension.node = nil
278 end
279 end
280 end
281end

onVehicleCharacterChanged

Description
Definition
onVehicleCharacterChanged()
Code
341function Suspensions:onVehicleCharacterChanged(character)
342 local spec = self.spec_suspensions
343 for _, suspensionNode in ipairs(spec.suspensionNodes) do
344 self:setSuspensionNodeCharacter(suspensionNode, character)
345 end
346end

prerequisitesPresent

Description
Definition
prerequisitesPresent()
Code
17function Suspensions.prerequisitesPresent(specializations)
18 return true
19end

registerEventListeners

Description
Definition
registerEventListeners()
Code
57function Suspensions.registerEventListeners(vehicleType)
58 SpecializationUtil.registerEventListener(vehicleType, "onLoad", Suspensions)
59 SpecializationUtil.registerEventListener(vehicleType, "onUpdate", Suspensions)
60 SpecializationUtil.registerEventListener(vehicleType, "onEnterVehicle", Suspensions)
61 SpecializationUtil.registerEventListener(vehicleType, "onLeaveVehicle", Suspensions)
62 SpecializationUtil.registerEventListener(vehicleType, "onVehicleCharacterChanged", Suspensions)
63end

registerFunctions

Description
Definition
registerFunctions()
Code
49function Suspensions.registerFunctions(vehicleType)
50 SpecializationUtil.registerFunction(vehicleType, "getSuspensionNodeFromIndex", Suspensions.getSuspensionNodeFromIndex)
51 SpecializationUtil.registerFunction(vehicleType, "getIsSuspensionNodeActive", Suspensions.getIsSuspensionNodeActive)
52 SpecializationUtil.registerFunction(vehicleType, "setSuspensionNodeCharacter", Suspensions.setSuspensionNodeCharacter)
53end

setSuspensionNodeCharacter

Description
Definition
setSuspensionNodeCharacter()
Code
300function Suspensions:setSuspensionNodeCharacter(suspensionNode, character)
301 if suspensionNode.useCharacterTorso then
302 suspensionNode.node = character.thirdPersonSuspensionNode
303
304 if suspensionNode.node ~= nil then
305 local component = self:getParentComponent(suspensionNode.node)
306 if component ~= nil then
307 suspensionNode.refNodeOffset = {localToLocal(character.characterNode, component, 0, 0, 0)}
308 suspensionNode.component = component
309 end
310 end
311 end
312end