39 | function Suspensions:onLoad(savegame) |
40 | if self.isClient then |
41 | local spec = self.spec_suspensions |
42 | |
43 | spec.suspensionNodes = {} |
44 | |
45 | local i = 0 |
46 | while true do |
47 | local key = string.format("vehicle.suspensions.suspension(%d)", i) |
48 | if not hasXMLProperty(self.xmlFile, key) then |
49 | break |
50 | end |
51 | |
52 | local entry = {} |
53 | entry.node = I3DUtil.indexToObject( self.components, getXMLString(self.xmlFile, key .. "#node"), self.i3dMappings) |
54 | entry.refNodeOffset = {0, 0, 0} |
55 | if entry.node ~= nil then |
56 | local component = self:getParentComponent(entry.node) |
57 | if component ~= nil then |
58 | entry.component = component |
59 | entry.refNodeOffset = {localToLocal(entry.node, component, 0, 0, 0)} |
60 | end |
61 | end |
62 | |
63 | entry.useCharacterTorso = Utils.getNoNil(getXMLBool(self.xmlFile, key .. "#useCharacterTorso"), false) |
64 | if (entry.node ~= nil and entry.component ~= nil) or entry.useCharacterTorso then |
65 | entry.weight = Utils.getNoNil(getXMLFloat(self.xmlFile, key .. "#weight"), 500) |
66 | |
67 | entry.minRotation = StringUtil.getRadiansFromString(getXMLString(self.xmlFile, key .. "#minRotation"), 3) |
68 | entry.maxRotation = StringUtil.getRadiansFromString(getXMLString(self.xmlFile, key .. "#maxRotation"), 3) |
69 | entry.isRotational = entry.minRotation ~= nil and entry.maxRotation ~= nil |
70 | |
71 | if not entry.isRotational and not entry.useCharacterTorso then |
72 | entry.baseTranslation = {getTranslation(entry.node)} |
73 | entry.minTranslation = StringUtil.getVectorNFromString(getXMLString(self.xmlFile, key.."#minTranslation"), 3) |
74 | entry.maxTranslation = StringUtil.getVectorNFromString(getXMLString(self.xmlFile, key.."#maxTranslation"), 3) |
75 | end |
76 | |
77 | entry.maxVelocityDifference = Utils.getNoNil(getXMLFloat(self.xmlFile, key .. "#maxVelocityDifference"), 0.1) |
78 | |
79 | local suspensionParametersX = StringUtil.getVectorNFromString(Utils.getNoNil(getXMLString(self.xmlFile, key .. "#suspensionParametersX"), "0 0"), 2) |
80 | local suspensionParametersY = StringUtil.getVectorNFromString(Utils.getNoNil(getXMLString(self.xmlFile, key .. "#suspensionParametersY"), "0 0"), 2) |
81 | local suspensionParametersZ = StringUtil.getVectorNFromString(Utils.getNoNil(getXMLString(self.xmlFile, key .. "#suspensionParametersZ"), "0 0"), 2) |
82 | entry.suspensionParameters = {} |
83 | entry.suspensionParameters[1] = {} |
84 | entry.suspensionParameters[2] = {} |
85 | entry.suspensionParameters[3] = {} |
86 | for j=1,2 do |
87 | entry.suspensionParameters[1][j] = suspensionParametersX[j] * 1000 |
88 | entry.suspensionParameters[2][j] = suspensionParametersY[j] * 1000 |
89 | entry.suspensionParameters[3][j] = suspensionParametersZ[j] * 1000 |
90 | end |
91 | |
92 | entry.inverseMovement = Utils.getNoNil(getXMLBool(self.xmlFile, key .. "#inverseMovement"), false) |
93 | |
94 | entry.lastRefNodePosition = nil |
95 | entry.lastRefNodeVelocity = nil |
96 | |
97 | entry.curRotation = {0, 0, 0} |
98 | entry.curRotationSpeed = {0, 0, 0} |
99 | |
100 | entry.curTranslation = {0, 0, 0} |
101 | entry.curTranslationSpeed = {0, 0, 0} |
102 | |
103 | entry.curAcc = {0, 0, 0} |
104 | end |
105 | |
106 | table.insert(spec.suspensionNodes, entry) |
107 | |
108 | i = i + 1 |
109 | end |
110 | |
111 | if #spec.suspensionNodes > 0 then |
112 | spec.suspensionAvailable = true |
113 | end |
114 | end |
115 | end |
119 | function Suspensions:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
120 | if self.isClient then |
121 | local spec = self.spec_suspensions |
122 | if spec.suspensionAvailable then |
123 | local timeDelta = 0.001 * g_physicsDt |
124 | |
125 | for _,suspension in ipairs(spec.suspensionNodes) do |
126 | if suspension.node ~= nil and entityExists(suspension.node) then |
127 | -- calc velocity diff |
128 | suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = 0, 0, 0 |
129 | |
130 | if self:getIsSuspensionNodeActive(suspension) then |
131 | local wx, wy, wz = localToWorld(suspension.component, unpack(suspension.refNodeOffset)) |
132 | |
133 | if suspension.lastRefNodePosition == nil then |
134 | suspension.lastRefNodePosition = {wx, wy, wz} |
135 | suspension.lastRefNodeVelocity = {0, 0, 0} |
136 | end |
137 | |
138 | local direction = (suspension.inverseMovement and -1) or 1 |
139 | |
140 | local newVelX, newVelY, newVelZ = (wx - suspension.lastRefNodePosition[1]) / timeDelta * direction, |
141 | (wy - suspension.lastRefNodePosition[2]) / timeDelta * direction, |
142 | (wz - suspension.lastRefNodePosition[3]) / timeDelta * direction |
143 | |
144 | local oldVelX, oldVelY, oldVelZ = unpack(suspension.lastRefNodeVelocity) |
145 | |
146 | local velDiffX, velDiffY, velDiffZ = worldDirectionToLocal(getParent(suspension.node), newVelX-oldVelX, newVelY-oldVelY, newVelZ-oldVelZ) |
147 | |
148 | velDiffX = MathUtil.clamp(velDiffX, -suspension.maxVelocityDifference, suspension.maxVelocityDifference) |
149 | velDiffY = MathUtil.clamp(velDiffY, -suspension.maxVelocityDifference, suspension.maxVelocityDifference) |
150 | velDiffZ = MathUtil.clamp(velDiffZ, -suspension.maxVelocityDifference, suspension.maxVelocityDifference) |
151 | |
152 | if suspension.isRotational then |
153 | if suspension.useCharacterTorso then |
154 | suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = MathUtil.crossProduct(velDiffX/timeDelta, velDiffY/timeDelta, velDiffZ/timeDelta, 1,0,0) |
155 | else |
156 | suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = MathUtil.crossProduct(velDiffX/timeDelta, velDiffY/timeDelta, velDiffZ/timeDelta, 0,1,0) |
157 | end |
158 | else |
159 | suspension.curAcc[1], suspension.curAcc[2], suspension.curAcc[3] = -velDiffX/timeDelta, -velDiffY/timeDelta, -velDiffZ/timeDelta |
160 | end |
161 | |
162 | -- prepare for next tick |
163 | suspension.lastRefNodePosition[1] = wx |
164 | suspension.lastRefNodePosition[2] = wy |
165 | suspension.lastRefNodePosition[3] = wz |
166 | |
167 | suspension.lastRefNodeVelocity[1] = newVelX |
168 | suspension.lastRefNodeVelocity[2] = newVelY |
169 | suspension.lastRefNodeVelocity[3] = newVelZ |
170 | end |
171 | |
172 | -- update spring/damper system, F = F_ext - k*x - c*(dx/dt) |
173 | -- using implicit euler for spring and damper, explicit euler for external force |
174 | for i=1, 3 do |
175 | local suspensionParameter = suspension.suspensionParameters[i] |
176 | if suspensionParameter[1] > 0 and suspensionParameter[2] > 0 then |
177 | local f = suspension.weight * suspension.curAcc[i] |
178 | |
179 | local k = suspensionParameter[1] |
180 | local c = suspensionParameter[2] |
181 | |
182 | if suspension.isRotational then |
183 | local x = suspension.curRotation[i] |
184 | local vx = suspension.curRotationSpeed[i] |
185 | |
186 | local force = f - (k*x) - (c*vx) |
187 | |
188 | -- 'Implicit Methods for Differential Equations' (Baraff), formula (4-6) |
189 | local m = suspension.weight |
190 | local h = timeDelta |
191 | local numerator = h * (force + h * (-k) * vx) / m |
192 | local denumerator = 1 - ((-c) + h * (-k)) * h / m |
193 | local curRotationSpeed = vx + numerator / denumerator |
194 | |
195 | local newRotation = x + (curRotationSpeed * timeDelta) |
196 | newRotation = MathUtil.clamp(newRotation, suspension.minRotation[i], suspension.maxRotation[i]) |
197 | |
198 | suspension.curRotationSpeed[i] = (newRotation - x) / timeDelta |
199 | suspension.curRotation[i] = newRotation |
200 | else |
201 | local x = suspension.curTranslation[i] |
202 | local vx = suspension.curTranslationSpeed[i] |
203 | |
204 | local force = f - (k*x) - (c*vx) |
205 | |
206 | -- 'Implicit Methods for Differential Equations' (Baraff), formula (4-6) |
207 | local m = suspension.weight |
208 | local h = timeDelta |
209 | local numerator = h * (force + h * (-k) * vx) / m |
210 | local denumerator = 1 - ((-c) + h * (-k)) * h / m |
211 | local curTranslationSpeed = vx + numerator / denumerator |
212 | |
213 | local newTranslation = x + (curTranslationSpeed * timeDelta) |
214 | newTranslation = MathUtil.clamp(newTranslation, suspension.minTranslation[i], suspension.maxTranslation[i]) |
215 | |
216 | suspension.curTranslationSpeed[i] = (newTranslation - x) / timeDelta |
217 | suspension.curTranslation[i] = newTranslation |
218 | end |
219 | end |
220 | end |
221 | |
222 | if suspension.isRotational then |
223 | setRotation(suspension.node, suspension.curRotation[1], suspension.curRotation[2], suspension.curRotation[3]) |
224 | else |
225 | setTranslation(suspension.node, suspension.baseTranslation[1] + suspension.curTranslation[1], suspension.baseTranslation[2] + suspension.curTranslation[2], suspension.baseTranslation[3] + suspension.curTranslation[3]) |
226 | end |
227 | |
228 | if self.setMovingToolDirty ~= nil then |
229 | self:setMovingToolDirty(suspension.node) |
230 | end |
231 | elseif suspension.node ~= nil then |
232 | g_logManager:xmlError(self.configFileName, "Failed to update suspension node %d. Node does not exist anymore!", suspension.node) |
233 | suspension.node = nil |
234 | end |
235 | end |
236 | end |
237 | end |
238 | end |