Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
1 | -- |
2 | -- Wheels util |
3 | -- Util class to manage wheels of a vehicle |
4 | -- |
5 | -- @author Stefan Geiger |
6 | -- @date 11/03/08 |
7 | -- |
8 | -- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved. |
9 | |
10 | WheelsUtil = {}; |
11 | |
12 | -- Note: these are in order of decreasing hardness so that default friction fallback is easier to implement |
13 | WheelsUtil.GROUND_ROAD = 1; |
14 | WheelsUtil.GROUND_HARD_TERRAIN = 2; |
15 | WheelsUtil.GROUND_SOFT_TERRAIN = 3; |
16 | WheelsUtil.GROUND_FIELD = 4; |
17 | WheelsUtil.NUM_GROUNDS = 4; |
18 | |
19 | WheelsUtil.tireTypes = {}; |
20 | |
21 | function WheelsUtil.registerTireType(name, frictionCoeffs, frictionCoeffsWet) |
22 | local tireType = WheelsUtil.getTireType(name); |
23 | if tireType ~= nil then |
24 | print("Warning: Adding duplicate tire type '"..name.."'"); |
25 | return; |
26 | end |
27 | |
28 | local function getNoNilCoeffs(frictionCoeffs) |
29 | local localCoeffs = {}; |
30 | if frictionCoeffs[1] == nil then |
31 | localCoeffs[1] = 1.15; |
32 | for i=2,WheelsUtil.NUM_GROUNDS do |
33 | if frictionCoeffs[i] ~= nil then |
34 | localCoeffs[1] = frictionCoeffs[i]; |
35 | break; |
36 | end |
37 | end |
38 | else |
39 | localCoeffs[1] = frictionCoeffs[1]; |
40 | end |
41 | for i=2,WheelsUtil.NUM_GROUNDS do |
42 | localCoeffs[i] = frictionCoeffs[i] or frictionCoeffs[i-1]; |
43 | end |
44 | return localCoeffs |
45 | end |
46 | |
47 | local tireType = {}; |
48 | tireType.name = name; |
49 | tireType.frictionCoeffs = getNoNilCoeffs(frictionCoeffs); |
50 | tireType.frictionCoeffsWet = getNoNilCoeffs(frictionCoeffsWet or frictionCoeffs); |
51 | table.insert(WheelsUtil.tireTypes, tireType); |
52 | end |
53 | |
54 | function WheelsUtil.getTireType(name) |
55 | for i, t in pairs(WheelsUtil.tireTypes) do |
56 | if t.name == name then |
57 | return i; |
58 | end |
59 | end |
60 | return nil; |
61 | end |
62 | |
63 | local mudTireCoeffs = {}; |
64 | mudTireCoeffs[WheelsUtil.GROUND_ROAD] = 1.15; |
65 | mudTireCoeffs[WheelsUtil.GROUND_HARD_TERRAIN] = 1.15; |
66 | mudTireCoeffs[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.1; |
67 | mudTireCoeffs[WheelsUtil.GROUND_FIELD] = 1.1; |
68 | |
69 | local mudTireCoeffsWet = {}; |
70 | mudTireCoeffsWet[WheelsUtil.GROUND_ROAD] = 1.05; |
71 | mudTireCoeffsWet[WheelsUtil.GROUND_HARD_TERRAIN] = 1.05; |
72 | mudTireCoeffsWet[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.0; |
73 | mudTireCoeffsWet[WheelsUtil.GROUND_FIELD] = 0.95; |
74 | |
75 | WheelsUtil.registerTireType("mud", mudTireCoeffs, mudTireCoeffsWet); |
76 | |
77 | local offRoadTireCoeffs = {}; |
78 | offRoadTireCoeffs[WheelsUtil.GROUND_ROAD] = 1.2; |
79 | offRoadTireCoeffs[WheelsUtil.GROUND_HARD_TERRAIN] = 1.15; |
80 | offRoadTireCoeffs[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.05; |
81 | offRoadTireCoeffs[WheelsUtil.GROUND_FIELD] = 1.0; |
82 | |
83 | local offRoadTireCoeffsWet = {}; |
84 | offRoadTireCoeffsWet[WheelsUtil.GROUND_ROAD] = 1.05; |
85 | offRoadTireCoeffsWet[WheelsUtil.GROUND_HARD_TERRAIN] = 1.0; |
86 | offRoadTireCoeffsWet[WheelsUtil.GROUND_SOFT_TERRAIN] = 0.95; |
87 | offRoadTireCoeffsWet[WheelsUtil.GROUND_FIELD] = 0.85; |
88 | |
89 | WheelsUtil.registerTireType("offRoad", offRoadTireCoeffs, offRoadTireCoeffsWet); |
90 | |
91 | local streetTireCoeffs = {}; |
92 | streetTireCoeffs[WheelsUtil.GROUND_ROAD] = 1.25; |
93 | streetTireCoeffs[WheelsUtil.GROUND_HARD_TERRAIN] = 1.15; |
94 | streetTireCoeffs[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.0; |
95 | streetTireCoeffs[WheelsUtil.GROUND_FIELD] = 0.9; |
96 | |
97 | local streetTireCoeffsWet = {}; |
98 | streetTireCoeffsWet[WheelsUtil.GROUND_ROAD] = 1.15; |
99 | streetTireCoeffsWet[WheelsUtil.GROUND_HARD_TERRAIN] = 1.0; |
100 | streetTireCoeffsWet[WheelsUtil.GROUND_SOFT_TERRAIN] = 0.85; |
101 | streetTireCoeffsWet[WheelsUtil.GROUND_FIELD] = 0.75; |
102 | |
103 | WheelsUtil.registerTireType("street", streetTireCoeffs, streetTireCoeffsWet); |
104 | |
105 | -- crawlers are similar to mud tires (but have slightly more traction in field, and loose less traction on fields when raining) |
106 | local crawlerCoeffs = {}; |
107 | crawlerCoeffs[WheelsUtil.GROUND_ROAD] = 1.15; |
108 | crawlerCoeffs[WheelsUtil.GROUND_HARD_TERRAIN] = 1.15; |
109 | crawlerCoeffs[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.15; |
110 | crawlerCoeffs[WheelsUtil.GROUND_FIELD] = 1.15; |
111 | |
112 | local crawlerCoeffsWet = {}; |
113 | crawlerCoeffsWet[WheelsUtil.GROUND_ROAD] = 1.05; |
114 | crawlerCoeffsWet[WheelsUtil.GROUND_HARD_TERRAIN] = 1.05; |
115 | crawlerCoeffsWet[WheelsUtil.GROUND_SOFT_TERRAIN] = 1.05; |
116 | crawlerCoeffsWet[WheelsUtil.GROUND_FIELD] = 1.05; |
117 | |
118 | WheelsUtil.registerTireType("crawler", crawlerCoeffs, crawlerCoeffsWet); |
119 | |
120 | function WheelsUtil.updateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, requiredDriveMode) |
121 | |
122 | local brakeAcc = false; |
123 | if (self.movingDirection*currentSpeed*Utils.sign(acceleration)) < -0.0003 then -- 0.0003 * 3600 = 1.08 km/h |
124 | -- do we want to accelerate in the opposite direction of the vehicle speed? |
125 | brakeAcc = true; |
126 | end; |
127 | |
128 | local accelerationPedal; |
129 | local brakePedal = 0; |
130 | if math.abs(acceleration) < 0.001 then |
131 | accelerationPedal = 0; |
132 | --brakePedal = 1; |
133 | |
134 | if currentSpeed < self.motor.lowBrakeForceSpeedLimit or doHandbrake then |
135 | --if self.rotatedTime == 0 or self.articulatedAxis == nil then -- TODO: Remove E3-Fix |
136 | if math.abs(self.rotatedTime) < 0.01 or self.articulatedAxis == nil then |
137 | brakePedal = 1; |
138 | end; |
139 | else |
140 | brakePedal = self.motor.lowBrakeForceScale; |
141 | end; |
142 | else |
143 | if not brakeAcc then |
144 | accelerationPedal = acceleration; |
145 | brakePedal = 0; |
146 | else |
147 | accelerationPedal = 0; |
148 | brakePedal = math.abs(acceleration); |
149 | end; |
150 | end; |
151 | |
152 | self:setBrakeLightsVisibility(brakePedal > self.motor.lowBrakeForceScale and currentSpeed > 0.0004); |
153 | self:setReverseLightsVisibility(self.movingDirection < 0 and currentSpeed > 0.0004); |
154 | |
155 | self.motor:updateMotorRpm(dt); |
156 | self.motor:updateGear(accelerationPedal); |
157 | |
158 | local absAccelerationPedal = math.abs(accelerationPedal); |
159 | |
160 | local wheelDriveTorque = 0; |
161 | |
162 | --print(string.format("brakeAcc:%s acc.:%6.4f accPed.:%6.4f braPed.:%6.4f curSpeed:%6.4f", tostring(brakeAcc), acceleration, accelerationPedal, brakePedal, currentSpeed)); |
163 | |
164 | if next(self.differentials) ~= nil and self.motorizedNode ~= nil then |
165 | --print(string.format("set vehicle props: %.2f %.1f %.1f %.1f", self.motor:getTorque(accelerationPedal, false)*absAccelerationPedal, self.motor:getCurMaxRpm(), self.motor:getGearRatio(), 0.01)); |
166 | local maxRotSpeed = self.motor:getCurMaxRpm() * math.pi / 30; |
167 | local torque = self.motor:getTorque(accelerationPedal, false); |
168 | |
169 | setVehicleProps(self.motorizedNode, torque, maxRotSpeed, self.motor:getGearRatio(), self.motor.maxClutchTorque); |
170 | else |
171 | local numTouching = 0; |
172 | local numNotTouching = 0; |
173 | local numHandbrake = 0; |
174 | |
175 | local axleSpeedSum = 0; |
176 | for _, wheel in pairs(self.wheels) do |
177 | if wheel.driveMode >= requiredDriveMode then |
178 | if doHandbrake and wheel.hasHandbrake then |
179 | numHandbrake = numHandbrake +1; |
180 | else |
181 | if wheel.hasGroundContact then |
182 | numTouching = numTouching+1; |
183 | else |
184 | numNotTouching = numNotTouching+1; |
185 | end; |
186 | end; |
187 | end; |
188 | end; |
189 | |
190 | if numTouching > 0 and absAccelerationPedal > 0.01 then |
191 | local axisTorque, brakePedalMotor = WheelsUtil.getWheelTorque(self, accelerationPedal); |
192 | if axisTorque ~= 0 then |
193 | wheelDriveTorque = axisTorque / (numTouching+numNotTouching); --*0.7); |
194 | else |
195 | brakePedal = brakePedalMotor; |
196 | end; |
197 | end; |
198 | end |
199 | |
200 | local doBrake = brakePedal > 0; --(brakePedal > 0 and self.lastSpeed > 0.0002) or doHandbrake; |
201 | for _, implement in pairs(self.attachedImplements) do |
202 | if implement.object ~= nil then |
203 | if doBrake then |
204 | implement.object:onBrake(brakePedal); |
205 | else |
206 | implement.object:onReleaseBrake(); |
207 | end; |
208 | end |
209 | end; |
210 | |
211 | for _, wheel in pairs(self.wheels) do |
212 | WheelsUtil.updateWheelPhysics(self, wheel, doHandbrake, wheelDriveTorque, brakePedal, requiredDriveMode, dt) |
213 | end; |
214 | |
215 | end; |
216 | |
217 | function WheelsUtil.getWheelTorque(self, accelerationPedal) |
218 | local torque, brakePedal = self.motor:getTorque(accelerationPedal, true); |
219 | local torque = torque * math.abs(accelerationPedal); |
220 | |
221 | return torque * self.motor:getGearRatio(), brakePedal; |
222 | end; |
223 | |
224 | function WheelsUtil.updateWheelPhysics(self, wheel, handbrake, motorTorque, brakePedal, requiredDriveMode, dt) |
225 | |
226 | local brakeForce = brakePedal*self.motor.brakeForce; |
227 | if handbrake and wheel.hasHandbrake then |
228 | brakeForce = self.motor.brakeForce*10; |
229 | end; |
230 | |
231 | if motorTorque ~= 0 then |
232 | if wheel.driveMode < requiredDriveMode then |
233 | motorTorque = 0; |
234 | elseif not wheel.hasGroundContact then |
235 | motorTorque = motorTorque*0.7; |
236 | end; |
237 | end |
238 | |
239 | local steeringAngle = wheel.steeringAngle; |
240 | |
241 | setWheelShapeProps(wheel.node, wheel.wheelShape, motorTorque, brakeForce, steeringAngle); |
242 | end; |
243 | |
244 | function WheelsUtil.updateWheelHasGroundContact(wheel) |
245 | |
246 | local x,_,_ = getWheelShapeContactPoint(wheel.node, wheel.wheelShape) |
247 | wheel.hasGroundContact = x~=nil; |
248 | --return wheel.hasGroundContact; |
249 | end; |
250 | |
251 | function WheelsUtil.updateWheelSteeringAngle(self, wheel, dt) |
252 | local steeringAngle = 0; |
253 | local rotatedTime = self.rotatedTime; |
254 | if wheel.rotSpeed ~= 0 then |
255 | if rotatedTime > 0 or wheel.rotSpeedNeg == nil then |
256 | steeringAngle = rotatedTime * wheel.rotSpeed; |
257 | else |
258 | steeringAngle = rotatedTime * wheel.rotSpeedNeg; |
259 | end |
260 | if steeringAngle > wheel.rotMax then |
261 | steeringAngle = wheel.rotMax; |
262 | elseif steeringAngle < wheel.rotMin then |
263 | steeringAngle = wheel.rotMin; |
264 | end; |
265 | if self.updateSteeringAngle ~= nil then |
266 | steeringAngle = self:updateSteeringAngle(wheel, dt, steeringAngle); |
267 | end |
268 | elseif wheel.steeringAxleScale ~= 0 then |
269 | steeringAngle = Utils.clamp(self.steeringAxleAngle * wheel.steeringAxleScale, wheel.steeringAxleRotMin, wheel.steeringAxleRotMax); |
270 | elseif wheel.versatileYRot then |
271 | if self.isServer then |
272 | if wheel.forceVersatility or wheel.hasGroundContact then |
273 | steeringAngle = Utils.getVersatileRotation(wheel.repr, wheel.node, dt, wheel.positionX, wheel.positionY, wheel.positionZ, wheel.steeringAngle, wheel.rotMin, wheel.rotMax); |
274 | end; |
275 | end; |
276 | end; |
277 | wheel.steeringAngle = steeringAngle; |
278 | end; |
279 | |
280 | function WheelsUtil.computeRpmFromWheels(self) |
281 | local wheelSpeed = 0; |
282 | local numWheels = 0; |
283 | for _, wheel in pairs(self.wheels) do |
284 | local axleSpeed = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape); -- rad/sec |
285 | if wheel.hasGroundContact then |
286 | wheelSpeed = wheelSpeed + axleSpeed * wheel.radius; |
287 | numWheels = numWheels+1; |
288 | end; |
289 | end; |
290 | |
291 | if numWheels > 0 then |
292 | return wheelSpeed*30/(math.pi*numWheels); |
293 | end |
294 | return 0; |
295 | end; |
296 | |
297 | function WheelsUtil.computeRpmFromSpeed(self) |
298 | -- v = w*r => w = v/r |
299 | -- w = 2pi*rpm / 60 |
300 | -- rpm = 60 * v/r/(2pi) |
301 | return (self.lastSpeedReal*30000 / math.pi); |
302 | end; |
303 | |
304 | function WheelsUtil.updateWheelsGraphics(self, dt) |
305 | if self.isServer then |
306 | self.hasWheelGroundContact = false; |
307 | end |
308 | |
309 | for i, wheel in pairs(self.wheels) do |
310 | WheelsUtil.updateWheelSteeringAngle(self, wheel, dt); |
311 | if self.isServer then |
312 | WheelsUtil.updateWheelHasGroundContact(wheel); |
313 | if wheel.hasGroundContact then |
314 | self.hasWheelGroundContact = true; |
315 | end; |
316 | |
317 | local x,y,z, xDrive = getWheelShapePosition(wheel.node, wheel.wheelShape); |
318 | |
319 | WheelsUtil.updateWheelGraphics(self, wheel, x, y, z, xDrive); |
320 | |
321 | --fill netinfo (on server) |
322 | wheel.netInfo.x = x; |
323 | wheel.netInfo.y = y; |
324 | wheel.netInfo.z = z; |
325 | wheel.netInfo.xDrive = xDrive; |
326 | else |
327 | -- client code |
328 | local x, y, z = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z; |
329 | local xDrive = wheel.netInfo.xDrive; |
330 | WheelsUtil.updateWheelGraphics(self, wheel, x, y, z, xDrive); |
331 | end; |
332 | end; |
333 | end; |
334 | |
335 | function WheelsUtil.updateWheelGraphics(self, wheel, x, y, z, xDrive) |
336 | local steeringAngle = wheel.steeringAngle; |
337 | if not wheel.showSteeringAngle then |
338 | steeringAngle = 0; |
339 | end; |
340 | local x,y,z = worldToLocal(getParent(wheel.repr), localToWorld(wheel.node, x,y,z)); |
341 | setTranslation(wheel.repr, x, y, z); |
342 | |
343 | if wheel.repr == wheel.driveNode then |
344 | setRotation(wheel.repr, xDrive, steeringAngle, 0); |
345 | else |
346 | setRotation(wheel.repr, 0, steeringAngle, 0); |
347 | setRotation(wheel.driveNode, xDrive, 0, 0); |
348 | end; |
349 | |
350 | if wheel.steeringNode ~= nil then |
351 | local refAngle = wheel.steeringNodeMaxRot; |
352 | local refTrans = wheel.steeringNodeMaxTransX |
353 | if steeringAngle < 0 then |
354 | refAngle = wheel.steeringNodeMinRot; |
355 | refTrans = wheel.steeringNodeMinTransX; |
356 | end; |
357 | local steering = 0; |
358 | if refAngle ~= 0 then |
359 | steering = steeringAngle / refAngle; |
360 | end; |
361 | |
362 | local x,y,z = getTranslation(wheel.steeringNode); |
363 | local x = refTrans * steering; |
364 | setTranslation(wheel.steeringNode, x, y, z); |
365 | end; |
366 | |
367 | if wheel.fenderNode ~= nil then |
368 | local angleDif = 0; |
369 | if steeringAngle > wheel.fenderRotMax then |
370 | angleDif = wheel.fenderRotMax - steeringAngle; |
371 | elseif steeringAngle < wheel.fenderRotMin then |
372 | angleDif = wheel.fenderRotMin - steeringAngle; |
373 | end; |
374 | setRotation(wheel.fenderNode, 0, angleDif, 0); |
375 | end; |
376 | end; |
377 | |
378 | function WheelsUtil.getTireFriction(tireType, groundType, wetScale) |
379 | if wetScale == nil then |
380 | wetScale = 0; |
381 | end |
382 | local coeff = WheelsUtil.tireTypes[tireType].frictionCoeffs[groundType]; |
383 | local coeffWet = WheelsUtil.tireTypes[tireType].frictionCoeffsWet[groundType]; |
384 | return coeff + (coeffWet-coeff)*wetScale; |
385 | end |
386 | |
387 | function WheelsUtil.getGroundType(isField, isRoad, attributes) |
388 | -- terrain softness: |
389 | -- [ 0, 0.1]: road |
390 | -- [0.1, 0.8]: hard terrain |
391 | -- [0.8, 1 ]: soft terrain |
392 | if isField then |
393 | return WheelsUtil.GROUND_FIELD |
394 | elseif isRoad or attributes[4] < 0.1 then |
395 | return WheelsUtil.GROUND_ROAD; |
396 | else |
397 | if attributes[4] > 0.8 then |
398 | return WheelsUtil.GROUND_SOFT_TERRAIN; |
399 | else |
400 | return WheelsUtil.GROUND_HARD_TERRAIN; |
401 | end |
402 | end |
403 | end
|
Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de