1 | -- |
2 | -- Vehicle |
3 | -- Base class for all vehicles |
4 | -- Functions of specializations called by vehicle: |
5 | -- bool validateAttacherJoint(implement, jointDesc, dt), return true if joint should be revalidated |
6 | -- |
7 | -- @author Stefan Geiger |
8 | -- @date 08/04/07 |
9 | -- |
10 | -- Copyright (C) GIANTS Software GmbH, Confidential, All Rights Reserved. |
11 | |
12 | Vehicle = {}; |
13 | |
14 | source("dataS/scripts/vehicles/specializations/VehicleSetBeaconLightEvent.lua"); |
15 | source("dataS/scripts/vehicles/specializations/VehicleSetTurnSignalEvent.lua"); |
16 | |
17 | InitStaticObjectClass(Vehicle, "Vehicle", ObjectIds.OBJECT_VEHICLE); |
18 | |
19 | Vehicle.springScale = 10; |
20 | |
21 | Vehicle.NUM_JOINTTYPES = 0; |
22 | Vehicle.jointTypeNameToInt = {} |
23 | |
24 | Vehicle.defaultWidth = 8; |
25 | Vehicle.defaultLength = 8; |
26 | |
27 | Vehicle.debugRendering = false; |
28 | |
29 | Vehicle.TURNSIGNAL_OFF = 0; |
30 | Vehicle.TURNSIGNAL_LEFT = 1; |
31 | Vehicle.TURNSIGNAL_RIGHT = 2; |
32 | Vehicle.TURNSIGNAL_HAZARD = 3; |
33 | Vehicle.turnSignalSendNumBits = 2; |
34 | |
35 | Vehicle.WHEEL_NO_CONTACT = 0; |
36 | Vehicle.WHEEL_OBJ_CONTACT = 1; |
37 | Vehicle.WHEEL_GROUND_CONTACT = 2; |
38 | |
39 | Vehicle.useFakeLightsIfNotReal = false; |
40 | |
41 | function Vehicle.registerJointType(name) |
42 | local key = "JOINTTYPE_"..string.upper(name); |
43 | if Vehicle[key] == nil then |
44 | Vehicle.NUM_JOINTTYPES = Vehicle.NUM_JOINTTYPES+1; |
45 | Vehicle[key] = Vehicle.NUM_JOINTTYPES; |
46 | Vehicle.jointTypeNameToInt[name] = Vehicle.NUM_JOINTTYPES; |
47 | end; |
48 | end; |
49 | |
50 | Vehicle.registerJointType("implement"); |
51 | Vehicle.registerJointType("trailer"); |
52 | Vehicle.registerJointType("trailerLow"); |
53 | Vehicle.registerJointType("telehandler"); |
54 | Vehicle.registerJointType("frontloader"); |
55 | Vehicle.registerJointType("semitrailer"); |
56 | Vehicle.registerJointType("attachableFrontloader"); |
57 | Vehicle.registerJointType("wheelLoader"); |
58 | Vehicle.registerJointType("manureBarrel"); |
59 | |
60 | function Vehicle:new(isServer, isClient, customMt) |
61 | --print("new vehicle"); |
62 | if Vehicle_mt == nil then |
63 | Vehicle_mt = Class(Vehicle, Object); |
64 | end; |
65 | |
66 | local mt = customMt; |
67 | if mt == nil then |
68 | mt = Vehicle_mt; |
69 | end; |
70 | |
71 | local instance = Object:new(isServer, isClient, mt); |
72 | instance.isAddedToMission = false; |
73 | instance.isDeleted = false; |
74 | instance.updateLoopIndex = -1; |
75 | return instance; |
76 | end; |
77 | |
78 | function Vehicle:load(configFile, positionX, offsetY, positionZ, yRot, typeName, isVehicleSaved, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) |
79 | |
80 | local modName, baseDirectory = Utils.getModNameAndBaseDirectory(configFile); |
81 | |
82 | self.configFileName = configFile; |
83 | self.baseDirectory = baseDirectory; |
84 | self.customEnvironment = modName; |
85 | self.typeName = typeName; |
86 | self.isVehicleSaved = Utils.getNoNil(isVehicleSaved, true); |
87 | |
88 | local typeDef = VehicleTypeUtil.vehicleTypes[self.typeName]; |
89 | self.specializations = typeDef.specializations; |
90 | |
91 | local xmlFile = loadXMLFile("TempConfig", configFile); |
92 | |
93 | for i=1, table.getn(self.specializations) do |
94 | if self.specializations[i].preLoad ~= nil then |
95 | self.specializations[i].preLoad(self, xmlFile); |
96 | end; |
97 | end; |
98 | |
99 | self.i3dFilename = getXMLString(xmlFile, "vehicle.filename"); |
100 | |
101 | if asyncCallbackFunction ~= nil then |
102 | Utils.loadSharedI3DFile(self.i3dFilename, baseDirectory, true, true, true, self.loadFinished, self, {xmlFile, positionX, offsetY, positionZ, yRot, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments}); |
103 | else |
104 | local i3dNode = Utils.loadSharedI3DFile(self.i3dFilename, baseDirectory, true, true, true); |
105 | self:loadFinished(i3dNode, {xmlFile, positionX, offsetY, positionZ, yRot, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments}) |
106 | end |
107 | end |
108 | |
109 | function Vehicle:loadFinished(i3dNode, arguments) |
110 | local xmlFile, positionX, offsetY, positionZ, yRot, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments = unpack(arguments); |
111 | local instance = self; |
112 | |
113 | if i3dNode == 0 then |
114 | if asyncCallbackFunction ~= nil then |
115 | asyncCallbackFunction(asyncCallbackObject, nil, asyncCallbackArguments); |
116 | end; |
117 | return; |
118 | end |
119 | |
120 | -- todo remove this as soon as the changes are done |
121 | instance.rootNode = getChildAt(i3dNode, 0) |
122 | --link(getRootNode(), instance.rootNode); |
123 | |
124 | local tempRootNode = createTransformGroup("tempRootNode"); |
125 | |
126 | instance.components = {}; |
127 | local numComponents = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.components#count"), 1); |
128 | |
129 | local maxNumComponents = getNumOfChildren(i3dNode); |
130 | if numComponents > maxNumComponents then |
131 | print("Error: Invalid components count '"..numComponents.."' in '".. self.configFileName.. "'"); |
132 | numComponents = maxNumComponents; |
133 | end; |
134 | |
135 | |
136 | local lastMotorizedNode = nil; |
137 | |
138 | local rootX, rootY, rootZ; |
139 | instance.vehicleNodes = {}; |
140 | for i=1, numComponents do |
141 | local namei = string.format("vehicle.components.component%d", i); |
142 | if not hasXMLProperty(xmlFile, namei) then |
143 | print("Warning: " .. namei .. " not found in '"..self.configFileName.."'"); |
144 | end; |
145 | |
146 | local component = {node=getChildAt(i3dNode, 0)}; |
147 | if not self.isServer then |
148 | setRigidBodyType(component.node, "Kinematic"); |
149 | end; |
150 | link(tempRootNode, component.node); |
151 | if i == 1 then |
152 | rootX, rootY, rootZ = getTranslation(component.node); |
153 | if rootY ~= 0 then |
154 | print("Warning: Y-Translation of component 1 (node 0>) ("..self.i3dFilename..") should be 0"); |
155 | end; |
156 | end; |
157 | -- the position of the first component is the zero |
158 | translate(component.node, -rootX, -rootY, -rootZ); |
159 | component.originalTranslation = {getTranslation(component.node)}; |
160 | component.originalRotation = {getRotation(component.node)}; |
161 | |
162 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, namei .. "#centerOfMass")); |
163 | if x ~= nil and y ~= nil and z ~= nil then |
164 | setCenterOfMass(component.node, x, y, z); |
165 | component.centerOfMass = { x, y, z }; |
166 | end; |
167 | local count = getXMLInt(xmlFile, namei .. "#solverIterationCount"); |
168 | if count ~= nil then |
169 | setSolverIterationCount(component.node, count); |
170 | component.solverIterationCount = count; |
171 | end; |
172 | |
173 | local motorized = getXMLBool(xmlFile, namei .. "#motorized"); -- Note: motorized is nil if not set the xml, and can be set by the wheels |
174 | |
175 | instance.vehicleNodes[component.node] = {component=component, motorized=motorized}; |
176 | |
177 | if motorized then |
178 | if lastMotorizedNode ~= nil then |
179 | if self.isServer then |
180 | addVehicleLink(lastMotorizedNode, component.node); |
181 | end |
182 | end |
183 | lastMotorizedNode = component.node; |
184 | end |
185 | |
186 | local clipDistance = getClipDistance(component.node); |
187 | if clipDistance >= 1000000 and getVisibility(component.node) then |
188 | local defaultClipdistance = 300; |
189 | print("Warning: No clipdistance set to node "..(i-1).."> ("..self.i3dFilename.."). Set default clipdistance ("..defaultClipdistance..")"); |
190 | setClipDistance(component.node, defaultClipdistance); |
191 | end; |
192 | |
193 | if g_isDevelopmentVersion then |
194 | if getLinearDamping(component.node) > 0.01 then |
195 | print(string.format("DevWarning: Non-zero linear damping (%.4f) for node %d in (%s). Is this correct?", getLinearDamping(component.node), i-1, self.i3dFilename)); |
196 | elseif getAngularDamping(component.node) > 0.05 then |
197 | print(string.format("DevWarning: Large angular damping (%.4f) for node %d in (%s). Is this correct?", getAngularDamping(component.node), i-1, self.i3dFilename)); |
198 | elseif getAngularDamping(component.node) < 0.0001 then |
199 | print(string.format("DevWarning: Zero damping for node %d in (%s). Is this correct?", i-1, self.i3dFilename)); |
200 | end |
201 | end |
202 | |
203 | table.insert(instance.components, component); |
204 | end; |
205 | instance.interpolationAlpha = 0; |
206 | instance.interpolationDuration = 50+30; |
207 | instance.positionIsDirty = false; |
208 | |
209 | delete(i3dNode); |
210 | |
211 | -- position the vehicle |
212 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, positionX, 300, positionZ); |
213 | |
214 | setTranslation(tempRootNode, positionX, terrainHeight+offsetY, positionZ); |
215 | setRotation(tempRootNode, 0, yRot, 0); |
216 | |
217 | -- now move the objects to the scene root node |
218 | for i=1, numComponents do |
219 | local x,y,z = getWorldTranslation(instance.components[i].node); |
220 | local rx,ry,rz = getWorldRotation(instance.components[i].node); |
221 | local qx, qy, qz, qw = getWorldQuaternion(instance.components[i].node); |
222 | setTranslation(instance.components[i].node, x,y,z); |
223 | setRotation(instance.components[i].node, rx,ry,rz); |
224 | link(getRootNode(), instance.components[i].node); |
225 | |
226 | instance.components[i].sentTranslation = {x,y,z}; |
227 | instance.components[i].sentRotation = {rx,ry,rz}; |
228 | instance.components[i].lastTranslation = {x,y,z}; |
229 | instance.components[i].lastRotation = {qx,qy,qz,qw}; |
230 | instance.components[i].targetTranslation = {x,y,z}; |
231 | instance.components[i].targetRotation = {qx,qy,qz,qw}; |
232 | instance.components[i].curTranslation = {x,y,z}; |
233 | instance.components[i].curRotation = {qx,qy,qz,qw}; |
234 | --instance.components[i].networkPoses = {}; |
235 | end; |
236 | delete(tempRootNode); |
237 | |
238 | -- load wheels |
239 | instance.maxRotTime = 0; |
240 | instance.minRotTime = 0; |
241 | instance.autoRotateBackSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.wheels#autoRotateBackSpeed"), 1.0); |
242 | instance.wheels = {}; |
243 | local i = 0; |
244 | while true do |
245 | local wheelnamei = string.format("vehicle.wheels.wheel(%d)", i); |
246 | local wheel = {}; |
247 | local reprStr = getXMLString(xmlFile, wheelnamei .. "#repr"); |
248 | if reprStr == nil then |
249 | break; |
250 | end; |
251 | wheel.repr = Utils.indexToObject(instance.components, reprStr); |
252 | if wheel.repr == nil then |
253 | print("Error: invalid wheel repr " .. reprStr); |
254 | else |
255 | wheel.driveNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, wheelnamei .. "#driveNode")); |
256 | if wheel.driveNode == nil then |
257 | wheel.driveNode = wheel.repr; |
258 | end; |
259 | wheel.isSynchronized = Utils.getNoNil(getXMLBool(xmlFile, wheelnamei .. "#isSynchronized"), true); |
260 | |
261 | --wheel.node = getParent(wheel.repr); |
262 | -- look for top parent component node |
263 | wheel.node = self:getParentComponent(wheel.repr); |
264 | if wheel.node == 0 then |
265 | print("Invalid wheel-repr. Needs to be a child of a collision"); |
266 | end; |
267 | wheel.positionX, wheel.positionY, wheel.positionZ = localToLocal(wheel.repr, wheel.node, 0,0,0); |
268 | wheel.startPositionX, wheel.startPositionY, wheel.startPositionZ = getTranslation(wheel.repr); |
269 | wheel.dirtAmount = 0; |
270 | wheel.lastColor = {0,0,0,0}; |
271 | wheel.contact = Vehicle.WHEEL_NO_CONTACT; |
272 | wheel.steeringAngle = 0; |
273 | wheel.hasGroundContact = false; |
274 | wheel.hasHandbrake = true; |
275 | |
276 | -- try to load wheels from i3d |
277 | local wheelFilename = getXMLString(xmlFile, wheelnamei .. "#wheelFilename"); |
278 | if wheelFilename ~= nil then |
279 | wheel.wheelFilename = wheelFilename; |
280 | wheelIndex = Utils.getNoNil(getXMLString(xmlFile, wheelnamei .. "#wheelIndex"), "0|0"); |
281 | local i3dNode = Utils.loadSharedI3DFile(wheelFilename, self.baseDirectory, false, false, false); |
282 | if i3dNode ~= 0 then |
283 | local loadedWheel = Utils.indexToObject(i3dNode, wheelIndex); |
284 | link(wheel.driveNode, loadedWheel); |
285 | delete(i3dNode); |
286 | end; |
287 | end; |
288 | |
289 | local vehicleNode = self.vehicleNodes[wheel.node]; |
290 | if vehicleNode ~= nil and vehicleNode.component ~= nil and vehicleNode.component.motorized == nil then |
291 | vehicleNode.component.motorized = true; |
292 | if lastMotorizedNode ~= nil then |
293 | if self.isServer then |
294 | addVehicleLink(lastMotorizedNode, vehicleNode.component.node); |
295 | end |
296 | end |
297 | lastMotorizedNode = vehicleNode.component.node; |
298 | end |
299 | |
300 | self:loadDynamicWheelDataFromXML(xmlFile, wheelnamei, wheel); |
301 | |
302 | wheel.width = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#width"), wheel.radius * 0.8); |
303 | |
304 | if g_currentMission.tyreTrackSystem ~= nil and Utils.getNoNil(getXMLBool(xmlFile, wheelnamei .. "#hasTyreTracks"), false) then |
305 | wheel.tyreTrackIndex = g_currentMission.tyreTrackSystem:createTrack(wheel.width, Utils.getNoNil(getXMLInt(xmlFile, wheelnamei .. "#tyreTrackAtlasIndex"), 0)); |
306 | end |
307 | wheel.xmlIndex = i; |
308 | table.insert(instance.wheels, wheel); |
309 | end; |
310 | i = i+1; |
311 | end; |
312 | |
313 | self:loadWheelsSteeringDataFromXML(xmlFile); |
314 | |
315 | self.aiSteeringSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.aiSteeringSpeed"), 1)*0.001; |
316 | |
317 | self.aiLeftMarker = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiLeftMarker#index")); |
318 | self.aiRightMarker = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiRightMarker#index")); |
319 | self.aiBackMarker = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiBackMarker#index")); |
320 | self.aiTrafficCollisionTrigger = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.aiTrafficCollisionTrigger#index")); |
321 | |
322 | self.aiTerrainDetailChannel1 = -1; |
323 | self.aiTerrainDetailChannel2 = -1; |
324 | self.aiTerrainDetailChannel3 = -1; |
325 | self.aiTerrainDetailChannel4 = -1; |
326 | |
327 | self.aiTerrainDetailProhibitedMask = 0; |
328 | self.aiRequiredFruitType = FruitUtil.FRUITTYPE_UNKNOWN; |
329 | self.aiRequiredMinGrowthState = 0; |
330 | self.aiRequiredMaxGrowthState = 0; |
331 | self.aiProhibitedFruitType = FruitUtil.FRUITTYPE_UNKNOWN; |
332 | self.aiProhibitedMinGrowthState = 0; |
333 | self.aiProhibitedMaxGrowthState = 0; |
334 | |
335 | instance.vehicleMovingDirection = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.vehicleMovingDirection#value"), 1); |
336 | instance.movingDirection = 0; |
337 | |
338 | instance.steeringAxleNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, "vehicle.steeringAxleNode#index")); |
339 | if instance.steeringAxleNode == nil then |
340 | instance.steeringAxleNode = instance.components[1].node; |
341 | end; |
342 | |
343 | instance.sizeWidth = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.size#width"), Vehicle.defaultWidth); |
344 | instance.sizeLength = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.size#length"), Vehicle.defaultLength); |
345 | instance.widthOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.size#widthOffset"), 0); |
346 | instance.lengthOffset = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.size#lengthOffset"), 0); |
347 | |
348 | instance.typeDesc = Utils.getXMLI18N(xmlFile, "vehicle.typeDesc", "", "TypeDescription", instance.customEnvironment); |
349 | |
350 | instance.serverMass = 0; |
351 | |
352 | -- spare parts |
353 | local i = 0; |
354 | while true do |
355 | local key = string.format("vehicle.spareParts.sparePart(%d)", i); |
356 | if not hasXMLProperty(xmlFile, key) then |
357 | break; |
358 | end; |
359 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index")); |
360 | if hasXMLProperty(xmlFile, key .. "#visibility") then |
361 | setVisibility(node, getXMLBool(xmlFile, key .. "#visibility")); |
362 | end; |
363 | i = i + 1; |
364 | end; |
365 | |
366 | self.lights = {}; |
367 | self.numLightTypes = 0; |
368 | local i = 0; |
369 | while true do |
370 | local key = string.format("vehicle.lights.light(%d)", i); |
371 | if not hasXMLProperty(xmlFile, key) then |
372 | break; |
373 | end; |
374 | local realLight = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#realLight")); |
375 | local fakeLight = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#fakeLight")); |
376 | local decoration = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#decoration")); |
377 | local lightType = getXMLInt(xmlFile, key .. "#lightType"); |
378 | local useFakeLightIfNotReal = Utils.getNoNil(getXMLBool(xmlFile, key .. "#useFakeLightIfNotReal"), realLight == nil or self.useFakeLightsIfNotReal); |
379 | if lightType ~= nil and lightType >= 0 and lightType <= 30 and (realLight ~= nil or fakeLight ~= nil or decoration ~= nil) then |
380 | self.numLightTypes = math.max(self.numLightTypes, lightType+1); |
381 | local entry = {realLight=realLight, fakeLight=fakeLight, decoration=decoration, lightType = lightType, useFakeLightIfNotReal=useFakeLightIfNotReal }; |
382 | if realLight ~= nil then |
383 | setVisibility(realLight, false); |
384 | end |
385 | if fakeLight ~= nil then |
386 | setVisibility(fakeLight, false); |
387 | end |
388 | if decoration ~= nil then |
389 | setVisibility(decoration, false); |
390 | end |
391 | table.insert(self.lights, entry); |
392 | else |
393 | print("Invalid light. Light type must be between 0 and 30. A real, fake or decoration light needs to be declared"); |
394 | end; |
395 | i = i + 1; |
396 | end; |
397 | |
398 | self.maxNumRealLights = g_maxNumRealVehicleLights; |
399 | |
400 | if table.getn(self.lights) == 0 then |
401 | -- fallback to read old light format from xml |
402 | |
403 | local numLights = Utils.getNoNil(getXMLInt(xmlFile, "vehicle.lights#count"), 0); |
404 | for i=1, numLights do |
405 | local lightnamei = string.format("vehicle.lights.light%d", i); |
406 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, lightnamei .. "#index")); |
407 | if node ~= nil then |
408 | self.numLightTypes = 1; |
409 | setVisibility(node, false); |
410 | table.insert(self.lights, {realLight=node, lightType=0, useFakeLightIfNotReal=false }); |
411 | end; |
412 | end; |
413 | self.maxNumRealLights = table.getn(self.lights); |
414 | |
415 | local i = 0; |
416 | while true do |
417 | local key = string.format("vehicle.lightCoronas.lightCorona(%d)", i); |
418 | if not hasXMLProperty(xmlFile, key) then |
419 | break; |
420 | end; |
421 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index")); |
422 | if node ~= nil then |
423 | self.numLightTypes = 1; |
424 | setVisibility(node, false); |
425 | table.insert(self.lights, {decoration=node, lightType=0, useFakeLightIfNotReal=false }); |
426 | end; |
427 | i = i + 1; |
428 | end; |
429 | |
430 | local i = 0; |
431 | while true do |
432 | local key = string.format("vehicle.lightCones.lightCone(%d)", i); |
433 | if not hasXMLProperty(xmlFile, key) then |
434 | break; |
435 | end; |
436 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#index")); |
437 | if node ~= nil then |
438 | setVisibility(node, false); |
439 | table.insert(self.lights, {fakeLight=node, lightType=0, useFakeLightIfNotReal=false }); |
440 | end; |
441 | i = i + 1; |
442 | end; |
443 | end |
444 | |
445 | self.beaconLights = {}; |
446 | local i = 0; |
447 | while true do |
448 | local key = string.format("vehicle.beaconLights.beaconLight(%d)", i); |
449 | if not hasXMLProperty(xmlFile, key) then |
450 | break; |
451 | end; |
452 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
453 | local rotator = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#rotator")); |
454 | local filename = getXMLString(xmlFile, key.."#filename"); |
455 | if filename ~= nil then |
456 | local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false); |
457 | if i3dNode ~= 0 then |
458 | local rootNode = getChildAt(i3dNode, 0); |
459 | link(node, rootNode); |
460 | delete(i3dNode); |
461 | setTranslation(rootNode, 0,0,0); |
462 | |
463 | local rotationIndex = getUserAttribute(rootNode, "rotationIndex"); |
464 | if rotationIndex ~= nil then |
465 | node = Utils.indexToObject(rootNode, rotationIndex); |
466 | end; |
467 | local rotatorIndex = getUserAttribute(rootNode, "rotatorIndex"); |
468 | if rotatorIndex ~= nil then |
469 | rotator = Utils.indexToObject(rootNode, rotatorIndex); |
470 | end; |
471 | end |
472 | end; |
473 | |
474 | local speed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#speed"), 0.02); |
475 | if node ~= nil then |
476 | if speed > 0 then |
477 | local rot = math.random(0, math.pi*2); |
478 | setRotation(node, 0, rot, 0); |
479 | if rotator ~= nil then |
480 | setRotation(rotator, 0, rot, 0); |
481 | end; |
482 | end; |
483 | setVisibility(node, false); |
484 | table.insert(self.beaconLights, {node=node, speed=speed, rotator=rotator, filename=filename}); |
485 | end; |
486 | i = i + 1; |
487 | end; |
488 | self.beaconLightsActive = false; |
489 | |
490 | self.brakeLights = {}; |
491 | local i = 0; |
492 | while true do |
493 | local key = string.format("vehicle.brakeLights.brakeLight(%d)", i); |
494 | if not hasXMLProperty(xmlFile, key) then |
495 | break; |
496 | end; |
497 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
498 | if node ~= nil then |
499 | setVisibility(node, false); |
500 | table.insert(self.brakeLights, node); |
501 | end; |
502 | i = i + 1; |
503 | end; |
504 | self.brakeLightsVisibility = false; |
505 | |
506 | self.reverseLights = {}; |
507 | local i = 0; |
508 | while true do |
509 | local key = string.format("vehicle.reverseLights.reverseLight(%d)", i); |
510 | if not hasXMLProperty(xmlFile, key) then |
511 | break; |
512 | end; |
513 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
514 | if node ~= nil then |
515 | setVisibility(node, false); |
516 | table.insert(self.reverseLights, node); |
517 | end; |
518 | i = i + 1; |
519 | end; |
520 | self.reverseLightsVisibility = false; |
521 | |
522 | self.turnSignals = {}; |
523 | self.turnSignals.left = {}; |
524 | self.turnSignals.right = {}; |
525 | local i = 0; |
526 | while true do |
527 | local keyLeft = string.format("vehicle.turnSignals.turnSignalLeft(%d)", i); |
528 | local keyRight = string.format("vehicle.turnSignals.turnSignalRight(%d)", i); |
529 | if not hasXMLProperty(xmlFile, keyLeft) and not hasXMLProperty(xmlFile, keyRight) then |
530 | break; |
531 | end; |
532 | |
533 | if hasXMLProperty(xmlFile, keyLeft) then |
534 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, keyLeft.."#index")); |
535 | setVisibility(node, false); |
536 | table.insert(self.turnSignals.left, node); |
537 | end; |
538 | |
539 | if hasXMLProperty(xmlFile, keyRight) then |
540 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, keyRight.."#index")); |
541 | setVisibility(node, false); |
542 | table.insert(self.turnSignals.right, node); |
543 | end; |
544 | i = i + 1; |
545 | end; |
546 | self.turnSignalState = Vehicle.TURNSIGNAL_OFF; |
547 | |
548 | if self.isClient then |
549 | self.sampleAttach = Utils.loadSample(xmlFile, {}, "vehicle.attachSound", nil, instance.baseDirectory); |
550 | end; |
551 | |
552 | if self.isClient then |
553 | self.sampleHydraulic = Utils.loadSample(xmlFile, {}, "vehicle.hydraulicSound", nil, self.baseDirectory); |
554 | end; |
555 | |
556 | |
557 | instance.componentJoints = {}; |
558 | |
559 | local componentJointI = 0; |
560 | while true do |
561 | local key = string.format("vehicle.components.joint(%d)", componentJointI); |
562 | local index1 = getXMLInt(xmlFile, key.."#component1"); |
563 | local index2 = getXMLInt(xmlFile, key.."#component2"); |
564 | local jointIndexStr = getXMLString(xmlFile, key.."#index"); |
565 | if index1 == nil or index2 == nil or jointIndexStr == nil then |
566 | break; |
567 | end; |
568 | local jointNode = Utils.indexToObject(instance.components, jointIndexStr); |
569 | if jointNode ~= nil and jointNode ~= 0 then |
570 | local jointDesc = {}; |
571 | jointDesc.componentIndices = {index1+1, index2+1}; |
572 | jointDesc.jointNode = jointNode; |
573 | jointDesc.jointNodeActor1 = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#indexActor1")), jointNode); |
574 | if self.isServer then |
575 | local constr = JointConstructor:new(); |
576 | if instance.components[index1+1] == nil or instance.components[index2+1] == nil then |
577 | print("Error: invalid joint indices (".. index1.. ", "..index2..") for component joint "..componentJointI.." in '"..self.configFileName.."'"); |
578 | break; |
579 | end; |
580 | constr:setActors(instance.components[index1+1].node, instance.components[index2+1].node); |
581 | constr:setJointTransforms(jointNode, jointDesc.jointNodeActor1); |
582 | |
583 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimit")); |
584 | local rotLimits = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) }; |
585 | |
586 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimit")); |
587 | local transLimits = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
588 | |
589 | jointDesc.rotLimit = rotLimits; |
590 | jointDesc.transLimit = transLimits; |
591 | |
592 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotMinLimit")); |
593 | local rotMinLimits = { Utils.getNoNilRad(x, nil), Utils.getNoNilRad(y, nil), Utils.getNoNilRad(z, nil) }; |
594 | |
595 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transMinLimit")); |
596 | local transMinLimits = { x,y,z }; |
597 | |
598 | for i=1,3 do |
599 | if rotMinLimits[i] == nil then |
600 | if rotLimits[i] >= 0 then |
601 | rotMinLimits[i] = -rotLimits[i]; |
602 | else |
603 | rotMinLimits[i] = rotLimits[i]+1; |
604 | end |
605 | end |
606 | if transMinLimits[i] == nil then |
607 | if transLimits[i] >= 0 then |
608 | transMinLimits[i] = -transLimits[i]; |
609 | else |
610 | transMinLimits[i] = transLimits[i]+1; |
611 | end |
612 | end |
613 | end |
614 | |
615 | jointDesc.rotMinLimit = rotMinLimits; |
616 | jointDesc.transMinLimit = transMinLimits; |
617 | |
618 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimitSpring")); |
619 | local rotLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
620 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotLimitDamping")); |
621 | local rotLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) }; |
622 | jointDesc.rotLimitSpring = rotLimitSpring; |
623 | jointDesc.rotLimitDamping = rotLimitDamping; |
624 | constr:setRotationLimitSpring(rotLimitSpring[1], rotLimitDamping[1], rotLimitSpring[2], rotLimitDamping[2], rotLimitSpring[3], rotLimitDamping[3]); |
625 | |
626 | |
627 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimitSpring")); |
628 | local transLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
629 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transLimitDamping")); |
630 | local transLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) }; |
631 | jointDesc.transLimitSpring = transLimitSpring; |
632 | jointDesc.transLimitDamping = transLimitDamping; |
633 | constr:setTranslationLimitSpring(transLimitSpring[1], transLimitDamping[1], transLimitSpring[2], transLimitDamping[2], transLimitSpring[3], transLimitDamping[3]); |
634 | |
635 | jointDesc.zRotationXOffset = 0; |
636 | local zRotationNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#zRotationNode")); |
637 | if zRotationNode ~= nil then |
638 | jointDesc.zRotationXOffset,_,_ = localToLocal(zRotationNode, jointNode, 0,0,0); |
639 | constr:setZRotationXOffset(jointDesc.zRotationXOffset); |
640 | end |
641 | |
642 | for i=1, 3 do |
643 | if rotLimits[i] >= rotMinLimits[i] then |
644 | constr:setRotationLimit(i-1, rotMinLimits[i], rotLimits[i]); |
645 | end; |
646 | |
647 | if transLimits[i] >= transMinLimits[i] then |
648 | constr:setTranslationLimit(i-1, true, transMinLimits[i], transLimits[i]); |
649 | else |
650 | constr:setTranslationLimit(i-1, false, 0, 0); |
651 | end; |
652 | end; |
653 | |
654 | if Utils.getNoNil(getXMLBool(xmlFile, key.."#breakable"), false) then |
655 | local force = Utils.getNoNil(getXMLFloat(xmlFile, key.."#breakForce"), 10); |
656 | local torque = Utils.getNoNil(getXMLFloat(xmlFile, key.."#breakTorque"), 10); |
657 | constr:setBreakable(force, torque); |
658 | end; |
659 | |
660 | local enableCollision = Utils.getNoNil(getXMLBool(xmlFile, key.."#enableCollision"), false); |
661 | constr:setEnableCollision(enableCollision); |
662 | |
663 | |
664 | -- Rotational drive |
665 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#rotDriveVelocity")); |
666 | local rotDriveVelocity = { Utils.getNoNilRad(x, nil), Utils.getNoNilRad(y, nil), Utils.getNoNilRad(z, nil) }; -- convert from deg/s to rad/s |
667 | |
668 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#maxRotDriveForce")); |
669 | local maxRotDriveForce = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
670 | |
671 | jointDesc.rotDriveVelocity = rotDriveVelocity; |
672 | jointDesc.maxRotDriveForce = maxRotDriveForce; |
673 | if maxRotDriveForce[1] > 0.0001 then |
674 | constr:setAngularDriveTwist(false, rotDriveVelocity[1] ~= nil, 0.0, 0.0, maxRotDriveForce[1]); |
675 | end |
676 | if maxRotDriveForce[2] > 0.0001 or maxRotDriveForce[3] > 0.0001 then |
677 | constr:setAngularDriveSwing(false, rotDriveVelocity[2] ~= nil and rotDriveVelocity[3] ~= nil, 0.0, 0.0, math.max(maxRotDriveForce[2], maxRotDriveForce[3])); |
678 | end |
679 | constr:setAngularDriveVelocity(Utils.getNoNil(rotDriveVelocity[1], 0), Utils.getNoNil(rotDriveVelocity[2], 0), Utils.getNoNil(rotDriveVelocity[3], 0)); |
680 | |
681 | |
682 | -- Translational drive |
683 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveVelocity")); |
684 | local transDriveVelocity = { x,y,z }; |
685 | |
686 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDrivePosition")); |
687 | local transDrivePosition = { x,y,z }; |
688 | |
689 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveSpring")); |
690 | local transDriveSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
691 | |
692 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#transDriveDamping")); |
693 | local transDriveDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) }; |
694 | |
695 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, key.."#maxTransDriveForce")); |
696 | local maxTransDriveForce = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
697 | |
698 | jointDesc.transDriveVelocity = transDriveVelocity; |
699 | jointDesc.transDrivePosition = transDrivePosition; |
700 | jointDesc.transDriveSpring = transDriveSpring; |
701 | jointDesc.transDriveDamping = transDriveDamping; |
702 | jointDesc.maxTransDriveForce = maxTransDriveForce; |
703 | for i=1,3 do |
704 | if maxTransDriveForce[i] > 0.0001 and (transDriveVelocity[i] ~= nil or transDrivePosition[i] ~= nil) then |
705 | local pos = Utils.getNoNil(transDrivePosition[i], 0); |
706 | local vel = Utils.getNoNil(transDriveVelocity[i], 0); |
707 | constr:setLinearDrive(i-1, transDrivePosition[i] ~= nil, transDriveVelocity[i] ~= nil, transDriveSpring[i], transDriveDamping[i], maxTransDriveForce[i], pos, vel); |
708 | end |
709 | end |
710 | |
711 | jointDesc.jointIndex = constr:finalize(); |
712 | end; |
713 | table.insert(instance.componentJoints, jointDesc); |
714 | end; |
715 | componentJointI = componentJointI +1; |
716 | end; |
717 | |
718 | local collisionPairI = 0; |
719 | while true do |
720 | local key = string.format("vehicle.components.collisionPair(%d)", collisionPairI); |
721 | if not hasXMLProperty(xmlFile, key) then |
722 | break; |
723 | end; |
724 | local enabled = getXMLBool(xmlFile, key.."#enabled"); |
725 | local index1 = getXMLInt(xmlFile, key.."#component1"); |
726 | local index2 = getXMLInt(xmlFile, key.."#component2"); |
727 | if index1 ~= nil and index2 ~= nil and enabled ~= nil then |
728 | local component1 = instance.components[index1+1]; |
729 | local component2 = instance.components[index2+1]; |
730 | if component1 ~= nil and component2 ~= nil then |
731 | if not enabled then |
732 | setPairCollision(component1.node, component2.node, false); |
733 | end; |
734 | end; |
735 | end; |
736 | collisionPairI = collisionPairI +1; |
737 | end; |
738 | |
739 | -- |
740 | instance.attacherJoints = {}; |
741 | |
742 | local i=0; |
743 | while true do |
744 | local baseName = string.format("vehicle.attacherJoints.attacherJoint(%d)", i); |
745 | local index = getXMLString(xmlFile, baseName.. "#index"); |
746 | if index == nil then |
747 | break; |
748 | end; |
749 | local object = Utils.indexToObject(instance.components, index); |
750 | if object ~= nil then |
751 | local entry = {}; |
752 | entry.jointTransform = object; |
753 | entry.jointOrigRot = { getRotation(entry.jointTransform) }; |
754 | entry.jointOrigTrans = { getTranslation(entry.jointTransform) }; |
755 | |
756 | local jointTypeStr = getXMLString(xmlFile, baseName.. "#jointType"); |
757 | local jointType; |
758 | if jointTypeStr ~= nil then |
759 | jointType = Vehicle.jointTypeNameToInt[jointTypeStr]; |
760 | if jointType == nil then |
761 | print("Warning: invalid jointType " .. jointTypeStr); |
762 | end; |
763 | end; |
764 | if jointType == nil then |
765 | jointType = Vehicle.JOINTTYPE_IMPLEMENT; |
766 | end; |
767 | entry.jointType = jointType; |
768 | entry.allowsJointLimitMovement = Utils.getNoNil(getXMLBool(xmlFile, baseName.."#allowsJointLimitMovement"), true); |
769 | entry.allowsLowering = Utils.getNoNil(getXMLBool(xmlFile, baseName.."#allowsLowering"), true); |
770 | |
771 | if jointType == Vehicle.JOINTTYPE_TRAILER or jointType == Vehicle.JOINTTYPE_TRAILERLOW then |
772 | entry.allowsLowering = false; |
773 | end; |
774 | |
775 | self:loadPowerTakeoff(xmlFile, baseName, entry); |
776 | |
777 | entry.canTurnOnImplement = Utils.getNoNil(getXMLBool(xmlFile, baseName.."#canTurnOnImplement"), true); |
778 | |
779 | local rotationNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. "#rotationNode")); |
780 | if rotationNode ~= nil then |
781 | entry.rotationNode = rotationNode; |
782 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#maxRot")); |
783 | entry.maxRot = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) }; |
784 | |
785 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#minRot")); |
786 | local rx,ry,rz = getRotation(rotationNode); |
787 | entry.minRot = { Utils.getNoNilRad(x, rx), Utils.getNoNilRad(y, ry), Utils.getNoNilRad(z, rz) }; |
788 | |
789 | end; |
790 | local rotationNode2 = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. "#rotationNode2")); |
791 | if rotationNode2 ~= nil then |
792 | entry.rotationNode2 = rotationNode2; |
793 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#maxRot2")); |
794 | entry.maxRot2 = { math.rad(Utils.getNoNil(x, 0)), math.rad(Utils.getNoNil(y, 0)), math.rad(Utils.getNoNil(z, 0)) }; |
795 | |
796 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#minRot2")); |
797 | local rx,ry,rz = getRotation(rotationNode2); |
798 | entry.minRot2 = { Utils.getNoNilRad(x, rx), Utils.getNoNilRad(y, ry), Utils.getNoNilRad(z, rz) }; |
799 | end; |
800 | entry.maxRotDistanceToGround = Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#maxRotDistanceToGround"), 0.7); |
801 | entry.minRotDistanceToGround = Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#minRotDistanceToGround"), 1.0); |
802 | entry.maxRotRotationOffset = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#maxRotRotationOffset"), 0)); |
803 | entry.minRotRotationOffset = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#minRotRotationOffset"), 8)); |
804 | |
805 | |
806 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#maxRotLimit")); |
807 | entry.maxRotLimit = {}; |
808 | entry.maxRotLimit[1] = math.rad(math.abs(Utils.getNoNil(x, 0))); |
809 | entry.maxRotLimit[2] = math.rad(math.abs(Utils.getNoNil(y, 0))); |
810 | entry.maxRotLimit[3] = math.rad(math.abs(Utils.getNoNil(z, 0))); |
811 | |
812 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#minRotLimit")); |
813 | entry.minRotLimit = {}; |
814 | entry.minRotLimit[1] = math.rad(math.abs(Utils.getNoNil(x, 0))); |
815 | entry.minRotLimit[2] = math.rad(math.abs(Utils.getNoNil(y, 0))); |
816 | entry.minRotLimit[3] = math.rad(math.abs(Utils.getNoNil(z, 0))); |
817 | |
818 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#maxTransLimit")); |
819 | entry.maxTransLimit = {}; |
820 | entry.maxTransLimit[1] = math.abs(Utils.getNoNil(x, 0)); |
821 | entry.maxTransLimit[2] = math.abs(Utils.getNoNil(y, 0)); |
822 | entry.maxTransLimit[3] = math.abs(Utils.getNoNil(z, 0)); |
823 | |
824 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#minTransLimit")); |
825 | entry.minTransLimit = {}; |
826 | entry.minTransLimit[1] = math.abs(Utils.getNoNil(x, 0)); |
827 | entry.minTransLimit[2] = math.abs(Utils.getNoNil(y, 0)); |
828 | entry.minTransLimit[3] = math.abs(Utils.getNoNil(z, 0)); |
829 | |
830 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#jointPositionOffset")); |
831 | entry.jointPositionOffset = {}; |
832 | entry.jointPositionOffset[1] = Utils.getNoNil(x, 0); |
833 | entry.jointPositionOffset[2] = Utils.getNoNil(y, 0); |
834 | entry.jointPositionOffset[3] = Utils.getNoNil(z, 0); |
835 | |
836 | entry.transNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.."#transNode")); |
837 | if entry.transNode ~= nil then |
838 | entry.transNodeOrgTrans = {getTranslation(entry.transNode)}; |
839 | entry.transMinYHeight = Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#transMinYHeight"), entry.jointOrigTrans[2]); |
840 | entry.transMaxYHeight = Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#transMaxYHeight"), entry.jointOrigTrans[2]); |
841 | end; |
842 | |
843 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#rotLimitSpring")); |
844 | local rotLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
845 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#rotLimitDamping")); |
846 | local rotLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) }; |
847 | entry.rotLimitSpring = rotLimitSpring; |
848 | entry.rotLimitDamping = rotLimitDamping; |
849 | |
850 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#transLimitSpring")); |
851 | local transLimitSpring = { Utils.getNoNil(x, 0), Utils.getNoNil(y, 0), Utils.getNoNil(z, 0) }; |
852 | local x, y, z = Utils.getVectorFromString(getXMLString(xmlFile, baseName.."#transLimitDamping")); |
853 | local transLimitDamping = { Utils.getNoNil(x, 1), Utils.getNoNil(y, 1), Utils.getNoNil(z, 1) }; |
854 | entry.transLimitSpring = transLimitSpring; |
855 | entry.transLimitDamping = transLimitDamping; |
856 | |
857 | entry.moveTime = Utils.getNoNil(getXMLFloat(xmlFile, baseName.."#moveTime"), 0.5)*1000; |
858 | |
859 | entry.enableCollision = Utils.getNoNil(getXMLBool(xmlFile, baseName.."#enableCollision"), false); |
860 | |
861 | local topArmFilename = getXMLString(xmlFile, baseName.. ".topArm#filename"); |
862 | if topArmFilename ~= nil then |
863 | local baseNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName.. ".topArm#baseNode")); |
864 | if baseNode ~= nil then |
865 | local i3dNode = Utils.loadSharedI3DFile(topArmFilename,self.baseDirectory, false, false, false); |
866 | if i3dNode ~= 0 then |
867 | local rootNode = getChildAt(i3dNode, 0); |
868 | link(baseNode, rootNode); |
869 | delete(i3dNode); |
870 | setTranslation(rootNode, 0,0,0); |
871 | local translationNode = getChildAt(rootNode, 0); |
872 | local referenceNode = getChildAt(translationNode, 0); |
873 | |
874 | |
875 | local topArm = {}; |
876 | topArm.rotationNode = rootNode; |
877 | topArm.rotX, topArm.rotY, topArm.rotZ = 0,0,0; |
878 | topArm.translationNode = translationNode; |
879 | |
880 | local _,_,referenceDistance = getTranslation(referenceNode); |
881 | topArm.referenceDistance = referenceDistance; |
882 | |
883 | topArm.zScale = 1; |
884 | local zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName.. ".topArm#zScale"), 1)); |
885 | if zScale < 0 then |
886 | topArm.rotY = math.pi; |
887 | setRotation(rootNode, topArm.rotX, topArm.rotY, topArm.rotZ); |
888 | end |
889 | |
890 | if getNumOfChildren(rootNode) > 1 then |
891 | topArm.scaleNode = getChildAt(rootNode, 1); |
892 | local scaleReferenceNode = getChildAt(topArm.scaleNode, 0); |
893 | local _,_,scaleReferenceDistance = getTranslation(scaleReferenceNode); |
894 | topArm.scaleReferenceDistance = scaleReferenceDistance; |
895 | end |
896 | |
897 | topArm.toggleVisibility = Utils.getNoNil(getXMLBool(xmlFile, baseName.. ".topArm#toggleVisibility"), false); |
898 | if topArm.toggleVisibility then |
899 | setVisibility(topArm.rotationNode, false); |
900 | end; |
901 | |
902 | entry.topArm = topArm; |
903 | end |
904 | end |
905 | else |
906 | local rotationNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".topArm#rotationNode")); |
907 | local translationNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".topArm#translationNode")); |
908 | local referenceNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".topArm#referenceNode")); |
909 | if rotationNode ~= nil then |
910 | local topArm = {}; |
911 | topArm.rotationNode = rotationNode; |
912 | topArm.rotX, topArm.rotY, topArm.rotZ = getRotation(rotationNode); |
913 | if translationNode ~= nil and referenceNode ~= nil then |
914 | topArm.translationNode = translationNode; |
915 | |
916 | local x,y,z = getTranslation(translationNode); |
917 | if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then |
918 | print("Warning: translation of topArm of attacherJoint "..i.." is not 0/0/0 in '"..self.configFileName.."'"); |
919 | end; |
920 | local ax, ay, az = getWorldTranslation(referenceNode); |
921 | local bx, by, bz = getWorldTranslation(translationNode); |
922 | topArm.referenceDistance = Utils.vector3Length(ax-bx, ay-by, az-bz); |
923 | end; |
924 | topArm.zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName.. ".topArm#zScale"), 1)); |
925 | topArm.toggleVisibility = Utils.getNoNil(getXMLBool(xmlFile, baseName.. ".topArm#toggleVisibility"), false); |
926 | if topArm.toggleVisibility then |
927 | setVisibility(topArm.rotationNode, false); |
928 | end; |
929 | |
930 | entry.topArm = topArm; |
931 | end; |
932 | end |
933 | local rotationNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".bottomArm#rotationNode")); |
934 | local translationNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".bottomArm#translationNode")); |
935 | local referenceNode = Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.. ".bottomArm#referenceNode")); |
936 | if rotationNode ~= nil then |
937 | local bottomArm = {}; |
938 | bottomArm.rotationNode = rotationNode; |
939 | bottomArm.rotX, bottomArm.rotY, bottomArm.rotZ = getRotation(rotationNode); |
940 | if translationNode ~= nil and referenceNode ~= nil then |
941 | bottomArm.translationNode = translationNode; |
942 | |
943 | local x,y,z = getTranslation(translationNode); |
944 | if math.abs(x) >= 0.0001 or math.abs(y) >= 0.0001 or math.abs(z) >= 0.0001 then |
945 | print("Warning: translation of bottomArm '"..getName(translationNode).."' of attacherJoint "..i.." is "..math.abs(x) .. "/" .. math.abs(y) .. "/" .. math.abs(z) .. "! Should be 0/0/0! ("..self.configFileName..")"); |
946 | end; |
947 | local ax, ay, az = getWorldTranslation(referenceNode); |
948 | local bx, by, bz = getWorldTranslation(translationNode); |
949 | bottomArm.referenceDistance = Utils.vector3Length(ax-bx, ay-by, az-bz); |
950 | end; |
951 | bottomArm.zScale = Utils.sign(Utils.getNoNil(getXMLFloat(xmlFile, baseName.. ".bottomArm#zScale"), 1)); |
952 | entry.bottomArm = bottomArm; |
953 | end; |
954 | entry.rootNode = Utils.getNoNil(Utils.indexToObject(instance.components, getXMLString(xmlFile, baseName.."#rootNode")), instance.components[1].node); |
955 | entry.jointIndex = 0; |
956 | table.insert(instance.attacherJoints, entry); |
957 | end; |
958 | i = i+1; |
959 | end; |
960 | |
961 | if hasXMLProperty(xmlFile, "vehicle.trailerAttacherJoints") then |
962 | print("Warning: vehicle.trailerAttacherJoints.trailerAttacherJoint are no longer supported. Use vehicle.attacherJoints.attacherJoint with jointType='trailer/trailerLow' instead \n"); |
963 | end; |
964 | |
965 | instance.attachedImplements = {}; |
966 | instance.selectedImplement = nil; |
967 | |
968 | local schemaOverlayFile = getXMLString(xmlFile, "vehicle.schemaOverlay#file"); |
969 | if schemaOverlayFile ~= nil then |
970 | schemaOverlayFile = Utils.getFilename(schemaOverlayFile, self.baseDirectory); |
971 | local width = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#width"), 1) * g_currentMission.vehicleSchemaOverlayScaleX; |
972 | local height = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#height"), 1) * g_currentMission.vehicleSchemaOverlayScaleY; |
973 | local invisibleBorderRight = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#invisibleBorderRight"), 0.05); |
974 | local invisibleBorderLeft = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.schemaOverlay#invisibleBorderLeft"), 0.05); |
975 | local x, y = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.schemaOverlay#attacherJointPosition")); |
976 | local baseX, baseY = Utils.getVectorFromString(getXMLString(xmlFile, "vehicle.schemaOverlay#basePosition")); |
977 | if baseX == nil then |
978 | baseX = x; |
979 | end; |
980 | if baseY == nil then |
981 | baseY = y; |
982 | end; |
983 | if x ~= nil and y ~= nil then |
984 | local overlay = Overlay:new("", schemaOverlayFile, 0,0, width, height); |
985 | |
986 | local attacherJoints = {}; |
987 | local i=0; |
988 | while true do |
989 | local key = string.format("vehicle.schemaOverlay.attacherJoint(%d)", i); |
990 | if not hasXMLProperty(xmlFile, key) then |
991 | break; |
992 | end; |
993 | local x, y = Utils.getVectorFromString(getXMLString(xmlFile, key.."#position")); |
994 | local rotation = math.rad(Utils.getNoNil(getXMLFloat(xmlFile, key.."#rotation"), 0)); |
995 | local invertX = Utils.getNoNil(getXMLBool(xmlFile, key.."#invertX"), false); |
996 | |
997 | attacherJoints[i+1] = {x=x,y=y, rotation=rotation,invertX=invertX}; |
998 | i = i + 1; |
999 | end; |
1000 | local schemaOverlayFileSelected = getXMLString(xmlFile, "vehicle.schemaOverlay#fileSelected"); |
1001 | local overlaySelected = overlay; |
1002 | if schemaOverlayFileSelected ~= nil then |
1003 | schemaOverlayFileSelected = Utils.getFilename(schemaOverlayFileSelected, self.baseDirectory); |
1004 | overlaySelected = Overlay:new("", schemaOverlayFileSelected, 0,0, width, height); |
1005 | end; |
1006 | |
1007 | local schemaOverlayFileTurnedOn = getXMLString(xmlFile, "vehicle.schemaOverlay#fileTurnedOn"); |
1008 | local overlayTurnedOn = overlay; |
1009 | if schemaOverlayFileTurnedOn ~= nil then |
1010 | schemaOverlayFileTurnedOn = Utils.getFilename(schemaOverlayFileTurnedOn, self.baseDirectory); |
1011 | overlayTurnedOn = Overlay:new("", schemaOverlayFileTurnedOn, 0,0, width, height); |
1012 | end; |
1013 | |
1014 | local schemaOverlayFileSelectedTurnedOn = getXMLString(xmlFile, "vehicle.schemaOverlay#fileSelectedTurnedOn"); |
1015 | local overlaySelectedTurnedOn = overlayTurnedOn; |
1016 | if schemaOverlayFileSelectedTurnedOn ~= nil then |
1017 | schemaOverlayFileSelectedTurnedOn = Utils.getFilename(schemaOverlayFileSelectedTurnedOn, self.baseDirectory); |
1018 | overlaySelectedTurnedOn = Overlay:new("", schemaOverlayFileSelectedTurnedOn, 0,0, width, height); |
1019 | end; |
1020 | |
1021 | self.schemaOverlay = {overlay=overlay, overlaySelected=overlaySelected, overlayTurnedOn=overlayTurnedOn, overlaySelectedTurnedOn=overlaySelectedTurnedOn, attacherJoints=attacherJoints, x=x, y=y, baseX=baseX, baseY=baseY, invisibleBorderRight=invisibleBorderRight,invisibleBorderLeft=invisibleBorderLeft}; |
1022 | end; |
1023 | end; |
1024 | |
1025 | self.dynamicallyLoadedParts = {}; |
1026 | local i=0; |
1027 | while true do |
1028 | local baseName = string.format("vehicle.dynamicallyLoadedParts.dynamicallyLoadedPart(%d)", i); |
1029 | if not hasXMLProperty(xmlFile, baseName) then |
1030 | break; |
1031 | end; |
1032 | local dynamicallyLoadedPart = {}; |
1033 | if self:loadDynamicallyPartsFromXML(dynamicallyLoadedPart, xmlFile, baseName) then |
1034 | table.insert(self.dynamicallyLoadedParts, dynamicallyLoadedPart); |
1035 | end; |
1036 | i = i + 1; |
1037 | end; |
1038 | |
1039 | self.speedRotatingParts = {}; |
1040 | local i=0; |
1041 | while true do |
1042 | local baseName = string.format("vehicle.speedRotatingParts.speedRotatingPart(%d)", i); |
1043 | if not hasXMLProperty(xmlFile, baseName) then |
1044 | break; |
1045 | end; |
1046 | local speedRotatingPart = {}; |
1047 | if self:loadSpeedRotatingPartFromXML(speedRotatingPart, xmlFile, baseName) then |
1048 | table.insert(self.speedRotatingParts, speedRotatingPart); |
1049 | end; |
1050 | i = i + 1; |
1051 | end; |
1052 | |
1053 | self.driveGroundParticleSystems = {}; |
1054 | if self.isClient then |
1055 | local i=0; |
1056 | while true do |
1057 | local key = string.format("vehicle.driveGroundParticleSystems.driveGroundParticleSystem(%d)", i); |
1058 | if not hasXMLProperty(xmlFile, key) then |
1059 | break; |
1060 | end; |
1061 | local ps = {}; |
1062 | ps.particleSystems = {}; |
1063 | local psRootNode = Utils.loadParticleSystem(xmlFile, ps.particleSystems, key, self.components, false, nil, self.baseDirectory); |
1064 | if table.getn(ps.particleSystems) > 0 then |
1065 | ps.minSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#minSpeed"), 5)/3600; |
1066 | ps.maxSpeed = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxSpeed"), 50)/3600; |
1067 | ps.minScale = Utils.getNoNil(getXMLFloat(xmlFile, key.."#minScale"), 0.1); |
1068 | ps.maxScale = Utils.getNoNil(getXMLFloat(xmlFile, key.."#maxScale"), 1); |
1069 | ps.direction = Utils.getNoNil(getXMLFloat(xmlFile, key.."#direction"), 0); |
1070 | local wheel = Utils.getNoNil(getXMLInt(xmlFile, key.."#wheel"), 1); |
1071 | if self.wheels[wheel] ~= nil then |
1072 | local wheel = self.wheels[wheel]; |
1073 | ps.offsets = Utils.getVectorNFromString(Utils.getNoNil(getXMLString(xmlFile, key.."#offset"), "0 0 0"), 3); |
1074 | link(wheel.node, psRootNode); |
1075 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)); |
1076 | setTranslation(psRootNode, wx + ps.offsets[1], wy - wheel.radius + ps.offsets[2], wz + ps.offsets[3]); |
1077 | ps.wheel = wheel; |
1078 | ps.rootNode = psRootNode; |
1079 | wheel.driveGroundParticleSystem = ps; |
1080 | else |
1081 | print("Warning: Invalid wheel id '"..tostring(wheel).."' for driveGroundParticleSystems!"); |
1082 | end; |
1083 | ps.onlyActiveOnGroundContact = Utils.getNoNil(getXMLBool(xmlFile, key.."#onlyActiveOnGroundContact"), true); |
1084 | table.insert(self.driveGroundParticleSystems, ps); |
1085 | end; |
1086 | |
1087 | i = i+1; |
1088 | end; |
1089 | end; |
1090 | |
1091 | instance.hasWheelGroundContact = false; |
1092 | |
1093 | |
1094 | instance.requiredDriveMode = 1; |
1095 | instance.steeringAxleAngle = 0; |
1096 | instance.steeringAxleTargetAngle = 0; |
1097 | instance.rotatedTime = 0; |
1098 | instance.firstTimeRun = false; |
1099 | |
1100 | instance.lightsTypesMask = 0; |
1101 | instance.realLightsActive = false; |
1102 | instance.lastPosition = nil; -- = {0,0,0}; |
1103 | instance.lastSpeed = 0; |
1104 | instance.lastSpeedReal = 0; |
1105 | instance.lastMovedDistance = 0; |
1106 | instance.lastSpeedAcceleration = 0; |
1107 | instance.speedDisplayDt = 0; |
1108 | instance.speedDisplayScale = 1; |
1109 | instance.isBroken = false; |
1110 | instance.lastMoveTime = -10000; |
1111 | instance.forcePtoUpdate = true; |
1112 | instance.lastSoundSpeed = 0; |
1113 | instance.tipErrorMessageTime = 0; |
1114 | instance.tipErrorMessage = ""; |
1115 | instance.showDetachingNotAllowedTime = 0; |
1116 | |
1117 | instance.time = 0; |
1118 | |
1119 | instance.forceIsActive = false; |
1120 | |
1121 | self.vehicleDirtyFlag = self:getNextDirtyFlag(); |
1122 | |
1123 | self.componentsVisibility = true; |
1124 | |
1125 | self.ikChains = {}; |
1126 | self.ikChainsById = {}; |
1127 | local i = 0; |
1128 | while true do |
1129 | local key = string.format("vehicle.ikChains.ikChain(%d)", i); |
1130 | if not hasXMLProperty(xmlFile, key) then |
1131 | break; |
1132 | end; |
1133 | IKUtil.loadIKChain(xmlFile, key, self.components, self.components, self.ikChains, self.ikChainsById, self.getParentComponent, self); |
1134 | i = i + 1; |
1135 | end; |
1136 | |
1137 | self.skinnedNodes = {}; |
1138 | local i = 0; |
1139 | while true do |
1140 | local key = string.format("vehicle.skinnedNodes.skinnedNode(%d)", i); |
1141 | if not hasXMLProperty(xmlFile, key) then |
1142 | break; |
1143 | end; |
1144 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
1145 | if node ~= nil then |
1146 | if Utils.getNoNil(getXMLBool(xmlFile, key.."#moveToZero"), false) then |
1147 | setTranslation(node, 0,0,0); |
1148 | setRotation(node, 0,0,0); |
1149 | end |
1150 | table.insert(self.skinnedNodes, node); |
1151 | end; |
1152 | i = i + 1; |
1153 | end; |
1154 | |
1155 | local i = 0; |
1156 | while true do |
1157 | local key = string.format("vehicle.colorNodes.colorNode(%d)", i); |
1158 | if not hasXMLProperty(xmlFile, key) then |
1159 | break; |
1160 | end; |
1161 | local node = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
1162 | if node ~= nil then |
1163 | if self.colorNodes == nil then |
1164 | self.colorNodes = {}; |
1165 | end; |
1166 | table.insert(self.colorNodes, node); |
1167 | end; |
1168 | i = i + 1; |
1169 | end; |
1170 | |
1171 | self.conflictCheckedInputs = {}; |
1172 | |
1173 | for i=1, table.getn(self.specializations) do |
1174 | self.specializations[i].load(self, xmlFile); |
1175 | end; |
1176 | for i=1, table.getn(self.specializations) do |
1177 | if self.specializations[i].postLoad ~= nil then |
1178 | self.specializations[i].postLoad(self, xmlFile); |
1179 | end; |
1180 | end; |
1181 | |
1182 | local speedLimit = math.huge; |
1183 | for i=1, table.getn(self.specializations) do |
1184 | if self.specializations[i].getDefaultSpeedLimit ~= nil then |
1185 | local limit = self.specializations[i].getDefaultSpeedLimit(self); |
1186 | speedLimit = math.min(limit, speedLimit); |
1187 | end; |
1188 | end; |
1189 | |
1190 | instance.checkSpeedLimit = speedLimit ~= math.huge; |
1191 | instance.speedLimit = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.speedLimit#value"), speedLimit); |
1192 | |
1193 | -- relink the skinned nodes after all loading functions such that the indices are not destroyed |
1194 | for _, node in pairs(self.skinnedNodes) do |
1195 | -- get the clip distance |
1196 | local minClipDistance = getClipDistance(node); |
1197 | local curNode = getParent(node); |
1198 | while curNode ~= 0 do |
1199 | minClipDistance = math.min(minClipDistance, getClipDistance(curNode)); |
1200 | curNode = getParent(curNode); |
1201 | end |
1202 | setClipDistance(node, minClipDistance) |
1203 | |
1204 | link(getRootNode(), node); |
1205 | end; |
1206 | |
1207 | -- colorize sparts |
1208 | local color = getXMLString(xmlFile, "vehicle.color#value"); |
1209 | if color ~= nil then |
1210 | local r,g,b,a = Utils.getVectorFromString(color); |
1211 | self:setColor(r,g,b,a); |
1212 | end; |
1213 | |
1214 | delete(xmlFile); |
1215 | |
1216 | if self.applyInitialAnimation ~= nil then |
1217 | self:applyInitialAnimation(); |
1218 | end |
1219 | |
1220 | if asyncCallbackFunction ~= nil then |
1221 | asyncCallbackFunction(asyncCallbackObject, self, asyncCallbackArguments); |
1222 | end; |
1223 | end; |
1224 | |
1225 | function Vehicle:loadPowerTakeoff(xmlFile, baseName, entry) |
1226 | local ptoOutputNode = Utils.indexToObject(self.components, getXMLString(xmlFile, baseName.. "#ptoOutputNode")); |
1227 | if ptoOutputNode ~= nil then |
1228 | local filename = getXMLString(xmlFile, baseName.. "#ptoFilename"); |
1229 | if filename ~= nil then |
1230 | local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false); |
1231 | if i3dNode ~= 0 then |
1232 | local rootNode = getChildAt(i3dNode, 0); |
1233 | unlink(rootNode); |
1234 | delete(i3dNode); |
1235 | setTranslation(rootNode, 0,0,0); |
1236 | local dirAndScaleNode = getChildAt(rootNode, 0); |
1237 | local attachNode = getChildAt(dirAndScaleNode, 0); |
1238 | local attachRefNode = getChildAt(attachNode, 0); |
1239 | |
1240 | local _,_,baseDistance = getTranslation(attachNode); |
1241 | unlink(attachNode); |
1242 | local ax, ay, az = getTranslation(attachRefNode); |
1243 | setTranslation(attachNode, -ax, -ay, -az); |
1244 | |
1245 | setTranslation(rootNode, ax, ay, az); |
1246 | |
1247 | entry.ptoOutput = {node = ptoOutputNode, rootNode=rootNode, dirAndScaleNode=dirAndScaleNode, attachNode=attachNode, baseDistance=baseDistance, rotation=0}; |
1248 | end |
1249 | end |
1250 | end |
1251 | entry.ptoActive = false; |
1252 | end |
1253 | |
1254 | function Vehicle:updateWheelTireFriction(wheel) |
1255 | if self.isServer then |
1256 | setWheelShapeTireFriction(wheel.node, wheel.wheelShape, wheel.maxLongStiffness, wheel.maxLatStiffness, wheel.maxLatStiffnessLoad, wheel.frictionScale*wheel.tireGroundFrictionCoeff); |
1257 | end; |
1258 | end |
1259 | |
1260 | function Vehicle:updateWheelBase(wheel) |
1261 | |
1262 | local positionY = wheel.positionY+wheel.deltaY; |
1263 | |
1264 | if self.isServer then |
1265 | local collisionMask = 255 - 4; -- all up to bit 8, except bit 2 which is set by the players kinematic object |
1266 | wheel.wheelShape = createWheelShape(wheel.node, wheel.positionX, positionY, wheel.positionZ, wheel.radius, wheel.suspTravel, wheel.spring, wheel.damper, wheel.mass, collisionMask, wheel.wheelShape); |
1267 | |
1268 | local forcePointY = positionY - wheel.radius * wheel.forcePointRatio; |
1269 | |
1270 | setWheelShapeForcePoint(wheel.node, wheel.wheelShape, wheel.positionX, forcePointY, wheel.positionZ); |
1271 | end; |
1272 | |
1273 | wheel.netInfo = {}; |
1274 | wheel.netInfo.xDrive = 0; |
1275 | wheel.netInfo.x = wheel.positionX; |
1276 | wheel.netInfo.y = positionY; |
1277 | wheel.netInfo.z = wheel.positionZ; |
1278 | |
1279 | -- The suspension elongates by 20% of the specified susp travel |
1280 | wheel.netInfo.yMin = positionY-1.2*wheel.suspTravel; |
1281 | wheel.netInfo.yRange = 1.2*wheel.suspTravel; |
1282 | if wheel.netInfo.yRange < 0.001 then |
1283 | -- avoid division by 0 |
1284 | wheel.netInfo.yRange = 0.001; |
1285 | end; |
1286 | |
1287 | if wheel.driveGroundParticleSystem ~= nil then |
1288 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)); |
1289 | setTranslation(wheel.driveGroundParticleSystem.rootNode, wx + wheel.driveGroundParticleSystem.offsets[1], wy - wheel.radius + wheel.driveGroundParticleSystem.offsets[2], wz + wheel.driveGroundParticleSystem.offsets[3]); |
1290 | end; |
1291 | end |
1292 | |
1293 | function Vehicle:loadDynamicWheelDataFromXML(xmlFile, wheelnamei, wheel) |
1294 | |
1295 | wheel.showSteeringAngle = Utils.getNoNil(getXMLBool(xmlFile, wheelnamei .. "#showSteeringAngle"), true); |
1296 | wheel.suspTravel = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#suspTravel"), 0.01); |
1297 | local initialCompression = getXMLFloat(xmlFile, wheelnamei .. "#initialCompression"); |
1298 | if initialCompression ~= nil then |
1299 | wheel.deltaY = (1-initialCompression*0.01)*wheel.suspTravel; |
1300 | else |
1301 | wheel.deltaY = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#deltaY"), 0.0); |
1302 | end |
1303 | wheel.spring = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#spring"), 0)*Vehicle.springScale; |
1304 | wheel.damper = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#damper"), 0); |
1305 | wheel.mass = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#mass"), 0.01); |
1306 | wheel.radius = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#radius"), 1); |
1307 | wheel.forcePointRatio = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#forcePointRatio"), 0); |
1308 | |
1309 | wheel.xoffset = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#xoffset"), 0); |
1310 | |
1311 | wheel.driveMode = Utils.getNoNil(getXMLInt(xmlFile, wheelnamei .. "#driveMode"), 0); |
1312 | |
1313 | wheel.steeringAxleScale = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#steeringAxleScale"), 0); |
1314 | wheel.steeringAxleRotMax = Utils.degToRad(Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#steeringAxleRotMax"), 0)); |
1315 | wheel.steeringAxleRotMin = Utils.degToRad(Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#steeringAxleRotMin"), -0)); |
1316 | |
1317 | wheel.restLoad = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#restLoad"), 1.0); -- [t] |
1318 | wheel.maxLongStiffness = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#maxLongStiffness"), 30.0); -- [t / rad] |
1319 | wheel.maxLatStiffness = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#maxLatStiffness"), 40)*wheel.restLoad; -- xml is ratio to restLoad [1/rad], final value is [t / rad] |
1320 | wheel.maxLatStiffnessLoad = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#maxLatStiffnessLoad"), 2)*wheel.restLoad; -- xml is ratio to restLoad, final value is [t] |
1321 | |
1322 | wheel.frictionScale = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#frictionScale"), 1.0); |
1323 | local tireTypeName = Utils.getNoNil(getXMLString(xmlFile, wheelnamei .. "#tireType"), "mud"); |
1324 | wheel.tireType = WheelsUtil.getTireType(tireTypeName); |
1325 | if wheel.tireType == nil then |
1326 | print("Warning: Failed to find tire type '"..tireTypeName.."'. Defaulting to 'mud'."); |
1327 | wheel.tireType = WheelsUtil.getTireType("mud"); |
1328 | end |
1329 | wheel.tireGroundFrictionCoeff = 1.0; -- This will be changed dynamically based on the tire-ground pair |
1330 | |
1331 | wheel.versatileYRot = Utils.getNoNil(getXMLBool(xmlFile, wheelnamei .. "#versatileYRot"), false); |
1332 | wheel.forceVersatility = Utils.getNoNil(getXMLBool(xmlFile, wheelnamei .. "#forceVersatility"), false); |
1333 | |
1334 | --[[wheel.lateralExtremumSlip = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#lateralExtremumSlip"), 0.02); |
1335 | wheel.lateralExtremumValue = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#lateralExtremumValue"), 0.04); |
1336 | wheel.lateralAsymptoteSlip = math.max(Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#lateralAsymptoteSlip"), 6.0), wheel.lateralExtremumSlip*1.05); |
1337 | wheel.lateralAsymptoteValue = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#lateralAsymptoteValue"), 0.03); |
1338 | wheel.lateralStiffness = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#lateralStiffness"), 1); |
1339 | |
1340 | wheel.longitudalExtremumSlip = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#longitudalExtremumSlip"), 0.9); |
1341 | wheel.longitudalExtremumValue = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#longitudalExtremumValue"), 0.02); |
1342 | wheel.longitudalAsymptoteSlip = math.max(Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#longitudalAsymptoteSlip"), 2.0), wheel.longitudalExtremumSlip*1.05); |
1343 | wheel.longitudalAsymptoteValue = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#longitudalAsymptoteValue"), 0.01); |
1344 | wheel.longitudalStiffness = Utils.getNoNil(getXMLFloat(xmlFile, wheelnamei .. "#longitudalStiffness"), 1);]] |
1345 | |
1346 | self:updateWheelBase(wheel); |
1347 | |
1348 | self:updateWheelTireFriction(wheel); |
1349 | |
1350 | |
1351 | end |
1352 | |
1353 | function Vehicle:loadWheelsSteeringDataFromXML(xmlFile) |
1354 | |
1355 | for i, wheel in ipairs(self.wheels) do |
1356 | local wheelnamei = string.format("vehicle.wheels.wheel(%d)", wheel.xmlIndex); |
1357 | wheel.rotSpeed = Utils.degToRad(getXMLFloat(xmlFile, wheelnamei .. "#rotSpeed")); |
1358 | wheel.rotSpeedNeg = Utils.getNoNilRad(getXMLFloat(xmlFile, wheelnamei .. "#rotSpeedNeg"), nil); |
1359 | wheel.rotMax = Utils.degToRad(getXMLFloat(xmlFile, wheelnamei .. "#rotMax")); |
1360 | wheel.rotMin = Utils.degToRad(getXMLFloat(xmlFile, wheelnamei .. "#rotMin")); |
1361 | end |
1362 | |
1363 | local rotSpeed = getXMLFloat(xmlFile, "vehicle.ackermannSteering#rotSpeed"); |
1364 | local rotMax = getXMLFloat(xmlFile, "vehicle.ackermannSteering#rotMax"); |
1365 | local centerX; |
1366 | local centerZ; |
1367 | local rotCenterWheel1 = getXMLInt(xmlFile, "vehicle.ackermannSteering#rotCenterWheel1"); |
1368 | if rotCenterWheel1 ~= nil and self.wheels[rotCenterWheel1+1] ~= nil then |
1369 | centerX = self.wheels[rotCenterWheel1+1].positionX; |
1370 | centerZ = self.wheels[rotCenterWheel1+1].positionZ; |
1371 | local rotCenterWheel2 = getXMLInt(xmlFile, "vehicle.ackermannSteering#rotCenterWheel2"); |
1372 | if rotCenterWheel2 ~= nil and self.wheels[rotCenterWheel2+1] ~= nil then |
1373 | centerX = (centerX+self.wheels[rotCenterWheel2+1].positionX)*0.5; |
1374 | centerZ = (centerZ+self.wheels[rotCenterWheel2+1].positionZ)*0.5; |
1375 | end |
1376 | else |
1377 | local centerNode, rootNode = Utils.indexToObject(self.components, getXMLString(xmlFile, "vehicle.ackermannSteering#rotCenterNode")); |
1378 | if centerNode ~= nil then |
1379 | centerX,_,centerZ = localToLocal(centerNode, rootNode, 0,0,0); |
1380 | else |
1381 | local p = Utils.getVectorNFromString(getXMLString(xmlFile, "vehicle.ackermannSteering#rotCenter", 2)); |
1382 | if p ~= nil then |
1383 | rotCenterValid = true; |
1384 | centerX = p[1]; |
1385 | centerZ = p[2]; |
1386 | end |
1387 | end |
1388 | end |
1389 | |
1390 | if rotSpeed ~= nil and rotMax ~= nil and centerX ~= nil then |
1391 | rotSpeed = math.abs(math.rad(rotSpeed)); |
1392 | rotMax = math.abs(math.rad(rotMax)); |
1393 | |
1394 | -- find the wheel that should get the maximum steering (the one that results in the maximum turnign radius) |
1395 | local maxTurningRadius = 0; |
1396 | local maxTurningRadiusWheel = 0; |
1397 | for i, wheel in ipairs(self.wheels) do |
1398 | if wheel.rotSpeed ~= 0 then |
1399 | local diffX = wheel.positionX-centerX; |
1400 | local diffZ = wheel.positionZ-centerZ; |
1401 | local turningRadius = math.abs(diffZ)/math.tan(rotMax) + math.abs(diffX); |
1402 | if turningRadius >= maxTurningRadius then |
1403 | maxTurningRadius = turningRadius; |
1404 | maxTurningRadiusWheel = i; |
1405 | end |
1406 | end |
1407 | end |
1408 | self.maxRotation = math.max(Utils.getNoNil(self.maxRotation, 0), rotMax); |
1409 | self.maxTurningRadius = maxTurningRadius; |
1410 | self.maxTurningRadiusWheel = maxTurningRadiusWheel; |
1411 | |
1412 | if maxTurningRadiusWheel > 0 then |
1413 | for i, wheel in ipairs(self.wheels) do |
1414 | if wheel.rotSpeed ~= 0 then |
1415 | local diffX = wheel.positionX-centerX; |
1416 | local diffZ = wheel.positionZ-centerZ; |
1417 | local rotMaxI = math.atan(diffZ/(maxTurningRadius-diffX)); |
1418 | local rotMinI = -math.atan(diffZ/(maxTurningRadius+diffX)); |
1419 | |
1420 | |
1421 | local switchMaxMin = rotMinI > rotMaxI |
1422 | if switchMaxMin then |
1423 | rotMaxI, rotMinI = rotMinI, rotMaxI; |
1424 | end |
1425 | |
1426 | wheel.rotMax = rotMaxI; |
1427 | wheel.rotMin = rotMinI; |
1428 | wheel.rotSpeed = rotSpeed*rotMaxI/rotMax; |
1429 | wheel.rotSpeedNeg = -rotSpeed*rotMinI/rotMax; |
1430 | |
1431 | if switchMaxMin then |
1432 | wheel.rotSpeed, wheel.rotSpeedNeg = -wheel.rotSpeedNeg, -wheel.rotSpeed |
1433 | end |
1434 | end |
1435 | end |
1436 | end |
1437 | end |
1438 | |
1439 | for i, wheel in ipairs(self.wheels) do |
1440 | local wheelnamei = string.format("vehicle.wheels.wheel(%d)", wheel.xmlIndex); |
1441 | |
1442 | if wheel.rotSpeed ~= 0 then |
1443 | |
1444 | -- if both speed and rot have the same sign, we can reach it with the positive time |
1445 | if (wheel.rotMax >= 0) == (wheel.rotSpeed >= 0) then |
1446 | self.maxRotTime = math.max(wheel.rotMax/wheel.rotSpeed, self.maxRotTime); |
1447 | end |
1448 | if (wheel.rotMin >= 0) == (wheel.rotSpeed >= 0) then |
1449 | self.maxRotTime = math.max(wheel.rotMin/wheel.rotSpeed, self.maxRotTime); |
1450 | end |
1451 | |
1452 | -- if speed and rot have a different sign, we can reach it with the negative time |
1453 | local rotSpeedNeg = wheel.rotSpeedNeg; |
1454 | if rotSpeedNeg == nil then |
1455 | rotSpeedNeg = wheel.rotSpeed; |
1456 | end |
1457 | if (wheel.rotMax >= 0) ~= (rotSpeedNeg >= 0) then |
1458 | self.minRotTime = math.min(wheel.rotMax/rotSpeedNeg, self.minRotTime); |
1459 | end |
1460 | if (wheel.rotMin >= 0) ~= (rotSpeedNeg >= 0) then |
1461 | self.minRotTime = math.min(wheel.rotMin/rotSpeedNeg, self.minRotTime); |
1462 | end |
1463 | end |
1464 | |
1465 | wheel.fenderNode = Utils.indexToObject(self.components, getXMLString(xmlFile, wheelnamei .. "#fenderNode")); |
1466 | wheel.fenderRotMax = Utils.getNoNilRad(getXMLFloat(xmlFile, wheelnamei .. "#fenderRotMax"), wheel.rotMax); |
1467 | wheel.fenderRotMin = Utils.getNoNilRad(getXMLFloat(xmlFile, wheelnamei .. "#fenderRotMin"), wheel.rotMin); |
1468 | |
1469 | wheel.steeringNode = Utils.indexToObject(self.components, getXMLString(xmlFile, wheelnamei .. "#steeringNode")); |
1470 | wheel.steeringNodeMinTransX = getXMLFloat(xmlFile, wheelnamei .. "#steeringNodeMinTransX"); |
1471 | wheel.steeringNodeMaxTransX = getXMLFloat(xmlFile, wheelnamei .. "#steeringNodeMaxTransX"); |
1472 | wheel.steeringNodeMaxRot = math.max(wheel.rotMax, wheel.steeringAxleRotMax); |
1473 | wheel.steeringNodeMinRot = math.min(wheel.rotMin, wheel.steeringAxleRotMin); |
1474 | end; |
1475 | |
1476 | end |
1477 | |
1478 | function Vehicle:delete() |
1479 | |
1480 | for i=table.getn(self.attachedImplements), 1, -1 do |
1481 | self:detachImplement(1, true); |
1482 | end; |
1483 | |
1484 | for i=table.getn(self.specializations), 1, -1 do |
1485 | if self.specializations[i].preDelete ~= nil then |
1486 | self.specializations[i].preDelete(self); |
1487 | end |
1488 | end; |
1489 | |
1490 | for i=table.getn(self.specializations), 1, -1 do |
1491 | self.specializations[i].delete(self); |
1492 | end; |
1493 | |
1494 | if self.schemaOverlay ~= nil then |
1495 | if self.schemaOverlay.overlaySelectedTurnedOn ~= self.schemaOverlay.overlayTurnedOn then |
1496 | self.schemaOverlay.overlaySelectedTurnedOn:delete(); |
1497 | end |
1498 | if self.schemaOverlay.overlayTurnedOn ~= self.schemaOverlay.overlay then |
1499 | self.schemaOverlay.overlayTurnedOn:delete(); |
1500 | end |
1501 | if self.schemaOverlay.overlaySelected ~= self.schemaOverlay.overlay then |
1502 | self.schemaOverlay.overlaySelected:delete(); |
1503 | end |
1504 | self.schemaOverlay.overlay:delete(); |
1505 | end |
1506 | |
1507 | for _, ps in pairs(self.driveGroundParticleSystems) do |
1508 | Utils.deleteParticleSystem(ps.particleSystems); |
1509 | end; |
1510 | |
1511 | for _, jointDesc in pairs(self.attacherJoints) do |
1512 | if jointDesc.ptoOutput ~= nil then |
1513 | delete(jointDesc.ptoOutput.rootNode); |
1514 | delete(jointDesc.ptoOutput.attachNode); |
1515 | end |
1516 | end |
1517 | |
1518 | if self.isClient then |
1519 | Utils.deleteSample(self.sampleAttach); |
1520 | Utils.deleteSample(self.sampleHydraulic); |
1521 | end; |
1522 | |
1523 | if self.isServer then |
1524 | for _,v in pairs(self.componentJoints) do |
1525 | removeJoint(v.jointIndex); |
1526 | end; |
1527 | end; |
1528 | |
1529 | for _, node in pairs(self.skinnedNodes) do |
1530 | delete(node); |
1531 | end; |
1532 | |
1533 | if g_currentMission.tyreTrackSystem ~= nil then |
1534 | for _,wheel in pairs(self.wheels) do |
1535 | if wheel.tyreTrackIndex ~= nil then |
1536 | g_currentMission.tyreTrackSystem:destroyTrack(wheel.tyreTrackIndex); |
1537 | end |
1538 | end; |
1539 | end; |
1540 | |
1541 | for _, dynamicallyLoadedPart in pairs(self.dynamicallyLoadedParts) do |
1542 | if dynamicallyLoadedPart.filename ~= nil then |
1543 | Utils.releaseSharedI3DFile(dynamicallyLoadedPart.filename, self.baseDirectory, true); |
1544 | end; |
1545 | end; |
1546 | |
1547 | for _, wheel in pairs(self.wheels) do |
1548 | if wheel.wheelFilename ~= nil then |
1549 | Utils.releaseSharedI3DFile(wheel.wheelFilename, self.baseDirectory, true); |
1550 | end; |
1551 | end; |
1552 | |
1553 | for _, beaconLight in pairs(self.beaconLights) do |
1554 | if beaconLight.filename ~= nil then |
1555 | Utils.releaseSharedI3DFile(beaconLight.filename, self.baseDirectory, true); |
1556 | end; |
1557 | end; |
1558 | |
1559 | for _,v in pairs(self.components) do |
1560 | delete(v.node); |
1561 | end; |
1562 | |
1563 | Utils.releaseSharedI3DFile(self.i3dFilename, self.baseDirectory, true); |
1564 | |
1565 | Vehicle:superClass().delete(self); |
1566 | |
1567 | self.isDeleted = true; |
1568 | end; |
1569 | |
1570 | function Vehicle:readStream(streamId, connection) |
1571 | Vehicle:superClass().readStream(self, streamId); |
1572 | local configFile = Utils.convertFromNetworkFilename(streamReadString(streamId)); |
1573 | |
1574 | local typeName = streamReadString(streamId); |
1575 | |
1576 | if self.configFileName == nil then |
1577 | self:load(configFile, 0,0,0,0,typeName); |
1578 | end; |
1579 | for i=1, table.getn(self.components) do |
1580 | local x=streamReadFloat32(streamId); |
1581 | local y=streamReadFloat32(streamId); |
1582 | local z=streamReadFloat32(streamId); |
1583 | local x_rot=streamReadFloat32(streamId) |
1584 | local y_rot=streamReadFloat32(streamId); |
1585 | local z_rot=streamReadFloat32(streamId); |
1586 | local w_rot=streamReadFloat32(streamId); |
1587 | self:setWorldPositionQuaternion(x,y,z, x_rot,y_rot,z_rot,w_rot, i); |
1588 | |
1589 | self.components[i].lastTranslation = {x,y,z}; |
1590 | self.components[i].lastRotation = {x_rot,y_rot,z_rot,w_rot}; |
1591 | self.components[i].targetTranslation = {x,y,z}; |
1592 | self.components[i].targetRotation = {x_rot,y_rot,z_rot,w_rot}; |
1593 | self.components[i].curTranslation = {x,y,z}; |
1594 | self.components[i].curRotation = {x_rot,y_rot,z_rot,w_rot}; |
1595 | end; |
1596 | self.interpolationAlpha = 0; |
1597 | self.positionIsDirty = false; |
1598 | for i=1, table.getn(self.wheels) do |
1599 | local wheel = self.wheels[i]; |
1600 | wheel.netInfo.x = streamReadFloat32(streamId); |
1601 | wheel.netInfo.y = streamReadFloat32(streamId); |
1602 | wheel.netInfo.z = streamReadFloat32(streamId); |
1603 | wheel.netInfo.xDrive = streamReadFloat32(streamId); |
1604 | if wheel.tyreTrackIndex ~= nil then |
1605 | wheel.contact = streamReadUIntN(streamId, 2); |
1606 | end; |
1607 | end; |
1608 | |
1609 | for _, speedRotatingPart in pairs(self.speedRotatingParts) do |
1610 | if speedRotatingPart.versatileYRot then |
1611 | local yRot = streamReadUIntN(streamId, 9); |
1612 | speedRotatingPart.steeringAngle = yRot / 511 * math.pi*2; |
1613 | end; |
1614 | end; |
1615 | |
1616 | local numImplements = streamReadInt8(streamId); |
1617 | for i=1, numImplements do |
1618 | local implementId = streamReadInt32(streamId); |
1619 | local inputJointDescIndex = streamReadInt8(streamId); |
1620 | local jointDescIndex = streamReadInt8(streamId); |
1621 | local moveDown = streamReadBool(streamId); |
1622 | local object = networkGetObject(implementId); |
1623 | if object ~= nil then |
1624 | self:attachImplement(object, inputJointDescIndex, jointDescIndex, true, i); |
1625 | self:setJointMoveDown(jointDescIndex, moveDown, true); |
1626 | end; |
1627 | end; |
1628 | local lightsTypesMask = streamReadInt32(streamId); |
1629 | self:setLightsTypesMask(lightsTypesMask, true); |
1630 | |
1631 | local hasColor = streamReadBool(streamId); |
1632 | if hasColor then |
1633 | local r = streamReadFloat32(streamId); |
1634 | local g = streamReadFloat32(streamId); |
1635 | local b = streamReadFloat32(streamId); |
1636 | local a = streamReadFloat32(streamId); |
1637 | self:setColor(r,g,b,a); |
1638 | end; |
1639 | |
1640 | self.serverMass = streamReadFloat32(streamId); |
1641 | |
1642 | local beaconLightsActive = streamReadBool(streamId); |
1643 | self:setBeaconLightsVisibility(beaconLightsActive, true); |
1644 | |
1645 | local turnSignalState = streamReadUIntN(streamId, Vehicle.turnSignalSendNumBits); |
1646 | self:setTurnSignalState(turnSignalState, true); |
1647 | |
1648 | for k,v in pairs(self.specializations) do |
1649 | if v.readStream ~= nil then |
1650 | v.readStream(self, streamId, connection); |
1651 | end; |
1652 | end; |
1653 | end; |
1654 | |
1655 | function Vehicle:writeStream(streamId, connection) |
1656 | Vehicle:superClass().writeStream(self, streamId); |
1657 | streamWriteString(streamId, Utils.convertToNetworkFilename(self.configFileName)); |
1658 | streamWriteString(streamId, self.typeName); |
1659 | for i=1, table.getn(self.components) do |
1660 | local x,y,z = getTranslation(self.components[i].node); |
1661 | local x_rot,y_rot,z_rot,w_rot=getQuaternion(self.components[i].node); |
1662 | streamWriteFloat32(streamId, x); |
1663 | streamWriteFloat32(streamId, y); |
1664 | streamWriteFloat32(streamId, z); |
1665 | streamWriteFloat32(streamId, x_rot); |
1666 | streamWriteFloat32(streamId, y_rot); |
1667 | streamWriteFloat32(streamId, z_rot); |
1668 | streamWriteFloat32(streamId, w_rot); |
1669 | end; |
1670 | for i=1, table.getn(self.wheels) do |
1671 | local wheel = self.wheels[i]; |
1672 | streamWriteFloat32(streamId, wheel.netInfo.x); |
1673 | streamWriteFloat32(streamId, wheel.netInfo.y); |
1674 | streamWriteFloat32(streamId, wheel.netInfo.z); |
1675 | streamWriteFloat32(streamId, wheel.netInfo.xDrive); |
1676 | if wheel.tyreTrackIndex ~= nil then |
1677 | streamWriteUIntN(streamId, wheel.contact, 2); |
1678 | end; |
1679 | end; |
1680 | |
1681 | for _, speedRotatingPart in pairs(self.speedRotatingParts) do |
1682 | if speedRotatingPart.versatileYRot then |
1683 | streamWriteUIntN(streamId, Utils.clamp(math.floor(speedRotatingPart.steeringAngle / (math.pi*2) * 511), 0, 511), 9); |
1684 | end; |
1685 | end; |
1686 | |
1687 | -- write attached implements |
1688 | streamWriteInt8(streamId, table.getn(self.attachedImplements)); |
1689 | for i=1, table.getn(self.attachedImplements) do |
1690 | local implement = self.attachedImplements[i]; |
1691 | local inputJointDescIndex = implement.object.inputAttacherJointDescIndex; |
1692 | local jointDescIndex = implement.jointDescIndex; |
1693 | local jointDesc = self.attacherJoints[jointDescIndex]; |
1694 | local moveDown = jointDesc.moveDown; |
1695 | streamWriteInt32(streamId, networkGetObjectId(implement.object)); |
1696 | streamWriteInt8(streamId, inputJointDescIndex); |
1697 | streamWriteInt8(streamId, jointDescIndex); |
1698 | streamWriteBool(streamId, moveDown); |
1699 | end; |
1700 | streamWriteInt32(streamId, self.lightsTypesMask); |
1701 | |
1702 | streamWriteBool(streamId, self.color ~= nil); |
1703 | if self.color ~= nil then |
1704 | streamWriteFloat32(streamId, self.color[1]); |
1705 | streamWriteFloat32(streamId, self.color[2]); |
1706 | streamWriteFloat32(streamId, self.color[3]); |
1707 | streamWriteFloat32(streamId, self.color[4]); |
1708 | end; |
1709 | |
1710 | streamWriteFloat32(streamId, self:getTotalMass()); |
1711 | |
1712 | streamWriteBool(streamId, self.beaconLightsActive); |
1713 | streamWriteUIntN(streamId, self.turnSignalState, Vehicle.turnSignalSendNumBits); |
1714 | |
1715 | for k,v in pairs(self.specializations) do |
1716 | if v.writeStream ~= nil then |
1717 | v.writeStream(self, streamId, connection); |
1718 | end; |
1719 | end; |
1720 | end; |
1721 | |
1722 | function Vehicle:readUpdateStream(streamId, timestamp, connection) |
1723 | if connection.isServer then |
1724 | local hasUpdate = streamReadBool(streamId); |
1725 | if hasUpdate then |
1726 | |
1727 | -- We need to use g_packetPhysicsNetworkTime instead of timestamp, because we need to use the exact same time stepping as we used when simulating |
1728 | -- Due to the async physics simulation, we always see the results of the dt of one frame behind, so physicsDt ~= dt |
1729 | |
1730 | -- Interpolate to target from currently interpolated position |
1731 | |
1732 | local deltaTime = g_client.tickDuration; |
1733 | if self.lastPhysicsNetworkTime ~= nil then |
1734 | deltaTime = math.min(g_packetPhysicsNetworkTime - self.lastPhysicsNetworkTime, 3*g_client.tickDuration); |
1735 | end |
1736 | self.lastPhysicsNetworkTime = g_packetPhysicsNetworkTime; |
1737 | |
1738 | -- interpTimeLeft is the time we would've continued interpolating. This should always be about g_clientInterpDelay. |
1739 | -- If we extrapolated, reset to the full delay |
1740 | local interpTimeLeft = g_clientInterpDelay; |
1741 | if self.interpolationAlpha < 1 then |
1742 | interpTimeLeft = (1-self.interpolationAlpha) * self.interpolationDuration; |
1743 | interpTimeLeft = interpTimeLeft * 0.95 + g_clientInterpDelay * 0.05; -- slighly blend towards the expected delay |
1744 | interpTimeLeft = math.min(interpTimeLeft, 3*g_clientInterpDelay); -- clamp to some reasonable maximum |
1745 | end |
1746 | self.interpolationDuration = interpTimeLeft + deltaTime; |
1747 | self.interpolationAlpha = 0; |
1748 | |
1749 | local paramsXZ = g_currentMission.vehicleXZPosCompressionParams; |
1750 | local paramsY = g_currentMission.vehicleYPosCompressionParams; |
1751 | for i=1, table.getn(self.components) do |
1752 | local x = Utils.readCompressedWorldPosition(streamId, paramsXZ); |
1753 | local y = Utils.readCompressedWorldPosition(streamId, paramsY); |
1754 | local z = Utils.readCompressedWorldPosition(streamId, paramsXZ); |
1755 | local x_rot = Utils.readCompressedAngle(streamId); |
1756 | local y_rot = Utils.readCompressedAngle(streamId); |
1757 | local z_rot = Utils.readCompressedAngle(streamId); |
1758 | |
1759 | local x_rot,y_rot,z_rot,w_rot = mathEulerToQuaternion(x_rot,y_rot,z_rot); |
1760 | local targetTranslation = self.components[i].targetTranslation; |
1761 | local targetRotation = self.components[i].targetRotation; |
1762 | targetTranslation[1] = x; |
1763 | targetTranslation[2] = y; |
1764 | targetTranslation[3] = z; |
1765 | targetRotation[1] = x_rot; targetRotation[2] = y_rot; targetRotation[3] = z_rot; targetRotation[4] = w_rot; |
1766 | |
1767 | local trans = self.components[i].curTranslation; |
1768 | local rot = self.components[i].curRotation; |
1769 | local lastTranslation = self.components[i].lastTranslation; |
1770 | local lastRotation = self.components[i].lastRotation; |
1771 | lastTranslation[1] = trans[1]; |
1772 | lastTranslation[2] = trans[2]; |
1773 | lastTranslation[3] = trans[3]; |
1774 | lastRotation[1] = rot[1]; lastRotation[2] = rot[2]; lastRotation[3] = rot[3]; lastRotation[4] = rot[4]; |
1775 | end; |
1776 | self.positionIsDirty = true; |
1777 | |
1778 | -- read netinfo |
1779 | for i=1, table.getn(self.wheels) do |
1780 | local wheel = self.wheels[i]; |
1781 | if wheel.isSynchronized then |
1782 | local y = streamReadUIntN(streamId, 6); |
1783 | wheel.netInfo.y = y / 63 * wheel.netInfo.yRange + wheel.netInfo.yMin; |
1784 | local xDrive = streamReadUIntN(streamId, 9); |
1785 | wheel.netInfo.xDrive = xDrive / 511 * math.pi*2; |
1786 | if wheel.tyreTrackIndex ~= nil then |
1787 | local contact = streamReadUIntN(streamId, 2) |
1788 | wheel.contact = contact; |
1789 | end; |
1790 | end; |
1791 | end; |
1792 | local rotatedTimeRange = math.max(self.maxRotTime - self.minRotTime, 0.001); |
1793 | local rotatedTime = streamReadUIntN(streamId, 6); |
1794 | self.rotatedTime = rotatedTime / 63 * rotatedTimeRange + self.minRotTime; |
1795 | -- set to 0 due to inaccuracy |
1796 | if math.abs(self.rotatedTime) < 0.02 then |
1797 | self.rotatedTime = 0; |
1798 | end; |
1799 | self.hasWheelGroundContact = streamReadBool(streamId); |
1800 | |
1801 | for _, speedRotatingPart in pairs(self.speedRotatingParts) do |
1802 | if speedRotatingPart.versatileYRot then |
1803 | local yRot = streamReadUIntN(streamId, 9); |
1804 | speedRotatingPart.steeringAngle = yRot / 511 * math.pi*2; |
1805 | end; |
1806 | end; |
1807 | end; |
1808 | end; |
1809 | |
1810 | for _,v in pairs(self.specializations) do |
1811 | if v.readUpdateStream ~= nil then |
1812 | v.readUpdateStream(self, streamId, timestamp, connection); |
1813 | end; |
1814 | end; |
1815 | end; |
1816 | |
1817 | function Vehicle:writeUpdateStream(streamId, connection, dirtyMask) |
1818 | if not connection.isServer then |
1819 | if streamWriteBool(streamId, bitAND(dirtyMask, self.vehicleDirtyFlag) ~= 0) then |
1820 | |
1821 | local paramsXZ = g_currentMission.vehicleXZPosCompressionParams; |
1822 | local paramsY = g_currentMission.vehicleYPosCompressionParams; |
1823 | for i=1, table.getn(self.components) do |
1824 | local x,y,z=getTranslation(self.components[i].node) |
1825 | --local x_rot,y_rot,z_rot,w_rot=getQuaternion(self.components[i].node); |
1826 | local x_rot,y_rot,z_rot = getRotation(self.components[i].node); |
1827 | |
1828 | Utils.writeCompressedWorldPosition(streamId, x, paramsXZ); |
1829 | Utils.writeCompressedWorldPosition(streamId, y, paramsY); |
1830 | Utils.writeCompressedWorldPosition(streamId, z, paramsXZ); |
1831 | Utils.writeCompressedAngle(streamId, x_rot); |
1832 | Utils.writeCompressedAngle(streamId, y_rot); |
1833 | Utils.writeCompressedAngle(streamId, z_rot); |
1834 | end; |
1835 | for i=1, table.getn(self.wheels) do |
1836 | local wheel = self.wheels[i]; |
1837 | if wheel.isSynchronized then |
1838 | streamWriteUIntN(streamId, Utils.clamp(math.floor((wheel.netInfo.y - wheel.netInfo.yMin) / wheel.netInfo.yRange * 63), 0, 63), 6); |
1839 | local xDrive = wheel.netInfo.xDrive % (math.pi*2); |
1840 | streamWriteUIntN(streamId, Utils.clamp(math.floor(xDrive / (math.pi*2) * 511), 0, 511), 9); |
1841 | if wheel.tyreTrackIndex ~= nil then |
1842 | streamWriteUIntN(streamId, wheel.contact, 2); |
1843 | end; |
1844 | end; |
1845 | end; |
1846 | local rotatedTimeRange = math.max(self.maxRotTime - self.minRotTime, 0.001); |
1847 | local rotatedTime = Utils.clamp(math.floor((self.rotatedTime - self.minRotTime)/rotatedTimeRange * 63), 0, 63); |
1848 | streamWriteUIntN(streamId, rotatedTime, 6); |
1849 | streamWriteBool(streamId, self.hasWheelGroundContact); |
1850 | |
1851 | for _, speedRotatingPart in pairs(self.speedRotatingParts) do |
1852 | if speedRotatingPart.versatileYRot then |
1853 | streamWriteUIntN(streamId, Utils.clamp(math.floor(speedRotatingPart.steeringAngle / (math.pi*2) * 511), 0, 511), 9); |
1854 | end; |
1855 | end; |
1856 | end; |
1857 | end; |
1858 | |
1859 | for _,v in pairs(self.specializations) do |
1860 | if v.writeUpdateStream ~= nil then |
1861 | v.writeUpdateStream(self, streamId, connection, dirtyMask); |
1862 | end; |
1863 | end; |
1864 | end; |
1865 | |
1866 | function Vehicle:testScope(x,y,z, coeff) |
1867 | local vx,vy,vz = getTranslation(self.components[1].node); |
1868 | local distanceSq = Utils.vector3LengthSq(vx-x, vy-y, vz-z); |
1869 | for k,v in pairs(self.specializations) do |
1870 | if v.testScope ~= nil then |
1871 | if not v.testScope(self, x,y,z, coeff, distanceSq) then |
1872 | return false; |
1873 | end; |
1874 | end; |
1875 | end; |
1876 | --return distance < 10; |
1877 | return true; |
1878 | end; |
1879 | |
1880 | function Vehicle:getUpdatePriority(skipCount, x, y, z, coeff, connection) |
1881 | if self:getOwner() == connection then |
1882 | return 50; |
1883 | end; |
1884 | local x1, y1, z1 = getTranslation(self.components[1].node); |
1885 | local dist = Utils.vector3Length(x1-x, y1-y, z1-z); |
1886 | local clipDist = getClipDistance(self.components[1].node)*coeff; |
1887 | return (1-dist/clipDist)* 0.8 + 0.5*skipCount * 0.2; |
1888 | end; |
1889 | |
1890 | function Vehicle:onGhostRemove() |
1891 | --print("remove ghost "..getName(self.components[1].node)); |
1892 | --setVisibility(self.components[1].node, false); |
1893 | for k,v in pairs(self.specializations) do |
1894 | if v.onGhostRemove ~= nil then |
1895 | v.onGhostRemove(self); |
1896 | end; |
1897 | end; |
1898 | end; |
1899 | |
1900 | function Vehicle:onGhostAdd() |
1901 | --print("add ghost "..getName(self.components[1].node)); |
1902 | --setVisibility(self.components[1].node, true); |
1903 | for k,v in pairs(self.specializations) do |
1904 | if v.onGhostAdd ~= nil then |
1905 | v.onGhostAdd(self); |
1906 | end; |
1907 | end; |
1908 | end; |
1909 | |
1910 | function Vehicle:loadFromAttributesAndNodes(xmlFile, key, resetVehicles) |
1911 | |
1912 | self.tourId = nil; |
1913 | local tourId = getXMLString(xmlFile, key.."#tourId"); |
1914 | if tourId ~= nil then |
1915 | self.tourId = tourId; |
1916 | if g_currentMission ~= nil then |
1917 | g_currentMission.tourVehicles[self.tourId] = self; |
1918 | end; |
1919 | end; |
1920 | |
1921 | local color = getXMLString(xmlFile, key.."#color"); |
1922 | if color ~= nil then |
1923 | local color = Utils.getVectorNFromString(color, 4); |
1924 | self:setColor(color[1], color[2], color[3], color[4]); |
1925 | end; |
1926 | |
1927 | local findPlace = resetVehicles; |
1928 | if not findPlace then |
1929 | local isAbsolute = Utils.getNoNil(getXMLBool(xmlFile, key.."#isAbsolute"), false); |
1930 | if isAbsolute then |
1931 | local pos = {}; |
1932 | for i=1, table.getn(self.components) do |
1933 | local componentKey = key..".component"..i; |
1934 | local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, componentKey.."#position")); |
1935 | local xRot,yRot,zRot = Utils.getVectorFromString(getXMLString(xmlFile, componentKey.."#rotation")); |
1936 | if x == nil or y == nil or z == nil or xRot == nil or yRot == nil or zRot == nil then |
1937 | findPlace = true; |
1938 | break; |
1939 | end; |
1940 | pos[i] = {x=x, y=y, z=z, xRot=xRot, yRot=yRot, zRot=zRot}; |
1941 | end; |
1942 | if not findPlace then |
1943 | for i=1, table.getn(self.components) do |
1944 | local p = pos[i]; |
1945 | self:setWorldPosition(p.x,p.y,p.z, p.xRot,p.yRot,p.zRot, i); |
1946 | end; |
1947 | end; |
1948 | else |
1949 | local yOffset = getXMLFloat(xmlFile, key.."#yOffset"); |
1950 | local xPosition = getXMLFloat(xmlFile, key.."#xPosition"); |
1951 | local zPosition = getXMLFloat(xmlFile, key.."#zPosition"); |
1952 | local yRotation = getXMLFloat(xmlFile, key.."#yRotation"); |
1953 | if yOffset == nil or xPosition == nil or zPosition == nil or yRotation == nil then |
1954 | findPlace = true; |
1955 | else |
1956 | self:setRelativePosition(xPosition, yOffset, zPosition, math.rad(yRotation)); |
1957 | end; |
1958 | end; |
1959 | end; |
1960 | if findPlace then |
1961 | if resetVehicles then |
1962 | local x, y, z, place, width, offset = PlacementUtil.getPlace(g_currentMission.loadSpawnPlaces, self.sizeWidth, self.sizeLength, self.widthOffset, self.lengthOffset, g_currentMission.usedLoadPlaces); |
1963 | if x ~= nil then |
1964 | local yRot = Utils.getYRotationFromDirection(place.dirPerpX, place.dirPerpZ); |
1965 | PlacementUtil.markPlaceUsed(g_currentMission.usedLoadPlaces, place, width); |
1966 | self:setRelativePosition(x, offset, z, yRot); |
1967 | else |
1968 | return BaseMission.VEHICLE_LOAD_ERROR; |
1969 | end; |
1970 | else |
1971 | return BaseMission.VEHICLE_LOAD_DELAYED; |
1972 | end; |
1973 | end; |
1974 | for k,v in pairs(self.specializations) do |
1975 | if v.loadFromAttributesAndNodes ~= nil then |
1976 | local r = v.loadFromAttributesAndNodes(self, xmlFile, key, resetVehicles) |
1977 | if r ~= BaseMission.VEHICLE_LOAD_OK then |
1978 | return r; |
1979 | end; |
1980 | end; |
1981 | end; |
1982 | |
1983 | return BaseMission.VEHICLE_LOAD_OK; |
1984 | end; |
1985 | |
1986 | function Vehicle:getSaveAttributesAndNodes(nodeIdent, usedModNames) |
1987 | |
1988 | local attributes = 'isAbsolute="true"'; |
1989 | |
1990 | if self.tourId ~= nil then |
1991 | attributes = attributes .. ' tourId="' .. self.tourId .. '"'; |
1992 | end; |
1993 | |
1994 | if self.color ~= nil then |
1995 | attributes = attributes .. ' color="' .. self.color[1] .. ' '.. self.color[2] ..' ' .. self.color[3] ..' ' .. self.color[4] ..'"'; |
1996 | end; |
1997 | |
1998 | local nodes = ""; |
1999 | if not self.isBroken then |
2000 | for i=1, table.getn(self.components) do |
2001 | if i>1 then |
2002 | nodes = nodes.."\n"; |
2003 | end; |
2004 | local node = self.components[i].node; |
2005 | local x,y,z = getTranslation(node); |
2006 | local xRot,yRot,zRot = getRotation(node); |
2007 | nodes = nodes.. nodeIdent..'<component'..i..' position="'..x..' '..y..' '..z..'" rotation="'..xRot..' '..yRot..' '..zRot..'" />'; |
2008 | end; |
2009 | end; |
2010 | for k,v in pairs(self.specializations) do |
2011 | if v.getSaveAttributesAndNodes ~= nil then |
2012 | local specAttributes, specNodes = v.getSaveAttributesAndNodes(self, nodeIdent, usedModNames); |
2013 | if specAttributes ~= nil and specAttributes ~= "" then |
2014 | attributes = attributes.." "..specAttributes; |
2015 | end; |
2016 | if specNodes ~= nil and specNodes ~= "" then |
2017 | nodes = nodes.."\n"..specNodes; |
2018 | end; |
2019 | end; |
2020 | end; |
2021 | return attributes, nodes; |
2022 | end; |
2023 | |
2024 | function Vehicle:getXMLStatsAttributes() |
2025 | if self.isDeleted or not self.isVehicleSaved or self.nonTabbable then |
2026 | return nil; |
2027 | end |
2028 | local name = "Unknown"; |
2029 | local category = "unknown"; |
2030 | local storeItem = StoreItemsUtil.storeItemsByXMLFilename[self.configFileName:lower()]; |
2031 | if storeItem ~= nil then |
2032 | if storeItem.name ~= nil then |
2033 | name = tostring(storeItem.name); |
2034 | end |
2035 | if storeItem.category ~= nil and storeItem.category ~= "" then |
2036 | category = tostring(storeItem.category); |
2037 | end |
2038 | end |
2039 | local attrString = ""; |
2040 | if self.components[1] ~= nil and self.components[1].node ~= 0 then |
2041 | local x,y,z = getWorldTranslation(self.components[1].node); |
2042 | attrString = attrString..string.format(' x="%.3f" y="%.3f" z="%.3f"', x,y,z); |
2043 | end |
2044 | |
2045 | local attributes = string.format('name="%s" category="%s" type="%s"%s', Utils.encodeToHTML(name), Utils.encodeToHTML(category), Utils.encodeToHTML(tostring(self.typeName)), attrString); |
2046 | for _,v in pairs(self.specializations) do |
2047 | if v.getXMLStatsAttributes ~= nil then |
2048 | local specAttributes = v.getXMLStatsAttributes(self); |
2049 | if specAttributes ~= nil and specAttributes ~= "" then |
2050 | attributes = attributes.." "..specAttributes; |
2051 | end; |
2052 | end; |
2053 | end; |
2054 | return attributes; |
2055 | end; |
2056 | |
2057 | function Vehicle:loadAttachmentFromNodes(xmlFile, key, idsToVehicle) |
2058 | local id1 = getXMLString(xmlFile, key.."#id1"); |
2059 | local jointIndex = getXMLInt(xmlFile, key.."#jointIndex"); |
2060 | local inputJointDescIndex = Utils.getNoNil(getXMLInt(xmlFile, key.."#inputJointDescIndex"), 1); |
2061 | if id1 ~= nil and jointIndex ~= nil then |
2062 | local vehicle1 = idsToVehicle[id1]; |
2063 | |
2064 | if vehicle1 ~= nil and vehicle1.inputAttacherJoints[inputJointDescIndex] ~= nil and self.attacherJoints[jointIndex] ~= nil and self.attacherJoints[jointIndex].jointIndex == 0 then |
2065 | self:attachImplement(vehicle1, inputJointDescIndex, jointIndex, true); |
2066 | |
2067 | local moveDown = getXMLBool(xmlFile, key.."#moveDown"); |
2068 | if moveDown ~= nil then |
2069 | self:setJointMoveDown(jointIndex, moveDown, true); |
2070 | end |
2071 | end |
2072 | end |
2073 | |
2074 | for _,v in ipairs(self.specializations) do |
2075 | if v.loadAttachmentFromNodes ~= nil then |
2076 | v.loadAttachmentFromNodes(self, xmlFile, key, idToVehicles); |
2077 | end; |
2078 | end; |
2079 | end |
2080 | |
2081 | function Vehicle:getAttachmentsSaveNodes(nodeIdent, vehiclesToId) |
2082 | local nodes = ""; |
2083 | local id = vehiclesToId[self]; |
2084 | if id ~= nil then |
2085 | for i=1, table.getn(self.attachedImplements) do |
2086 | local implement = self.attachedImplements[i]; |
2087 | local object = implement.object; |
2088 | if object ~= nil and vehiclesToId[object] ~= nil then |
2089 | local jointDescIndex = implement.jointDescIndex; |
2090 | local jointDesc = self.attacherJoints[jointDescIndex]; |
2091 | local inputJointDescIndex = implement.object.inputAttacherJointDescIndex; |
2092 | local moveDown = jointDesc.moveDown; |
2093 | nodes = nodes .. nodeIdent .. '<attachment id0="'..id..'" id1="'..vehiclesToId[object]..'" inputJointDescIndex="'..inputJointDescIndex..'" jointIndex="'..jointDescIndex..'" moveDown="'..tostring(moveDown)..'"'; |
2094 | |
2095 | local subNodes = ""; |
2096 | for _,v in ipairs(self.specializations) do |
2097 | if v.getAttachmentSaveAttributesAndNodes ~= nil then |
2098 | local specAttributes, specNodes = v.getAttachmentSaveAttributesAndNodes(self, implement, nodeIdent, vehiclesToId); |
2099 | if specAttributes ~= nil and specAttributes ~= "" then |
2100 | nodes = nodes.." "..specAttributes; |
2101 | end; |
2102 | if specNodes ~= nil and specNodes ~= "" then |
2103 | subNodes = subNodes..specNodes.."\n"; |
2104 | end; |
2105 | end; |
2106 | end; |
2107 | if subNodes ~= "" then |
2108 | nodes = nodes..">\n"..subNodes.."</attachment>\n"; |
2109 | else |
2110 | nodes = nodes.."/>\n"; |
2111 | end |
2112 | end |
2113 | end; |
2114 | end |
2115 | return nodes; |
2116 | end |
2117 | |
2118 | function Vehicle:setRelativePosition(positionX, offsetY, positionZ, yRot) |
2119 | local tempRootNode = createTransformGroup("tempRootNode"); |
2120 | |
2121 | local numComponents = table.getn(self.components); |
2122 | |
2123 | for i=1, numComponents do |
2124 | link(tempRootNode, self.components[i].node); |
2125 | setTranslation(self.components[i].node, unpack(self.components[i].originalTranslation)); |
2126 | setRotation(self.components[i].node, unpack(self.components[i].originalRotation)); |
2127 | end; |
2128 | |
2129 | -- position the vehicle |
2130 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, positionX, 300, positionZ); |
2131 | |
2132 | setTranslation(tempRootNode, positionX, terrainHeight+offsetY, positionZ); |
2133 | setRotation(tempRootNode, 0, yRot, 0); |
2134 | |
2135 | -- now move the objects to the scene root node |
2136 | for i=1, numComponents do |
2137 | local x,y,z = getWorldTranslation(self.components[i].node); |
2138 | local rx,ry,rz = getWorldRotation(self.components[i].node); |
2139 | local qx, qy, qz, qw = getWorldQuaternion(self.components[i].node); |
2140 | setTranslation(self.components[i].node, x,y,z); |
2141 | setRotation(self.components[i].node, rx,ry,rz); |
2142 | link(getRootNode(), self.components[i].node); |
2143 | |
2144 | self.components[i].lastTranslation = {x,y,z}; |
2145 | self.components[i].lastRotation = {qx,qy,qz,qw}; |
2146 | self.components[i].targetTranslation = {x,y,z}; |
2147 | self.components[i].targetRotation = {qx,qy,qz,qw}; |
2148 | self.components[i].curTranslation = {x,y,z}; |
2149 | self.components[i].curRotation = {qx,qy,qz,qw}; |
2150 | end; |
2151 | self.interpolationAlpha = 2; |
2152 | self.positionIsDirty = false; |
2153 | delete(tempRootNode); |
2154 | |
2155 | for k,v in pairs(self.specializations) do |
2156 | if v.setRelativePosition ~= nil then |
2157 | v.setRelativePosition(self, positionX, offsetY, positionZ, yRot); |
2158 | end; |
2159 | end; |
2160 | end; |
2161 | |
2162 | function Vehicle:setWorldPosition(x,y,z, xRot,yRot,zRot, i) |
2163 | setTranslation(self.components[i].node, x,y,z); |
2164 | setRotation(self.components[i].node, xRot,yRot,zRot); |
2165 | end; |
2166 | |
2167 | function Vehicle:setWorldPositionQuaternion(x,y,z, xRot,yRot,zRot,wRot, i) |
2168 | setTranslation(self.components[i].node, x,y,z); |
2169 | setQuaternion(self.components[i].node, xRot,yRot,zRot,wRot); |
2170 | end; |
2171 | |
2172 | function Vehicle:addNodeVehicleMapping(list) |
2173 | for k,v in pairs(self.components) do |
2174 | list[v.node] = self; |
2175 | end; |
2176 | |
2177 | for k,v in pairs(self.specializations) do |
2178 | if v.addNodeVehicleMapping ~= nil then |
2179 | v.addNodeVehicleMapping(self, list); |
2180 | end; |
2181 | end; |
2182 | end; |
2183 | |
2184 | function Vehicle:removeNodeVehicleMapping(list) |
2185 | for k,v in pairs(self.components) do |
2186 | list[v.node] = nil; |
2187 | end; |
2188 | |
2189 | for k,v in pairs(self.specializations) do |
2190 | if v.removeNodeVehicleMapping ~= nil then |
2191 | v.removeNodeVehicleMapping(self, list); |
2192 | end; |
2193 | end; |
2194 | end; |
2195 | |
2196 | function Vehicle:mouseEvent(posX, posY, isDown, isUp, button) |
2197 | |
2198 | if self.isEntered or self.selectedImplement == nil then |
2199 | for k,v in pairs(self.specializations) do |
2200 | v.mouseEvent(self, posX, posY, isDown, isUp, button); |
2201 | end; |
2202 | end; |
2203 | |
2204 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
2205 | self.selectedImplement.object:mouseEvent(posX, posY, isDown, isUp, button); |
2206 | end; |
2207 | end; |
2208 | |
2209 | function Vehicle:keyEvent(unicode, sym, modifier, isDown) |
2210 | |
2211 | if self.isEntered or self.selectedImplement == nil then |
2212 | for k,v in pairs(self.specializations) do |
2213 | v.keyEvent(self, unicode, sym, modifier, isDown); |
2214 | end; |
2215 | end; |
2216 | |
2217 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
2218 | self.selectedImplement.object:keyEvent(unicode, sym, modifier, isDown); |
2219 | end; |
2220 | end; |
2221 | |
2222 | function Vehicle:update(dt) |
2223 | |
2224 | if not self.isServer and self.positionIsDirty then |
2225 | local maxIntrpAlpha = 1.2; |
2226 | local interpolationAlpha = self.interpolationAlpha + g_physicsDtUnclamped / self.interpolationDuration; |
2227 | --local interpolationAlpha = math.max((g_physicsNetworkTime-g_clientInterpDelay - self.lastPoseTime) / (self.targetPoseTime - self.lastPoseTime), 0); |
2228 | --self.curPoseTime = g_physicsNetworkTime-g_clientInterpDelay; |
2229 | --[[local index1 = math.max(table.getn(self.networkPosesTimes)-1, 1); |
2230 | local index2 = table.getn(self.networkPosesTimes); |
2231 | for i=1, table.getn(self.networkPosesTimes) do |
2232 | if self.networkPosesTimes[i] >= g_physicsNetworkTime-g_clientInterpDelay then |
2233 | index2 = i; |
2234 | index1 = math.max(i-1, 1); |
2235 | break; |
2236 | end |
2237 | end |
2238 | if index1 ~= index2 then |
2239 | self.interpolationAlpha = math.max((g_physicsNetworkTime-g_clientInterpDelay - self.networkPosesTimes[index1]) / (self.networkPosesTimes[index2] - self.networkPosesTimes[index1]), 0); |
2240 | else |
2241 | self.interpolationAlpha = maxIntrpAlpha; |
2242 | end |
2243 | if self.isEntered then |
2244 | local component = self.components[1]; |
2245 | local dist = Utils.vector3Length(component.networkPoses[index2][1]-component.networkPoses[index1][1], component.networkPoses[index2][2]-component.networkPoses[index1][2], component.networkPoses[index2][3]-component.networkPoses[index1][3]); |
2246 | local dt = (self.networkPosesTimes[index2] - self.networkPosesTimes[index1]); |
2247 | print(string.format("Interp %.2f: %d@%.2fms -> %d@%.2fms: %.2f speed: %.2f in %.2fms", g_physicsNetworkTime-g_clientInterpDelay, index1, self.networkPosesTimes[index1], index2, self.networkPosesTimes[index2], self.interpolationAlpha, dist/(dt*0.001), dt)); |
2248 | end]] |
2249 | if interpolationAlpha >= maxIntrpAlpha then |
2250 | interpolationAlpha = maxIntrpAlpha; |
2251 | self.positionIsDirty = false; |
2252 | end; |
2253 | self.interpolationAlpha = interpolationAlpha; |
2254 | for i=1, table.getn(self.components) do |
2255 | local lastTranslation = self.components[i].lastTranslation; |
2256 | local targetTranslation = self.components[i].targetTranslation; |
2257 | local curTranslation = self.components[i].curTranslation; |
2258 | curTranslation[1] = lastTranslation[1] + interpolationAlpha * (targetTranslation[1] - lastTranslation[1]); |
2259 | curTranslation[2] = lastTranslation[2] + interpolationAlpha * (targetTranslation[2] - lastTranslation[2]); |
2260 | curTranslation[3] = lastTranslation[3] + interpolationAlpha * (targetTranslation[3] - lastTranslation[3]); |
2261 | local rot1 = self.components[i].lastRotation; |
2262 | local rot2 = self.components[i].targetRotation; |
2263 | local x,y,z,w = Utils.nlerpQuaternionShortestPath(rot1[1], rot1[2], rot1[3], rot1[4], rot2[1], rot2[2], rot2[3], rot2[4], interpolationAlpha); |
2264 | --[[local lastPose = self.components[i].networkPoses[index1]; |
2265 | local targetPose = self.components[i].networkPoses[index2]; |
2266 | local curTranslation = self.components[i].curTranslation; |
2267 | curTranslation[1] = lastPose[1] + interpolationAlpha * (targetPose[1] - lastPose[1]); |
2268 | curTranslation[2] = lastPose[2] + interpolationAlpha * (targetPose[2] - lastPose[2]); |
2269 | curTranslation[3] = lastPose[3] + interpolationAlpha * (targetPose[3] - lastPose[3]); |
2270 | --local rot1 = self.components[i].lastRotation; |
2271 | --local rot2 = self.components[i].targetRotation; |
2272 | local x,y,z,w = Utils.nlerpQuaternionShortestPath(lastPose[4], lastPose[5], lastPose[6], lastPose[7], targetPose[4], targetPose[5], targetPose[6], targetPose[7], interpolationAlpha);]] |
2273 | local curRotation = self.components[i].curRotation; |
2274 | curRotation[1] = x; curRotation[2] = y; curRotation[3] = z; curRotation[4] = w; |
2275 | self:setWorldPositionQuaternion(curTranslation[1], curTranslation[2], curTranslation[3], x,y,z,w, i); |
2276 | end; |
2277 | end; |
2278 | |
2279 | self.isActive = self:getIsActive(); |
2280 | |
2281 | self.updateLoopIndex = g_updateLoopIndex; |
2282 | |
2283 | if self.isServer then |
2284 | local vx, vy, vz = worldDirectionToLocal(self.components[1].node, getLinearVelocity(self.components[1].node)); |
2285 | local movingDirection = 0; |
2286 | if vz > 0.001 then |
2287 | movingDirection = 1; |
2288 | elseif vz < -0.001 then |
2289 | movingDirection = -1; |
2290 | end; |
2291 | local lastSpeedReal = Utils.vector3Length(vx, vy, vz)*0.001; |
2292 | local lastMovedDistance = lastSpeedReal*dt; |
2293 | |
2294 | self.lastSpeedAcceleration = (lastSpeedReal*movingDirection - self.lastSpeedReal*self.movingDirection)/dt; |
2295 | self.lastSpeed = self.lastSpeed*0.5 + lastSpeedReal*0.5; |
2296 | self.lastSpeedReal = lastSpeedReal; |
2297 | self.movingDirection = movingDirection; |
2298 | self.lastMovedDistance = lastMovedDistance; |
2299 | else |
2300 | local newX, newY, newZ = getWorldTranslation(self.components[1].node); |
2301 | if self.lastPosition == nil then |
2302 | self.lastPosition = {newX, newY, newZ}; |
2303 | end; |
2304 | local lastMovingDirection = self.movingDirection; |
2305 | local dx, dy, dz = worldDirectionToLocal(self.components[1].node, newX-self.lastPosition[1], newY-self.lastPosition[2], newZ-self.lastPosition[3]); |
2306 | if dz > 0.001 then |
2307 | self.movingDirection = 1; |
2308 | elseif dz < -0.001 then |
2309 | self.movingDirection = -1; |
2310 | else |
2311 | self.movingDirection = 0; |
2312 | end; |
2313 | self.lastMovedDistance = Utils.vector3Length(dx, dy, dz); |
2314 | local lastLastSpeedReal = self.lastSpeedReal; |
2315 | self.lastSpeedReal = (self.lastMovedDistance/dt); |
2316 | self.lastSpeedAcceleration = (self.lastSpeedReal*self.movingDirection - lastLastSpeedReal*lastMovingDirection)/dt; |
2317 | self.lastSpeed = self.lastSpeed*0.5 + self.lastSpeedReal*0.5; |
2318 | self.lastPosition[1], self.lastPosition[2], self.lastPosition[3] = newX, newY, newZ; |
2319 | end |
2320 | |
2321 | if self.isActive then |
2322 | for _, implement in pairs(self.attachedImplements) do |
2323 | if implement.object ~= nil then |
2324 | if self.isServer or (self.updateLoopIndex == implement.object.updateLoopIndex) then |
2325 | self:updateAttacherJointGraphics(implement, dt); |
2326 | end |
2327 | end |
2328 | end; |
2329 | if not self.isServer and self.attacherVehicle ~= nil then |
2330 | if self.updateLoopIndex == self.attacherVehicle.updateLoopIndex then |
2331 | local implement = self.attacherVehicle:getImplementByObject(self); |
2332 | if implement ~= nil then |
2333 | self.attacherVehicle:updateAttacherJointGraphics(implement, dt); |
2334 | end |
2335 | end |
2336 | end |
2337 | |
2338 | if self.lightsTypesMask > 0 then |
2339 | local realLightsActive = self:getIsActiveForLights(); |
2340 | |
2341 | if realLightsActive ~= self.realLightsActive then |
2342 | self.realLightsActive = realLightsActive; |
2343 | if realLightsActive then |
2344 | -- disable fake lights |
2345 | for _, light in pairs(self.lights) do |
2346 | if light.fakeLight ~= nil then |
2347 | setVisibility(light.fakeLight, false); |
2348 | end |
2349 | end |
2350 | |
2351 | -- enable real lights |
2352 | self:activateRealLights(); |
2353 | else |
2354 | -- disable real lights and enable fake lights |
2355 | for i, light in pairs(self.lights) do |
2356 | if light.realLight ~= nil then |
2357 | setVisibility(light.realLight, false); |
2358 | end |
2359 | if light.fakeLight ~= nil and bitAND(self.lightsTypesMask, 2^light.lightType) ~= 0 then |
2360 | setVisibility(light.fakeLight, true); |
2361 | end |
2362 | end |
2363 | end |
2364 | end |
2365 | end |
2366 | |
2367 | if self.beaconLightsActive then |
2368 | for _, beaconLight in pairs(self.beaconLights) do |
2369 | rotate(beaconLight.node, 0, beaconLight.speed*dt, 0); |
2370 | if beaconLight.rotator ~= nil then |
2371 | rotate(beaconLight.rotator, 0, beaconLight.speed*dt, 0); |
2372 | end; |
2373 | end; |
2374 | end; |
2375 | |
2376 | IKUtil.updateChains(self.ikChains); |
2377 | |
2378 | if self.firstTimeRun then |
2379 | WheelsUtil.updateWheelsGraphics(self, dt); |
2380 | |
2381 | for _,wheel in ipairs(self.wheels) do |
2382 | local color = nil; |
2383 | local wheelSpeed = 0; |
2384 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)); |
2385 | wy = wy - wheel.radius; |
2386 | wx = wx + wheel.xoffset; |
2387 | wx, wy, wz = localToWorld(wheel.node, wx,wy,wz); |
2388 | |
2389 | if self.isServer then |
2390 | wheelSpeed = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape); |
2391 | local contactObject = getWheelShapeContactObject(wheel.node, wheel.wheelShape); |
2392 | if contactObject == g_currentMission.terrainRootNode then |
2393 | wheel.contact = Vehicle.WHEEL_GROUND_CONTACT; |
2394 | elseif wheel.hasGroundContact and contactObject ~= 0 and getRigidBodyType(contactObject) == "Static" and getUserAttribute(contactObject, "noTyreTracks") ~= true then |
2395 | wheel.contact = Vehicle.WHEEL_OBJ_CONTACT; |
2396 | else |
2397 | wheel.contact = Vehicle.WHEEL_NO_CONTACT; |
2398 | end; |
2399 | else |
2400 | wheelSpeed = 1; -- TODO: Calculate wheelSpeed on client |
2401 | end; |
2402 | |
2403 | local isOnField = false; |
2404 | if wheel.contact == Vehicle.WHEEL_GROUND_CONTACT then |
2405 | local densityBits = getDensityAtWorldPos(g_currentMission.terrainDetailId, wx,wz); |
2406 | if densityBits == 0 then |
2407 | if GS_IS_OLD_GEN then |
2408 | color = {0.157, 0.153, 0.149, 0}; |
2409 | else |
2410 | color = {getTerrainAttributesAtWorldPos(g_currentMission.terrainRootNode, wx,wy,wz)}; |
2411 | end |
2412 | else |
2413 | isOnField = true; |
2414 | color = {Utils.getTyreTrackColorFromDensityBits(densityBits)}; |
2415 | end; |
2416 | wheel.dirtAmount = 1; |
2417 | wheel.lastColor = color; |
2418 | elseif wheel.contact == Vehicle.WHEEL_OBJ_CONTACT then |
2419 | if wheel.dirtAmount > 0 then |
2420 | color = wheel.lastColor; |
2421 | color[4] = 0; -- no depth to tyre tracks on road etc. |
2422 | wheel.dirtAmount = math.max(wheel.dirtAmount - self.lastMovedDistance/30, 0); |
2423 | end; |
2424 | end; |
2425 | |
2426 | if self.isServer and wheel.contact ~= Vehicle.WHEEL_NO_CONTACT then |
2427 | local groundType = WheelsUtil.getGroundType(isOnField, wheel.contact ~= Vehicle.WHEEL_GROUND_CONTACT, color); |
2428 | local coeff = WheelsUtil.getTireFriction(wheel.tireType, groundType, g_currentMission.environment.groundWetness); |
2429 | if coeff ~= wheel.tireGroundFrictionCoeff then |
2430 | wheel.tireGroundFrictionCoeff = coeff; |
2431 | self:updateWheelTireFriction(wheel); |
2432 | end |
2433 | end |
2434 | |
2435 | if wheel.tyreTrackIndex ~= nil then |
2436 | if color ~= nil then |
2437 | local ux,uy,uz = localDirectionToWorld(self.rootNode, 0,1,0); |
2438 | -- we are using dirtAmount as alpha value -> realistic dirt fadeout |
2439 | g_currentMission.tyreTrackSystem:addTrackPoint(wheel.tyreTrackIndex, wx, wy, wz, ux, uy, uz, color[1], color[2], color[3], wheel.dirtAmount, color[4], wheelSpeed); |
2440 | else |
2441 | g_currentMission.tyreTrackSystem:cutTrack(wheel.tyreTrackIndex); |
2442 | end; |
2443 | end |
2444 | end; |
2445 | |
2446 | for _, speedRotatingPart in pairs(self.speedRotatingParts) do |
2447 | local isActive = self:getIsSpeedRotatingPartActive(speedRotatingPart); |
2448 | if isActive or (speedRotatingPart.lastSpeed ~= 0 and not speedRotatingPart.stopIfNotActive) then |
2449 | local speed = speedRotatingPart.lastSpeed; |
2450 | local dir = speedRotatingPart.lastDir; |
2451 | if isActive then |
2452 | if speedRotatingPart.wheel ~= nil then |
2453 | x,_,_ = getRotation(speedRotatingPart.wheel.driveNode); |
2454 | local deltaRot = x-speedRotatingPart.lastWheelXRot; |
2455 | speed = math.abs(deltaRot); |
2456 | dir = Utils.sign(deltaRot); |
2457 | speedRotatingPart.lastWheelXRot = x; |
2458 | elseif speedRotatingPart.speedRefNode ~= nil then |
2459 | local newX, newY, newZ = getWorldTranslation(speedRotatingPart.speedRefNode); |
2460 | if speedRotatingPart.lastPosition == nil then |
2461 | speedRotatingPart.lastPosition = { newX, newY, newZ }; |
2462 | end |
2463 | local dx, dy, dz = worldDirectionToLocal(speedRotatingPart.speedRefNode, newX-speedRotatingPart.lastPosition[1], newY-speedRotatingPart.lastPosition[2], newZ-speedRotatingPart.lastPosition[3]); |
2464 | local movingDirection = 0; |
2465 | if dz > 0.0001 then |
2466 | movingDirection = 1; |
2467 | elseif dz < -0.0001 then |
2468 | movingDirection = -1; |
2469 | end; |
2470 | movedDistance = Utils.vector3Length(dx, dy, dz) * movingDirection; |
2471 | speedRotatingPart.lastPosition[1] = newX; |
2472 | speedRotatingPart.lastPosition[2] = newY; |
2473 | speedRotatingPart.lastPosition[3] = newZ; |
2474 | speed = movedDistance; |
2475 | else |
2476 | speed = self.lastSpeedReal * dt; |
2477 | dir = self.movingDirection; |
2478 | end; |
2479 | speedRotatingPart.brakeForce = speed * dt/3000; |
2480 | else |
2481 | speed = math.max(speed - speedRotatingPart.brakeForce, 0); |
2482 | end; |
2483 | |
2484 | speedRotatingPart.lastSpeed = speed; |
2485 | speedRotatingPart.lastDir = dir; |
2486 | speedRotatingPart.xDrive = speedRotatingPart.xDrive + speed * dir * self:getSpeedRotatingPartDirection(speedRotatingPart) * speedRotatingPart.wheelScale; |
2487 | |
2488 | if speedRotatingPart.versatileYRot then |
2489 | if self.isServer and self:getLastSpeed(true) > 1 then |
2490 | local posX, posY, posZ = localToLocal(speedRotatingPart.repr, speedRotatingPart.componentNode, 0,0,0); |
2491 | speedRotatingPart.steeringAngle = Utils.getVersatileRotation(speedRotatingPart.repr, speedRotatingPart.componentNode, dt, posX, posY, posZ, speedRotatingPart.steeringAngle, speedRotatingPart.minYRot, speedRotatingPart.maxYRot); |
2492 | end; |
2493 | else |
2494 | if speedRotatingPart.componentNode ~= nil and speedRotatingPart.dirRefNode ~= nil and not speedRotatingPart.alignDirection then |
2495 | speedRotatingPart.steeringAngle = Utils.getYRotationBetweenNodes(speedRotatingPart.componentNode, speedRotatingPart.dirRefNode); |
2496 | local _,yTrans,_ = localToLocal(speedRotatingPart.driveNode, speedRotatingPart.wheel.driveNode, 0, 0, 0); |
2497 | setTranslation(speedRotatingPart.driveNode, 0, yTrans, 0); |
2498 | end; |
2499 | |
2500 | if speedRotatingPart.dirRefNode ~= nil and speedRotatingPart.alignDirection then |
2501 | local upX, upY, upZ = localDirectionToWorld(speedRotatingPart.dirFrameNode, 0, 1, 0); |
2502 | local dirX, dirY, dirZ = localDirectionToWorld(speedRotatingPart.dirRefNode, 0, 0, 1); |
2503 | Utils.setWorldDirection(speedRotatingPart.repr, dirX, dirY, dirZ, upX, upY, upZ, 2); |
2504 | if speedRotatingPart.wheel ~= nil then |
2505 | local xTrans,yTrans,zTrans = getWorldTranslation(speedRotatingPart.wheel.driveNode); |
2506 | local _,yTrans,_ = worldToLocal(getParent(speedRotatingPart.repr), xTrans, yTrans, zTrans); |
2507 | setTranslation(speedRotatingPart.repr, 0, yTrans, 0); |
2508 | end; |
2509 | end; |
2510 | end; |
2511 | |
2512 | if speedRotatingPart.driveNode ~= nil then |
2513 | if speedRotatingPart.repr == speedRotatingPart.driveNode then |
2514 | setRotation(speedRotatingPart.repr, speedRotatingPart.xDrive, speedRotatingPart.steeringAngle, 0); |
2515 | else |
2516 | if not speedRotatingPart.alignDirection then |
2517 | setRotation(speedRotatingPart.repr, 0, speedRotatingPart.steeringAngle, 0); |
2518 | end; |
2519 | setRotation(speedRotatingPart.driveNode, speedRotatingPart.xDrive, 0, 0); |
2520 | end; |
2521 | end; |
2522 | |
2523 | if speedRotatingPart.shaderNode ~= nil then |
2524 | if speedRotatingPart.useShaderRotation then |
2525 | setShaderParameter(speedRotatingPart.shaderNode, "offsetUV", 0, 0, speedRotatingPart.xDrive, 0, false); |
2526 | else |
2527 | local pos = (speedRotatingPart.xDrive % math.pi) / (2*math.pi); -- normalize rotation |
2528 | setShaderParameter(speedRotatingPart.shaderNode, "offsetUV", pos*speedRotatingPart.scrollScale[1], pos*speedRotatingPart.scrollScale[2], 0, 0, false); |
2529 | end; |
2530 | end; |
2531 | end; |
2532 | end; |
2533 | end; |
2534 | if self.showDetachingNotAllowedTime > 0 then |
2535 | self.showDetachingNotAllowedTime = self.showDetachingNotAllowedTime - dt; |
2536 | end; |
2537 | else |
2538 | self.showDetachingNotAllowedTime = 0; |
2539 | if g_currentMission.tyreTrackSystem ~= nil then |
2540 | for _,wheel in ipairs(self.wheels) do |
2541 | if wheel.tyreTrackIndex ~= nil then |
2542 | g_currentMission.tyreTrackSystem:cutTrack(wheel.tyreTrackIndex); |
2543 | end |
2544 | end; |
2545 | end |
2546 | end; |
2547 | |
2548 | for k,v in pairs(self.specializations) do |
2549 | v.update(self, dt); |
2550 | end; |
2551 | |
2552 | for _,v in ipairs(self.specializations) do |
2553 | if v.postUpdate ~= nil then |
2554 | v.postUpdate(self, dt); |
2555 | end; |
2556 | end; |
2557 | |
2558 | if self.forcePtoUpdate then |
2559 | for _,implement in pairs(self.attachedImplements) do |
2560 | self:updatePowerTakeoff(implement, dt); |
2561 | end; |
2562 | end; |
2563 | |
2564 | self.firstTimeRun = true; |
2565 | end; |
2566 | |
2567 | function Vehicle:updateTick(dt) |
2568 | self.tickDt = dt; |
2569 | self.wasTooFast = false; |
2570 | if self.isServer then |
2571 | local hasOwner = self:getOwner() ~= nil; |
2572 | for i=1, table.getn(self.components) do |
2573 | local x,y,z = getTranslation(self.components[i].node); |
2574 | local x_rot,y_rot,z_rot=getRotation(self.components[i].node); |
2575 | local sentTranslation = self.components[i].sentTranslation; |
2576 | local sentRotation = self.components[i].sentRotation; |
2577 | if hasOwner or |
2578 | math.abs(x-sentTranslation[1]) > 0.005 or |
2579 | math.abs(y-sentTranslation[2]) > 0.005 or |
2580 | math.abs(z-sentTranslation[3]) > 0.005 or |
2581 | math.abs(x_rot-sentRotation[1]) > 0.1 or |
2582 | math.abs(y_rot-sentRotation[2]) > 0.1 or |
2583 | math.abs(z_rot-sentRotation[3]) > 0.1 |
2584 | then |
2585 | self:raiseDirtyFlags(self.vehicleDirtyFlag); |
2586 | sentTranslation[1] = x; sentTranslation[2] = y; sentTranslation[3] = z; |
2587 | sentRotation[1] = x_rot; sentRotation[2] = y_rot; sentRotation[3] = z_rot; |
2588 | self.lastMoveTime = g_currentMission.time; |
2589 | |
2590 | local paramsXZ = g_currentMission.vehicleXZPosCompressionParams; |
2591 | local paramsY = g_currentMission.vehicleYPosCompressionParams; |
2592 | if not Utils.getIsWorldPositionInCompressionRange(x, paramsXZ) or |
2593 | not Utils.getIsWorldPositionInCompressionRange(z, paramsXZ) or |
2594 | not Utils.getIsWorldPositionInCompressionRange(y, paramsY) |
2595 | then |
2596 | -- leave the vehicle first if we want to reset the controlled vehicle |
2597 | --[[if not g_currentMission.controlPlayer and g_currentMission.controlledVehicle ~= nil and self == g_currentMission.controlledVehicle then |
2598 | g_currentMission:onLeaveVehicle(g_currentMission.playerStartX, g_currentMission.playerStartY, g_currentMission.playerStartZ); |
2599 | end |
2600 | g_client:getServerConnection():sendEvent(ResetVehicleEvent:new(self)); |
2601 | return;]] |
2602 | end |
2603 | end; |
2604 | end; |
2605 | end; |
2606 | |
2607 | if self.isActive then |
2608 | |
2609 | local playHydraulicSound = false; |
2610 | |
2611 | for _, implement in pairs(self.attachedImplements) do |
2612 | if implement.object ~= nil then |
2613 | local jointDesc = self.attacherJoints[implement.jointDescIndex]; |
2614 | |
2615 | local jointFrameInvalid = false; |
2616 | if jointDesc.allowsLowering then |
2617 | local moveAlpha = Utils.getMovedLimitedValue(jointDesc.moveAlpha, jointDesc.lowerAlpha, jointDesc.upperAlpha, jointDesc.moveTime, dt, not jointDesc.moveDown); |
2618 | if moveAlpha ~= jointDesc.moveAlpha then |
2619 | playHydraulicSound = true; |
2620 | jointDesc.moveAlpha = moveAlpha; |
2621 | jointFrameInvalid = true; |
2622 | if jointDesc.rotationNode ~= nil then |
2623 | setRotation(jointDesc.rotationNode, Utils.vector3ArrayLerp(jointDesc.minRot, jointDesc.maxRot, jointDesc.moveAlpha)); |
2624 | end |
2625 | if jointDesc.rotationNode2 ~= nil then |
2626 | setRotation(jointDesc.rotationNode2, Utils.vector3ArrayLerp(jointDesc.minRot2, jointDesc.maxRot2, jointDesc.moveAlpha)); |
2627 | end |
2628 | self:updateAttacherJointRotation(jointDesc, implement.object); |
2629 | end |
2630 | end |
2631 | for _,v in pairs(self.specializations) do |
2632 | if v.validateAttacherJoint ~= nil then |
2633 | jointFrameInvalid = jointFrameInvalid or v.validateAttacherJoint(self, implement, jointDesc, dt); |
2634 | end; |
2635 | end |
2636 | jointFrameInvalid = jointFrameInvalid or jointDesc.jointFrameInvalid; |
2637 | if jointFrameInvalid then |
2638 | jointDesc.jointFrameInvalid = false; |
2639 | if self.isServer then |
2640 | setJointFrame(jointDesc.jointIndex, 0, jointDesc.jointTransform); |
2641 | end; |
2642 | end; |
2643 | |
2644 | if self.isServer then |
2645 | if jointDesc.allowsLowering and jointDesc.allowsJointLimitMovement then |
2646 | if implement.object.attacherJoint.allowsJointRotLimitMovement then |
2647 | for i=1,3 do |
2648 | local newRotLimit = Utils.lerp(implement.minRotLimit[i], implement.maxRotLimit[i], jointDesc.moveAlpha); |
2649 | if math.abs(newRotLimit - implement.jointRotLimit[i]) > 0.0005 then |
2650 | setJointRotationLimit(jointDesc.jointIndex, i-1, true, -newRotLimit, newRotLimit); |
2651 | implement.jointRotLimit[i] = newRotLimit; |
2652 | end; |
2653 | end; |
2654 | end; |
2655 | |
2656 | if implement.object.attacherJoint.allowsJointTransLimitMovement then |
2657 | for i=1, 3 do |
2658 | local newTransLimit = Utils.lerp(implement.minTransLimit[i], implement.maxTransLimit[i], jointDesc.moveAlpha); |
2659 | if math.abs(newTransLimit - implement.jointTransLimit[i]) > 0.0005 then |
2660 | setJointTranslationLimit(jointDesc.jointIndex, i-1, true, -newTransLimit, newTransLimit); |
2661 | implement.jointTransLimit[i] = newTransLimit; |
2662 | end; |
2663 | end; |
2664 | end; |
2665 | end; |
2666 | end; |
2667 | end |
2668 | end; |
2669 | |
2670 | if self:getIsActiveForSound() and self.sampleHydraulic ~= nil and self.sampleHydraulic.sample ~= nil then |
2671 | if playHydraulicSound then |
2672 | if not self.sampleHydraulic.isPlaying then |
2673 | Utils.playSample(self.sampleHydraulic, 0, 0, nil); |
2674 | end; |
2675 | elseif self.sampleHydraulic.isPlaying then |
2676 | Utils.stopSample(self.sampleHydraulic, true); |
2677 | end; |
2678 | end; |
2679 | |
2680 | if self.isClient then |
2681 | for k, ps in pairs(self.driveGroundParticleSystems) do |
2682 | local scale = self:getDriveGroundParticleSystemsScale(ps); |
2683 | -- interpolate between different ground colors to avoid unrealisitic particle color changes |
2684 | if ps.lastColor == nil then |
2685 | ps.lastColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]}; |
2686 | ps.targetColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]}; |
2687 | ps.currentColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]}; |
2688 | ps.alpha = 1; |
2689 | end; |
2690 | |
2691 | if ps.alpha ~= 1 then |
2692 | ps.alpha = math.min(ps.alpha + dt/1000, 1); |
2693 | ps.currentColor = {Utils.vector3ArrayLerp(ps.lastColor, ps.targetColor, ps.alpha)}; |
2694 | if ps.alpha == 1 then |
2695 | ps.lastColor = {ps.currentColor[1], ps.currentColor[2], ps.currentColor[3]}; |
2696 | end; |
2697 | end; |
2698 | |
2699 | if ps.alpha == 1 and ps.wheel.lastColor[1] ~= ps.targetColor[1] and ps.wheel.lastColor[2] ~= ps.targetColor[2] and ps.wheel.lastColor[3] ~= ps.targetColor[3] then |
2700 | ps.alpha = 0; |
2701 | ps.targetColor = {ps.wheel.lastColor[1], ps.wheel.lastColor[2], ps.wheel.lastColor[3]}; |
2702 | end; |
2703 | |
2704 | if scale > 0 then |
2705 | Utils.setEmittingState(ps.particleSystems, true); |
2706 | Utils.setEmitCountScale(ps.particleSystems, scale); |
2707 | for _,particleSystem in ipairs(ps.particleSystems) do |
2708 | setShaderParameter(particleSystem.shape, "psColor", ps.currentColor[1], ps.currentColor[2], ps.currentColor[3], 1, false); |
2709 | end; |
2710 | else |
2711 | Utils.setEmittingState(ps.particleSystems, false); |
2712 | end; |
2713 | end; |
2714 | if self.tipErrorMessageTime > 0 then |
2715 | self.tipErrorMessageTime = self.tipErrorMessageTime - dt; |
2716 | end; |
2717 | end; |
2718 | else |
2719 | self.tipErrorMessageTime = 0; |
2720 | if self.isClient then |
2721 | for _, ps in pairs(self.driveGroundParticleSystems) do |
2722 | Utils.setEmittingState(ps.particleSystems, false); |
2723 | end; |
2724 | end; |
2725 | end; |
2726 | |
2727 | for _,v in pairs(self.specializations) do |
2728 | if v.updateTick ~= nil then |
2729 | v.updateTick(self, dt); |
2730 | end; |
2731 | end; |
2732 | |
2733 | for _,v in ipairs(self.specializations) do |
2734 | if v.postUpdateTick ~= nil then |
2735 | v.postUpdateTick(self, dt); |
2736 | end; |
2737 | end; |
2738 | end; |
2739 | |
2740 | function Vehicle:drawUIInfo() |
2741 | if g_showVehicleDistance then |
2742 | local x,y,z = getWorldTranslation(self.rootNode); |
2743 | local x1,y1,z1 = getWorldTranslation(getCamera()) |
2744 | local dist = Utils.vector3Length(x-x1,y-y1,z-z1); |
2745 | if dist <= 350 then |
2746 | local sx,sy,sz = project(x,y+3,z); |
2747 | if sz <= 1 then |
2748 | dist = string.format("%.0f", dist); |
2749 | setTextAlignment(RenderText.ALIGN_CENTER); |
2750 | setTextBold(false); |
2751 | setTextColor(0.0, 0.0, 0.0, 0.75); |
2752 | renderText(sx, sy-0.0015, getCorrectTextSize(0.02), dist); |
2753 | setTextColor(0.5, 1.0, 0.5, 1.0); |
2754 | renderText(sx, sy, getCorrectTextSize(0.02), dist); |
2755 | setTextAlignment(RenderText.ALIGN_LEFT); |
2756 | end; |
2757 | end; |
2758 | end; |
2759 | end; |
2760 | |
2761 | function Vehicle:draw() |
2762 | if self.isEntered or self.selectedImplement == nil then |
2763 | for k,v in pairs(self.specializations) do |
2764 | v.draw(self); |
2765 | end; |
2766 | if self.showDetachingNotAllowedTime > 0 then |
2767 | local alpha = (math.sin(self.showDetachingNotAllowedTime/100)+1)*0.5; |
2768 | g_currentMission:enableHudIcon("detachingNotAllowed", 6, alpha); |
2769 | end; |
2770 | end; |
2771 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
2772 | self.selectedImplement.object:draw(); |
2773 | end; |
2774 | if self.tipErrorMessageTime > 0 then |
2775 | g_currentMission:showBlinkingWarning(self.tipErrorMessage); |
2776 | end; |
2777 | |
2778 | if Vehicle.debugRendering and self.isEntered then |
2779 | self.isSelectable = true; |
2780 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
2781 | Vehicle.drawDebugRendering(self.selectedImplement.object); |
2782 | else |
2783 | Vehicle.drawDebugRendering(self); |
2784 | end |
2785 | end |
2786 | end; |
2787 | |
2788 | function Vehicle:updateAttacherJointGraphics(implement, dt) |
2789 | if implement.object ~= nil then |
2790 | local jointDesc = self.attacherJoints[implement.jointDescIndex]; |
2791 | local attacherJoint = implement.object.attacherJoint; |
2792 | if jointDesc.topArm ~= nil and attacherJoint.topReferenceNode ~= nil then |
2793 | local ax, ay, az = getWorldTranslation(jointDesc.topArm.rotationNode); |
2794 | local bx, by, bz = getWorldTranslation(attacherJoint.topReferenceNode); |
2795 | |
2796 | local x, y, z = worldDirectionToLocal(getParent(jointDesc.topArm.rotationNode), bx-ax, by-ay, bz-az); |
2797 | local distance = Utils.vector3Length(x,y,z); |
2798 | |
2799 | |
2800 | -- kept for debuging |
2801 | local upX, upY, upZ = 0,1,0; |
2802 | if math.abs(y) > 0.99*distance then |
2803 | -- direction and up is parallel |
2804 | upY = 0; |
2805 | if y > 0 then |
2806 | upZ = 1; |
2807 | --print(" ---> topArm switch !!! (upZ = 1)"); |
2808 | else |
2809 | --print(" ---> topArm switch !!! (upZ = -1)"); |
2810 | upZ = -1; |
2811 | end; |
2812 | end; |
2813 | |
2814 | -- different approach I) rotate actual direction of topArm by 90degree around x-axis |
2815 | local alpha = math.rad(-90); |
2816 | -- check if rotationNode is at back of tractor => inverted rotation direction, could be dismissed by rotating TG in i3d |
2817 | local px,py,pz = getWorldTranslation(jointDesc.topArm.rotationNode); |
2818 | local lx,ly,lz = worldToLocal(self.components[1].node, px,py,pz); |
2819 | if lz < 0 then |
2820 | alpha = math.rad(90); |
2821 | end; |
2822 | |
2823 | local dx, dy, dz = localDirectionToWorld(jointDesc.topArm.rotationNode, 0,0,1); |
2824 | dx, dy, dz = worldDirectionToLocal(getParent(jointDesc.topArm.rotationNode), dx, dy, dz); |
2825 | local upX = dx; |
2826 | local upY = math.cos(alpha)*dy - math.sin(alpha)*dz; |
2827 | local upZ = math.sin(alpha)*dy + math.cos(alpha)*dz; |
2828 | |
2829 | -- different approach II) use y-axis of rotationNode |
2830 | -- => less computation, but wrong behaviour in case of "Same Fortis + Grimme KS75-4 @ fronthyd." |
2831 | --local dx, dy, dz = localDirectionToWorld(jointDesc.topArm.rotationNode, 0,1,0); |
2832 | --local upX, upY, upZ = worldDirectionToLocal(getParent(jointDesc.topArm.rotationNode), dx, dy, dz); |
2833 | --upX = 0; -- ToDo: This might be evil? But which hydraulic has its topArm not centered? |
2834 | |
2835 | |
2836 | setDirection(jointDesc.topArm.rotationNode, x*jointDesc.topArm.zScale, y*jointDesc.topArm.zScale, z*jointDesc.topArm.zScale, upX, upY, upZ); |
2837 | if jointDesc.topArm.translationNode ~= nil then |
2838 | local translation = (distance-jointDesc.topArm.referenceDistance) |
2839 | setTranslation(jointDesc.topArm.translationNode, 0, 0, translation*jointDesc.topArm.zScale); |
2840 | if jointDesc.topArm.scaleNode ~= nil then |
2841 | setScale(jointDesc.topArm.scaleNode, 1, 1, math.max((translation+jointDesc.topArm.scaleReferenceDistance)/jointDesc.topArm.scaleReferenceDistance, 0)); |
2842 | end |
2843 | end; |
2844 | end; |
2845 | if jointDesc.bottomArm ~= nil then |
2846 | local ax, ay, az = getWorldTranslation(jointDesc.bottomArm.rotationNode); |
2847 | local bx, by, bz = getWorldTranslation(attacherJoint.node); |
2848 | |
2849 | local x, y, z = worldDirectionToLocal(getParent(jointDesc.bottomArm.rotationNode), bx-ax, by-ay, bz-az); |
2850 | local distance = Utils.vector3Length(x,y,z); |
2851 | local upX, upY, upZ = 0,1,0; |
2852 | if math.abs(y) > 0.99*distance then |
2853 | -- direction and up is parallel |
2854 | upY = 0; |
2855 | if y > 0 then |
2856 | upZ = 1; |
2857 | else |
2858 | upZ = -1; |
2859 | end; |
2860 | end |
2861 | setDirection(jointDesc.bottomArm.rotationNode, x*jointDesc.bottomArm.zScale, y*jointDesc.bottomArm.zScale, z*jointDesc.bottomArm.zScale, upX, upY, upZ); |
2862 | if jointDesc.bottomArm.translationNode ~= nil then |
2863 | setTranslation(jointDesc.bottomArm.translationNode, 0, 0, (distance-jointDesc.bottomArm.referenceDistance)*jointDesc.bottomArm.zScale); |
2864 | end |
2865 | if self.setMovingToolDirty ~= nil then |
2866 | self:setMovingToolDirty(jointDesc.bottomArm.rotationNode); |
2867 | end |
2868 | end; |
2869 | end |
2870 | |
2871 | self:updatePowerTakeoff(implement, dt); |
2872 | end |
2873 | |
2874 | function Vehicle:getDriveGroundParticleSystemsScale(particleSystem) |
2875 | local wheel = particleSystem.wheel; |
2876 | if wheel ~= nil then |
2877 | if particleSystem.onlyActiveOnGroundContact and wheel.contact ~= Vehicle.WHEEL_GROUND_CONTACT then |
2878 | return 0; |
2879 | end; |
2880 | |
2881 | if math.abs(wheel.lastColor[1]-0.275) <= 0.001 and math.abs(wheel.lastColor[2]-0.212) <= 0.001 and math.abs(wheel.lastColor[3]-0.160) <= 0.001 then |
2882 | return 0; |
2883 | end; |
2884 | end; |
2885 | local minSpeed = particleSystem.minSpeed; |
2886 | local direction = particleSystem.direction; |
2887 | if self.lastSpeedReal > minSpeed and (direction == 0 or (direction > 0) == (self.movingDirection > 0)) then |
2888 | local maxSpeed = particleSystem.maxSpeed; |
2889 | local alpha = math.min((self.lastSpeedReal - minSpeed) / (maxSpeed - minSpeed), 1); |
2890 | local scale = Utils.lerp(particleSystem.minScale, particleSystem.maxScale, alpha); |
2891 | return scale; |
2892 | end; |
2893 | return 0; |
2894 | end; |
2895 | |
2896 | function Vehicle:getSpeedLimit(onlyIfWorking) |
2897 | local limit = math.huge; |
2898 | local doCheckSpeedLimit = self:doCheckSpeedLimit(); |
2899 | if onlyIfWorking == nil or (onlyIfWorking and doCheckSpeedLimit) then |
2900 | limit = self.speedLimit; |
2901 | end; |
2902 | for _, implement in pairs(self.attachedImplements) do |
2903 | if implement.object ~= nil then |
2904 | local speed, implementDoCheckSpeedLimit = implement.object:getSpeedLimit(onlyIfWorking); |
2905 | if onlyIfWorking == nil or (onlyIfWorking and implementDoCheckSpeedLimit) then |
2906 | limit = math.min(limit, speed); |
2907 | end; |
2908 | doCheckSpeedLimit = implementDoCheckSpeedLimit or implementDoCheckSpeedLimit; |
2909 | end |
2910 | end; |
2911 | |
2912 | return limit, doCheckSpeedLimit; |
2913 | end; |
2914 | |
2915 | function Vehicle:doCheckSpeedLimit() |
2916 | return self.checkSpeedLimit; |
2917 | |
2918 | --[[ |
2919 | if self.attacherVehicle ~= nil then |
2920 | return self.checkSpeedLimit and self.attacherVehicle:doCheckSpeedLimit(); |
2921 | end |
2922 | return self.checkSpeedLimit;]] |
2923 | end; |
2924 | |
2925 | function Vehicle:getAttachedTrailersFillLevelAndCapacity() |
2926 | local fillLevel = 0; |
2927 | local capacity = 0; |
2928 | local hasTrailer = false; |
2929 | |
2930 | if self.fillLevel ~= nil and self.getCapacity ~= nil then |
2931 | fillLevel = fillLevel + self.fillLevel; |
2932 | capacity = capacity + self:getCapacity(); |
2933 | hasTrailer = true; |
2934 | end; |
2935 | |
2936 | for _, implement in pairs(self.attachedImplements) do |
2937 | if implement.object ~= nil then |
2938 | local f, c = implement.object:getAttachedTrailersFillLevelAndCapacity(); |
2939 | if f ~= nil and c ~= nil then |
2940 | fillLevel = fillLevel + f; |
2941 | capacity = capacity + c; |
2942 | hasTrailer = true; |
2943 | end; |
2944 | end |
2945 | end; |
2946 | if hasTrailer then |
2947 | return fillLevel, capacity; |
2948 | end; |
2949 | return nil; |
2950 | end; |
2951 | |
2952 | function Vehicle:calculateAttacherJointMoveUpperLowerAlpha(jointDesc, object) |
2953 | if jointDesc.allowsLowering then |
2954 | local upperAlpha = Utils.clamp((object.attacherJoint.upperDistanceToGround - jointDesc.minRotDistanceToGround) / (jointDesc.maxRotDistanceToGround - jointDesc.minRotDistanceToGround), 0, 1); |
2955 | local lowerAlpha = Utils.clamp((object.attacherJoint.lowerDistanceToGround - jointDesc.minRotDistanceToGround) / (jointDesc.maxRotDistanceToGround - jointDesc.minRotDistanceToGround), 0, 1); |
2956 | if object.attacherJoint.allowsLowering and jointDesc.allowsLowering then |
2957 | return upperAlpha, lowerAlpha; |
2958 | else |
2959 | if object.attacherJoint.isDefaultLowered then |
2960 | return lowerAlpha,lowerAlpha; |
2961 | else |
2962 | return upperAlpha,upperAlpha; |
2963 | end |
2964 | end |
2965 | end |
2966 | if object.attacherJoint.isDefaultLowered then |
2967 | return 1,1; |
2968 | else |
2969 | return 0,0; |
2970 | end |
2971 | end |
2972 | |
2973 | function Vehicle:updateAttacherJointRotation(jointDesc, object) |
2974 | -- rotate attacher such that |
2975 | local targetRot = Utils.lerp(object.attacherJoint.upperRotationOffset, object.attacherJoint.lowerRotationOffset, jointDesc.moveAlpha); |
2976 | local curRot = Utils.lerp(jointDesc.minRotRotationOffset, jointDesc.maxRotRotationOffset, jointDesc.moveAlpha); |
2977 | local rotDiff = targetRot - curRot; |
2978 | |
2979 | setRotation(jointDesc.jointTransform, unpack(jointDesc.jointOrigRot)); |
2980 | rotateAboutLocalAxis(jointDesc.jointTransform, rotDiff, 0, 0, 1); |
2981 | end |
2982 | |
2983 | function Vehicle:attachImplement(object, inputJointDescIndex, jointDescIndex, noEventSend, index, startLowered) |
2984 | local jointDesc = self.attacherJoints[jointDescIndex]; |
2985 | |
2986 | object:onPreAttach(self, inputJointDescIndex); |
2987 | |
2988 | local upperAlpha, lowerAlpha = self:calculateAttacherJointMoveUpperLowerAlpha(jointDesc, object); |
2989 | |
2990 | if startLowered == nil then |
2991 | startLowered = true; |
2992 | if object.attacherJoint.allowsLowering and jointDesc.allowsLowering then |
2993 | local ax,ay,az = getWorldTranslation(object.attacherJoint.node); |
2994 | -- test which position to use |
2995 | if jointDesc.rotationNode ~= nil then |
2996 | setRotation(jointDesc.rotationNode, Utils.vector3ArrayLerp(jointDesc.minRot, jointDesc.maxRot, upperAlpha)); |
2997 | end; |
2998 | if jointDesc.rotationNode2 ~= nil then |
2999 | setRotation(jointDesc.rotationNode2, Utils.vector3ArrayLerp(jointDesc.minRot2, jointDesc.maxRot2, upperAlpha)); |
3000 | end; |
3001 | local x,y,z = getWorldTranslation(jointDesc.jointTransform); |
3002 | local distanceSqUpper = Utils.vector3LengthSq(x-ax, y-ay, z-az); |
3003 | |
3004 | if jointDesc.rotationNode ~= nil then |
3005 | setRotation(jointDesc.rotationNode, Utils.vector3ArrayLerp(jointDesc.minRot, jointDesc.maxRot, lowerAlpha)); |
3006 | end; |
3007 | if jointDesc.rotationNode2 ~= nil then |
3008 | setRotation(jointDesc.rotationNode2, Utils.vector3ArrayLerp(jointDesc.minRot2, jointDesc.maxRot2, lowerAlpha)); |
3009 | end; |
3010 | local x,y,z = getWorldTranslation(jointDesc.jointTransform); |
3011 | local distanceSqLower = Utils.vector3LengthSq(x-ax, y-ay, z-az); |
3012 | if distanceSqUpper < distanceSqLower*1.1 then -- Use the position which has the smaller distance (use a slight preference for min rot) |
3013 | startLowered = false; |
3014 | end |
3015 | else |
3016 | if not object.attacherJoint.isDefaultLowered then |
3017 | startLowered = false; |
3018 | end |
3019 | end |
3020 | end |
3021 | |
3022 | if noEventSend == nil or noEventSend == false then |
3023 | if g_server ~= nil then |
3024 | g_server:broadcastEvent(VehicleAttachEvent:new(self, object, inputJointDescIndex, jointDescIndex, startLowered), nil, nil, self); |
3025 | else |
3026 | g_client:getServerConnection():sendEvent(VehicleAttachEvent:new(self, object, inputJointDescIndex, jointDescIndex, startLowered)); |
3027 | end; |
3028 | end |
3029 | |
3030 | if jointDesc.transNode ~= nil and object.attacherJoint.attacherHeight ~= nil then |
3031 | -- limit attacher height |
3032 | local yTarget = Utils.clamp(object.attacherJoint.attacherHeight, jointDesc.transMinYHeight, jointDesc.transMaxYHeight); |
3033 | -- get current joint position relative to rootnode |
3034 | local _,yRoot,_ = worldToLocal(self.rootNode, getWorldTranslation(jointDesc.jointTransform)); |
3035 | -- get joint - transnode offset |
3036 | local _,yOffset,_ = worldToLocal(jointDesc.transNode, getWorldTranslation(jointDesc.jointTransform)); |
3037 | -- get new transNode y position |
3038 | local _,yTrans,_ = worldToLocal(getParent(jointDesc.transNode), localToWorld(self.rootNode, 0, yRoot - ((yRoot - yTarget) + yOffset), 0)); |
3039 | local x,_,z = getTranslation(jointDesc.transNode); |
3040 | setTranslation(jointDesc.transNode, x, yTrans, z); |
3041 | end; |
3042 | |
3043 | if object.attacherJoint.topReferenceNode ~= nil then |
3044 | if jointDesc.topArm ~= nil and jointDesc.topArm.toggleVisibility then |
3045 | setVisibility(jointDesc.topArm.rotationNode, true); |
3046 | end; |
3047 | end; |
3048 | |
3049 | local implement = {}; |
3050 | implement.object = object; |
3051 | implement.object:onAttach(self, jointDescIndex); |
3052 | implement.jointDescIndex = jointDescIndex; |
3053 | |
3054 | jointDesc.upperAlpha = upperAlpha; |
3055 | jointDesc.lowerAlpha = lowerAlpha; |
3056 | |
3057 | jointDesc.moveAlpha = upperAlpha; |
3058 | if startLowered then |
3059 | jointDesc.moveAlpha = lowerAlpha; |
3060 | end |
3061 | |
3062 | if jointDesc.rotationNode ~= nil then |
3063 | setRotation(jointDesc.rotationNode, Utils.vector3ArrayLerp(jointDesc.minRot, jointDesc.maxRot, jointDesc.moveAlpha)); |
3064 | end; |
3065 | if jointDesc.rotationNode2 ~= nil then |
3066 | setRotation(jointDesc.rotationNode2, Utils.vector3ArrayLerp(jointDesc.minRot2, jointDesc.maxRot2, jointDesc.moveAlpha)); |
3067 | end; |
3068 | self:updateAttacherJointRotation(jointDesc, object); |
3069 | |
3070 | if self.isServer then |
3071 | local xNew = jointDesc.jointOrigTrans[1] + jointDesc.jointPositionOffset[1]; |
3072 | local yNew = jointDesc.jointOrigTrans[2] + jointDesc.jointPositionOffset[2]; |
3073 | local zNew = jointDesc.jointOrigTrans[3] + jointDesc.jointPositionOffset[3]; |
3074 | |
3075 | -- transform offset position to world coord and to jointTransform coord to get position offset dependend on angle and position |
3076 | local x,y,z = localToWorld(getParent(jointDesc.jointTransform), xNew, yNew, zNew); |
3077 | local x1,y1,z1 = worldToLocal(jointDesc.jointTransform, x,y,z); |
3078 | |
3079 | -- move jointTransform to offset pos |
3080 | setTranslation(jointDesc.jointTransform, xNew, yNew, zNew); |
3081 | |
3082 | -- transform it to implement position and angle |
3083 | x,y,z = localToWorld(implement.object.attacherJoint.node,x1,y1,z1); |
3084 | local x2,y2,z2 = worldToLocal(getParent(implement.object.attacherJoint.node), x,y,z); |
3085 | setTranslation(implement.object.attacherJoint.node, x2,y2, z2); |
3086 | |
3087 | |
3088 | local constr = JointConstructor:new(); |
3089 | constr:setActors(jointDesc.rootNode, implement.object.attacherJoint.rootNode); |
3090 | constr:setJointTransforms(jointDesc.jointTransform, implement.object.attacherJoint.node); |
3091 | --constr:setBreakable(20, 10); |
3092 | |
3093 | implement.jointRotLimit = {}; |
3094 | implement.jointTransLimit = {}; |
3095 | implement.maxRotLimit = {}; |
3096 | implement.maxTransLimit = {}; |
3097 | implement.minRotLimit = {}; |
3098 | implement.minTransLimit = {}; |
3099 | for i=1, 3 do |
3100 | local maxRotLimit = jointDesc.maxRotLimit[i]*implement.object.attacherJoint.rotLimitScale[i]; |
3101 | local minRotLimit = jointDesc.minRotLimit[i]*implement.object.attacherJoint.rotLimitScale[i]; |
3102 | if implement.object.attacherJoint.fixedRotation then |
3103 | maxRotLimit = 0; |
3104 | minRotLimit = 0; |
3105 | end; |
3106 | local maxTransLimit = jointDesc.maxTransLimit[i]*implement.object.attacherJoint.transLimitScale[i]; |
3107 | local minTransLimit = jointDesc.minTransLimit[i]*implement.object.attacherJoint.transLimitScale[i]; |
3108 | implement.maxRotLimit[i] = maxRotLimit; |
3109 | implement.minRotLimit[i] = minRotLimit; |
3110 | |
3111 | implement.maxTransLimit[i] = maxTransLimit; |
3112 | implement.minTransLimit[i] = minTransLimit; |
3113 | |
3114 | local rotLimit = maxRotLimit; |
3115 | local transLimit = maxTransLimit; |
3116 | if jointDesc.allowsLowering and jointDesc.allowsJointLimitMovement then |
3117 | if implement.object.attacherJoint.allowsJointRotLimitMovement then |
3118 | rotLimit = Utils.lerp(minRotLimit, maxRotLimit, jointDesc.moveAlpha); |
3119 | end |
3120 | if implement.object.attacherJoint.allowsJointTransLimitMovement then |
3121 | transLimit = Utils.lerp(minTransLimit, maxTransLimit, jointDesc.moveAlpha); |
3122 | end |
3123 | end |
3124 | |
3125 | constr:setRotationLimit(i-1, -rotLimit, rotLimit); |
3126 | implement.jointRotLimit[i] = rotLimit; |
3127 | |
3128 | constr:setTranslationLimit(i-1, true, -transLimit, transLimit); |
3129 | implement.jointTransLimit[i] = transLimit; |
3130 | end; |
3131 | if jointDesc.enableCollision then |
3132 | constr:setEnableCollision(true); |
3133 | end |
3134 | |
3135 | local springX = math.max(jointDesc.rotLimitSpring[1], implement.object.attacherJoint.rotLimitSpring[1]); |
3136 | local springY = math.max(jointDesc.rotLimitSpring[2], implement.object.attacherJoint.rotLimitSpring[2]); |
3137 | local springZ = math.max(jointDesc.rotLimitSpring[3], implement.object.attacherJoint.rotLimitSpring[3]); |
3138 | local dampingX = math.max(jointDesc.rotLimitDamping[1], implement.object.attacherJoint.rotLimitDamping[1]); |
3139 | local dampingY = math.max(jointDesc.rotLimitDamping[2], implement.object.attacherJoint.rotLimitDamping[2]); |
3140 | local dampingZ = math.max(jointDesc.rotLimitDamping[3], implement.object.attacherJoint.rotLimitDamping[3]); |
3141 | constr:setRotationLimitSpring(springX, dampingX, springY, dampingY, springZ, dampingZ); |
3142 | |
3143 | local transSpringX = math.max(jointDesc.transLimitSpring[1], implement.object.attacherJoint.transLimitSpring[1]); |
3144 | local transSpringY = math.max(jointDesc.transLimitSpring[2], implement.object.attacherJoint.transLimitSpring[2]); |
3145 | local transSpringZ = math.max(jointDesc.transLimitSpring[3], implement.object.attacherJoint.transLimitSpring[3]); |
3146 | local transDampingX = math.max(jointDesc.transLimitDamping[1], implement.object.attacherJoint.transLimitDamping[1]); |
3147 | local transDampingY = math.max(jointDesc.transLimitDamping[2], implement.object.attacherJoint.transLimitDamping[2]); |
3148 | local transDampingZ = math.max(jointDesc.transLimitDamping[3], implement.object.attacherJoint.transLimitDamping[3]); |
3149 | constr:setTranslationLimitSpring(transSpringX, transDampingX, transSpringY, transDampingY, transSpringZ, transDampingZ); |
3150 | |
3151 | jointDesc.jointIndex = constr:finalize(); |
3152 | |
3153 | -- restore implement attacher joint position (to ensure correct bottom arm alignment) |
3154 | setTranslation(implement.object.attacherJoint.node, unpack(object.attacherJoint.jointOrigTrans)); |
3155 | else |
3156 | jointDesc.jointIndex = -1; |
3157 | end; |
3158 | jointDesc.moveDown = implement.object.attacherJoint.isDefaultLowered; |
3159 | object:onSetLowered(jointDesc.moveDown); |
3160 | |
3161 | if object.ptoInput ~= nil and jointDesc.ptoOutput ~= nil then |
3162 | jointDesc.ptoActive = true; |
3163 | if object.ptoInput.rootNode ~= nil then |
3164 | link(jointDesc.ptoOutput.node, object.ptoInput.rootNode); |
3165 | link(object.ptoInput.node, object.ptoInput.attachNode); |
3166 | |
3167 | if object.addWashableNode ~= nil then |
3168 | object:addWashableNode(object.ptoOutput.rootNode); |
3169 | object:addWashableNode(object.ptoOutput.attachNode); |
3170 | object:addWashableNode(object.ptoOutput.dirAndScaleNode); |
3171 | object:setDirtAmount(object:getDirtAmount(), force); |
3172 | end; |
3173 | else |
3174 | link(jointDesc.ptoOutput.node, jointDesc.ptoOutput.rootNode); |
3175 | link(object.ptoInput.node, jointDesc.ptoOutput.attachNode); |
3176 | |
3177 | if object.addWashableNode ~= nil then |
3178 | object:addWashableNode(jointDesc.ptoOutput.rootNode); |
3179 | object:addWashableNode(jointDesc.ptoOutput.attachNode); |
3180 | object:addWashableNode(jointDesc.ptoOutput.dirAndScaleNode); |
3181 | object:setDirtAmount(object:getDirtAmount(), force); |
3182 | end; |
3183 | end |
3184 | |
3185 | self:updatePowerTakeoff(implement, 0); |
3186 | end |
3187 | |
3188 | if index == nil then |
3189 | table.insert(self.attachedImplements, implement); |
3190 | index = table.getn(self.attachedImplements); |
3191 | else |
3192 | -- make the table at least as big as the number of implements |
3193 | local numAdd = index-table.getn(self.attachedImplements); |
3194 | for i=1, numAdd do |
3195 | table.insert(self.attachedImplements, {}); |
3196 | end; |
3197 | self.attachedImplements[index] = implement; |
3198 | end; |
3199 | |
3200 | self:updateAttacherJointGraphics(implement, 0) |
3201 | |
3202 | for _,v in pairs(self.specializations) do |
3203 | if v.attachImplement ~= nil then |
3204 | v.attachImplement(self, implement); |
3205 | end; |
3206 | end; |
3207 | |
3208 | implement.object:onAttached(self, jointDescIndex); |
3209 | |
3210 | if self.isClient then |
3211 | implement.object:setSelectedImplement(nil); |
3212 | if implement.object:getIsSelectable() and implement.object.forceSelection then |
3213 | -- find the root element |
3214 | local rootAttacherVehicle = self:getRootAttacherVehicle(); |
3215 | -- select the new element as selected |
3216 | rootAttacherVehicle:setSelectedImplement(implement); |
3217 | end; |
3218 | end; |
3219 | |
3220 | end; |
3221 | |
3222 | function Vehicle:detachImplement(implementIndex, noEventSend) |
3223 | |
3224 | if noEventSend == nil or noEventSend == false then |
3225 | if g_server ~= nil then |
3226 | g_server:broadcastEvent(VehicleDetachEvent:new(self, self.attachedImplements[implementIndex].object), nil, nil, self); |
3227 | else |
3228 | -- Send detach request to server and return |
3229 | local implement = self.attachedImplements[implementIndex]; |
3230 | if implement.object ~= nil then |
3231 | g_client:getServerConnection():sendEvent(VehicleDetachEvent:new(self, implement.object)); |
3232 | end |
3233 | return; |
3234 | end; |
3235 | end |
3236 | for k,v in pairs(self.specializations) do |
3237 | if v.detachImplement ~= nil then |
3238 | v.detachImplement(self, implementIndex); |
3239 | end; |
3240 | end; |
3241 | |
3242 | local implement = self.attachedImplements[implementIndex]; |
3243 | local jointDesc; |
3244 | if implement.object ~= nil then |
3245 | jointDesc = self.attacherJoints[implement.jointDescIndex]; |
3246 | if jointDesc.transNode ~= nil then |
3247 | setTranslation(jointDesc.transNode, unpack(jointDesc.transNodeOrgTrans)); |
3248 | end; |
3249 | if self.isServer then |
3250 | removeJoint(jointDesc.jointIndex); |
3251 | end; |
3252 | jointDesc.jointIndex = 0; |
3253 | end |
3254 | |
3255 | local rootAttacherVehicle = self:getRootAttacherVehicle(); |
3256 | local selectNewImplement = false; |
3257 | |
3258 | if self.isClient then |
3259 | if rootAttacherVehicle.selectedImplement == implement then |
3260 | selectNewImplement = true; |
3261 | rootAttacherVehicle:setSelectedImplement(nil); |
3262 | end; |
3263 | end; |
3264 | if implement.object ~= nil then |
3265 | local object = implement.object; |
3266 | implement.object:onDetach(self, jointDescIndex); |
3267 | implement.object = nil; |
3268 | if self.isClient then |
3269 | if jointDesc.topArm ~= nil then |
3270 | setRotation(jointDesc.topArm.rotationNode, jointDesc.topArm.rotX, jointDesc.topArm.rotY, jointDesc.topArm.rotZ); |
3271 | if jointDesc.topArm.translationNode ~= nil then |
3272 | setTranslation(jointDesc.topArm.translationNode, 0, 0, 0); |
3273 | end; |
3274 | if jointDesc.topArm.scaleNode ~= nil then |
3275 | setScale(jointDesc.topArm.scaleNode, 1, 1, 1); |
3276 | end |
3277 | if jointDesc.topArm.toggleVisibility then |
3278 | setVisibility(jointDesc.topArm.rotationNode, false); |
3279 | end; |
3280 | end; |
3281 | if jointDesc.bottomArm ~= nil then |
3282 | setRotation(jointDesc.bottomArm.rotationNode, jointDesc.bottomArm.rotX, jointDesc.bottomArm.rotY, jointDesc.bottomArm.rotZ); |
3283 | if jointDesc.bottomArm.translationNode ~= nil then |
3284 | setTranslation(jointDesc.bottomArm.translationNode, 0, 0, 0); |
3285 | end |
3286 | if self.setMovingToolDirty ~= nil then |
3287 | self:setMovingToolDirty(jointDesc.bottomArm.rotationNode); |
3288 | end |
3289 | end; |
3290 | end; |
3291 | if self.isServer then |
3292 | -- restore original translation |
3293 | setTranslation(jointDesc.jointTransform, unpack(jointDesc.jointOrigTrans)); |
3294 | setTranslation(object.attacherJoint.node, unpack(object.attacherJoint.jointOrigTrans)); |
3295 | |
3296 | if jointDesc.rotationNode ~= nil then |
3297 | setRotation(jointDesc.rotationNode, unpack(jointDesc.minRot)); |
3298 | end; |
3299 | end; |
3300 | |
3301 | if jointDesc.ptoActive then |
3302 | if object.ptoInput.rootNode ~= nil then |
3303 | unlink(object.ptoInput.rootNode); |
3304 | unlink(object.ptoInput.attachNode); |
3305 | |
3306 | if object.removeWashableNode ~= nil then |
3307 | object:removeWashableNode(object.ptoOutput.rootNode); |
3308 | object:removeWashableNode(object.ptoOutput.attachNode); |
3309 | object:removeWashableNode(object.ptoOutput.dirAndScaleNode); |
3310 | end; |
3311 | else |
3312 | unlink(jointDesc.ptoOutput.rootNode); |
3313 | unlink(jointDesc.ptoOutput.attachNode); |
3314 | |
3315 | if object.removeWashableNode ~= nil then |
3316 | object:removeWashableNode(jointDesc.ptoOutput.rootNode); |
3317 | object:removeWashableNode(jointDesc.ptoOutput.attachNode); |
3318 | object:removeWashableNode(jointDesc.ptoOutput.dirAndScaleNode); |
3319 | end; |
3320 | end |
3321 | jointDesc.ptoActive = false; |
3322 | end |
3323 | |
3324 | object:onDetached(self, jointDescIndex); |
3325 | end |
3326 | |
3327 | table.remove(self.attachedImplements, implementIndex); |
3328 | if self.isClient then |
3329 | if selectNewImplement then |
3330 | local newSelectedImplement = nil; |
3331 | local newIndex = math.min(implementIndex, table.getn(self.attachedImplements)); |
3332 | if newIndex == 0 then |
3333 | if self ~= rootAttacherVehicle then |
3334 | -- select self |
3335 | newSelectedImplement = self.attacherVehicle:getImplementByObject(self); |
3336 | end |
3337 | else |
3338 | -- select the implement at the new index |
3339 | newSelectedImplement = self.attachedImplements[newIndex]; |
3340 | end |
3341 | rootAttacherVehicle:setSelectedImplement(newSelectedImplement); |
3342 | end |
3343 | end; |
3344 | |
3345 | end; |
3346 | |
3347 | function Vehicle:detachImplementByObject(object, noEventSend) |
3348 | |
3349 | for i,implement in ipairs(self.attachedImplements) do |
3350 | if implement.object == object then |
3351 | self:detachImplement(i, noEventSend); |
3352 | break; |
3353 | end; |
3354 | end; |
3355 | end; |
3356 | |
3357 | function Vehicle:setSelectedImplement(selectedImplement) |
3358 | if selectedImplement == self then |
3359 | selectedImplement = nil; |
3360 | end |
3361 | if selectedImplement ~= self.selectedImplement then |
3362 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
3363 | self.selectedImplement.object:onDeselect(); |
3364 | end; |
3365 | self.selectedImplement = selectedImplement; |
3366 | if selectedImplement ~= nil and selectedImplement.object ~= nil then |
3367 | selectedImplement.object:onSelect(); |
3368 | end |
3369 | end |
3370 | end; |
3371 | |
3372 | -- selects the next implement in a dfs order |
3373 | -- returns if a child or sub*-child was selected |
3374 | function Vehicle:selectNextSelectableImplement(selectedIndex) |
3375 | local numImplements = table.getn(self.attachedImplements); |
3376 | if numImplements == 0 then |
3377 | return false; |
3378 | end; |
3379 | local rootAttacherVehicle = self:getRootAttacherVehicle(); |
3380 | if self == rootAttacherVehicle then |
3381 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil then |
3382 | local curVehicle = self.selectedImplement.object; |
3383 | selectedIndex = 0; |
3384 | while curVehicle ~= nil and curVehicle ~= self do |
3385 | if curVehicle:selectNextSelectableImplement(selectedIndex) then |
3386 | return true; |
3387 | end; |
3388 | if curVehicle.attacherVehicle ~= nil then |
3389 | selectedIndex = curVehicle.attacherVehicle:getImplementIndexByObject(curVehicle); |
3390 | end |
3391 | curVehicle = curVehicle.attacherVehicle; |
3392 | end |
3393 | end; |
3394 | end |
3395 | |
3396 | if selectedIndex == nil then |
3397 | selectedIndex = 0; |
3398 | end |
3399 | |
3400 | for i=1, numImplements do |
3401 | local index = selectedIndex+i; |
3402 | if index > numImplements then |
3403 | if self.attacherVehicle == nil and not self:getIsSelectable() then |
3404 | -- loop around if this is the root vehicle and it is not selectable |
3405 | index = index - selectedIndex; |
3406 | else |
3407 | if self.attacherVehicle == nil then |
3408 | self:setSelectedImplement(nil); |
3409 | end |
3410 | return false; |
3411 | end; |
3412 | end; |
3413 | local implement = self.attachedImplements[index]; |
3414 | if implement.object ~= nil then |
3415 | if implement.object:getIsSelectable() then |
3416 | rootAttacherVehicle:setSelectedImplement(implement); |
3417 | return true; |
3418 | elseif implement.object:selectNextSelectableImplement() then |
3419 | return true; |
3420 | end; |
3421 | end |
3422 | end; |
3423 | |
3424 | return false; |
3425 | end; |
3426 | |
3427 | function Vehicle:getIsSelectable() |
3428 | if self.isSelectable then |
3429 | return true; |
3430 | end; |
3431 | if self.attacherVehicle ~= nil and self.attacherJoint.allowsLowering then |
3432 | local implement = self.attacherVehicle.getImplementByObject(self); |
3433 | if implement ~= nil then |
3434 | local jointDesc = self.attacherVehicle.attacherJoints[implement.jointDescIndex]; |
3435 | if jointDesc.allowsLowering then |
3436 | return true; |
3437 | end; |
3438 | end; |
3439 | end; |
3440 | return false |
3441 | end; |
3442 | |
3443 | function Vehicle:getIsAChildSelectable() |
3444 | for _,implement in pairs(self.attachedImplements) do |
3445 | if implement.object ~= nil then |
3446 | if implement.object:getIsSelectable() or implement.object:getIsAChildSelectable() then |
3447 | return true; |
3448 | end; |
3449 | end |
3450 | end; |
3451 | return false |
3452 | end; |
3453 | |
3454 | function Vehicle:playAttachSound() |
3455 | if self.isClient then |
3456 | Utils.playSample(self.sampleAttach, 1, 0, nil); |
3457 | end; |
3458 | end; |
3459 | |
3460 | function Vehicle:playDetachSound() |
3461 | if self.isClient then |
3462 | Utils.playSample(self.sampleAttach, 1, 0, nil); |
3463 | end; |
3464 | end; |
3465 | |
3466 | function Vehicle:handleAttachEvent() |
3467 | |
3468 | if self == g_currentMission.controlledVehicle and g_currentMission.trailerInTipRange ~= nil then |
3469 | -- check if current fruit type is accepted by the station |
3470 | if g_currentMission.currentTipTrigger ~= nil then |
3471 | if g_currentMission.currentTipTriggerIsAllowed then |
3472 | self.tipErrorMessageTime = 0; |
3473 | g_currentMission.trailerInTipRange:toggleTipState(g_currentMission.currentTipTrigger, g_currentMission.currentTipReferencePointIndex); |
3474 | else |
3475 | local text = g_currentMission.currentTipTrigger:getNoAllowedText(g_currentMission.trailerInTipRange); |
3476 | if text ~= nil and text ~= "" then |
3477 | self.tipErrorMessageTime = 3000; |
3478 | self.tipErrorMessage = text; |
3479 | end |
3480 | end; |
3481 | end; |
3482 | elseif self:handleAttachAttachableEvent() then |
3483 | self:playAttachSound(); |
3484 | elseif self:handleDetachAttachableEvent() then |
3485 | self:playDetachSound(); |
3486 | end |
3487 | end; |
3488 | |
3489 | function Vehicle:handleAttachAttachableEvent() |
3490 | if g_currentMission.attachableInMountRange ~= nil then |
3491 | if g_currentMission.attachableInMountRangeVehicle == self then |
3492 | if self.attacherJoints[g_currentMission.attachableInMountRangeIndex].jointIndex == 0 then |
3493 | self:attachImplement(g_currentMission.attachableInMountRange, g_currentMission.attachableInMountRangeJointIndex, g_currentMission.attachableInMountRangeIndex); |
3494 | return true; |
3495 | end; |
3496 | else |
3497 | for _,implement in ipairs(self.attachedImplements) do |
3498 | if implement.object ~= nil and implement.object:handleAttachAttachableEvent() then |
3499 | return true; |
3500 | end; |
3501 | end; |
3502 | end; |
3503 | end; |
3504 | return false; |
3505 | end; |
3506 | |
3507 | function Vehicle:handleDetachAttachableEvent() |
3508 | |
3509 | if self.selectedImplement ~= nil then |
3510 | local object = self.selectedImplement.object; |
3511 | if object ~= nil and object.attacherVehicle ~= nil then |
3512 | if object.isDetachAllowed == nil or object:isDetachAllowed() then |
3513 | local implementIndex = object.attacherVehicle:getImplementIndexByObject(object); |
3514 | if implementIndex ~= nil then |
3515 | object.attacherVehicle:detachImplement(implementIndex); |
3516 | return true; |
3517 | end |
3518 | else |
3519 | self.showDetachingNotAllowedTime = 2000; |
3520 | return false; |
3521 | end; |
3522 | end; |
3523 | end; |
3524 | return false; |
3525 | end; |
3526 | |
3527 | function Vehicle:detachingIsPossible() |
3528 | if self.selectedImplement ~= nil then |
3529 | local object = self.selectedImplement.object; |
3530 | if object ~= nil and object.attacherVehicle ~= nil and object:isDetachAllowed() then |
3531 | local implementIndex = object.attacherVehicle:getImplementIndexByObject(object); |
3532 | if implementIndex ~= nil then |
3533 | return true; |
3534 | end |
3535 | end; |
3536 | end; |
3537 | return false; |
3538 | end; |
3539 | |
3540 | function Vehicle:handleLowerImplementEvent() |
3541 | if self.selectedImplement ~= nil and self.selectedImplement.object ~= nil and self.selectedImplement.object.attacherVehicle ~= nil then |
3542 | local object = self.selectedImplement.object; |
3543 | local jointDesc = object.attacherVehicle.attacherJoints[self.selectedImplement.jointDescIndex]; |
3544 | if object.attacherJoint.allowsLowering and jointDesc.allowsLowering then |
3545 | object.attacherVehicle:setJointMoveDown(self.selectedImplement.jointDescIndex, not jointDesc.moveDown, false); |
3546 | end; |
3547 | end; |
3548 | end; |
3549 | |
3550 | function Vehicle:getImplementIndexByJointDescIndex(jointDescIndex) |
3551 | for i=1, table.getn(self.attachedImplements) do |
3552 | if self.attachedImplements[i].jointDescIndex == jointDescIndex then |
3553 | return i; |
3554 | end; |
3555 | end; |
3556 | return nil; |
3557 | end; |
3558 | |
3559 | function Vehicle:getImplementIndexByObject(object) |
3560 | for i=1, table.getn(self.attachedImplements) do |
3561 | if self.attachedImplements[i].object == object then |
3562 | return i; |
3563 | end; |
3564 | end; |
3565 | return nil; |
3566 | end; |
3567 | |
3568 | function Vehicle:getImplementByObject(object) |
3569 | for i=1, table.getn(self.attachedImplements) do |
3570 | if self.attachedImplements[i].object == object then |
3571 | return self.attachedImplements[i]; |
3572 | end; |
3573 | end; |
3574 | return nil; |
3575 | end; |
3576 | |
3577 | function Vehicle:setLightsVisibility(visibility, noEventSend) |
3578 | -- fallback for old scripts |
3579 | if visibility then |
3580 | self:setLightsTypesMask(1, noEventSend); |
3581 | else |
3582 | self:setLightsTypesMask(0, noEventSend); |
3583 | end |
3584 | end |
3585 | |
3586 | function Vehicle:setLightsTypesMask(lightsTypesMask, noEventSend) |
3587 | |
3588 | if lightsTypesMask ~= self.lightsTypesMask then |
3589 | if noEventSend == nil or noEventSend == false then |
3590 | if g_server ~= nil then |
3591 | g_server:broadcastEvent(SteerableToggleLightEvent:new(self, lightsTypesMask), nil, nil, self); |
3592 | else |
3593 | g_client:getServerConnection():sendEvent(SteerableToggleLightEvent:new(self, lightsTypesMask)); |
3594 | end; |
3595 | end; |
3596 | |
3597 | self.lightsTypesMask = lightsTypesMask; |
3598 | self.realLightsActive = (lightsTypesMask > 0 and self:getIsActiveForLights()); |
3599 | |
3600 | for _,light in pairs(self.lights) do |
3601 | local lightActive = bitAND(lightsTypesMask, 2^light.lightType) ~= 0; |
3602 | if light.decoration ~= nil then |
3603 | setVisibility(light.decoration, lightActive); |
3604 | end |
3605 | if light.fakeLight ~= nil then |
3606 | setVisibility(light.fakeLight, lightActive and not self.realLightsActive); |
3607 | end |
3608 | -- the real lights are turned on separately |
3609 | if light.realLight ~= nil then |
3610 | setVisibility(light.realLight, false); |
3611 | end |
3612 | end |
3613 | |
3614 | if self.realLightsActive then |
3615 | self:activateRealLights(); |
3616 | end |
3617 | |
3618 | for _,v in pairs(self.attachedImplements) do |
3619 | if v.object ~= nil then |
3620 | v.object:setLightsTypesMask(lightsTypesMask, true); |
3621 | end |
3622 | end; |
3623 | for _,v in pairs(self.specializations) do |
3624 | if v.setLightsTypesMask ~= nil then |
3625 | v.setLightsTypesMask(self, lightsTypesMask); |
3626 | end; |
3627 | end; |
3628 | end; |
3629 | end; |
3630 | |
3631 | function Vehicle:activateRealLights() |
3632 | if self.lightsTypesMask > 0 then |
3633 | local highestLightsType = bitHighestSet(self.lightsTypesMask); |
3634 | local numActiveRealLights = 0; |
3635 | local numLights = table.getn(self.lights); |
3636 | for lightType=highestLightsType,0,-1 do |
3637 | if bitAND(self.lightsTypesMask, 2^lightType) ~= 0 then |
3638 | for i=numLights,1,-1 do |
3639 | local light = self.lights[i]; |
3640 | if light.lightType == lightType then |
3641 | if numActiveRealLights < self.maxNumRealLights and light.realLight ~= nil then |
3642 | setVisibility(light.realLight, true); |
3643 | numActiveRealLights = numActiveRealLights + 1; |
3644 | elseif light.fakeLight ~= nil and light.useFakeLightIfNotReal then |
3645 | setVisibility(light.fakeLight, true); |
3646 | end |
3647 | end |
3648 | end |
3649 | end |
3650 | end |
3651 | end |
3652 | end |
3653 | |
3654 | function Vehicle:setBeaconLightsVisibility(visibility, noEventSend) |
3655 | if visibility ~= self.beaconLightsActive then |
3656 | |
3657 | if noEventSend == nil or noEventSend == false then |
3658 | if g_server ~= nil then |
3659 | g_server:broadcastEvent(VehicleSetBeaconLightEvent:new(self, visibility), nil, nil, self); |
3660 | else |
3661 | g_client:getServerConnection():sendEvent(VehicleSetBeaconLightEvent:new(self, visibility)); |
3662 | end; |
3663 | end; |
3664 | |
3665 | self.beaconLightsActive = visibility; |
3666 | |
3667 | for _, beaconLight in pairs(self.beaconLights) do |
3668 | setVisibility(beaconLight.node, visibility); |
3669 | end; |
3670 | for _,v in pairs(self.attachedImplements) do |
3671 | if v.object ~= nil then |
3672 | v.object:setBeaconLightsVisibility(visibility, true); |
3673 | end |
3674 | end; |
3675 | for _,v in pairs(self.specializations) do |
3676 | if v.setBeaconLightsVisibility ~= nil then |
3677 | v.setBeaconLightsVisibility(self, visibility); |
3678 | end; |
3679 | end; |
3680 | end; |
3681 | end; |
3682 | |
3683 | function Vehicle:setTurnSignalState(state, noEventSend) |
3684 | if state ~= self.turnSignalState then |
3685 | if noEventSend == nil or noEventSend == false then |
3686 | if g_server ~= nil then |
3687 | g_server:broadcastEvent(VehicleSetTurnSignalEvent:new(self, state), nil, nil, self); |
3688 | else |
3689 | g_client:getServerConnection():sendEvent(VehicleSetTurnSignalEvent:new(self, state)); |
3690 | end; |
3691 | end; |
3692 | |
3693 | for _, signal in pairs(self.turnSignals.left) do |
3694 | setVisibility(signal, state == Vehicle.TURNSIGNAL_LEFT or state == Vehicle.TURNSIGNAL_HAZARD); |
3695 | end; |
3696 | for _, signal in pairs(self.turnSignals.right) do |
3697 | setVisibility(signal, state == Vehicle.TURNSIGNAL_RIGHT or state == Vehicle.TURNSIGNAL_HAZARD); |
3698 | end; |
3699 | |
3700 | for _,v in pairs(self.attachedImplements) do |
3701 | if v.object ~= nil then |
3702 | v.object:setTurnSignalState(state, true); |
3703 | end |
3704 | end; |
3705 | for _,v in pairs(self.specializations) do |
3706 | if v.setTurnSignalState ~= nil then |
3707 | v.setTurnSignalState(self, state); |
3708 | end; |
3709 | end; |
3710 | |
3711 | self.turnSignalState = state; |
3712 | end; |
3713 | end; |
3714 | |
3715 | function Vehicle:setBrakeLightsVisibility(visibility) |
3716 | if visibility ~= self.brakeLightsVisibility then |
3717 | self.brakeLightsVisibility = visibility; |
3718 | for _, brakeLight in pairs(self.brakeLights) do |
3719 | setVisibility(brakeLight, visibility); |
3720 | end |
3721 | for _,v in pairs(self.attachedImplements) do |
3722 | if v.object ~= nil then |
3723 | v.object:setBrakeLightsVisibility(visibility); |
3724 | end |
3725 | end |
3726 | for _,v in pairs(self.specializations) do |
3727 | if v.setBrakeLightsVisibility ~= nil then |
3728 | v.setBrakeLightsVisibility(self, visibility); |
3729 | end |
3730 | end |
3731 | end |
3732 | end |
3733 | |
3734 | function Vehicle:setReverseLightsVisibility(visibility) |
3735 | if visibility ~= self.reverseLightsVisibility then |
3736 | self.reverseLightsVisibility = visibility; |
3737 | for _, reverseLight in pairs(self.reverseLights) do |
3738 | setVisibility(reverseLight, visibility); |
3739 | end |
3740 | for _,v in pairs(self.attachedImplements) do |
3741 | if v.object ~= nil then |
3742 | v.object:setReverseLightsVisibility(visibility); |
3743 | end |
3744 | end |
3745 | for _,v in pairs(self.specializations) do |
3746 | if v.setReverseLightsVisibility ~= nil then |
3747 | v.setReverseLightsVisibility(self, visibility); |
3748 | end |
3749 | end |
3750 | end |
3751 | end |
3752 | |
3753 | function Vehicle:getIsActiveForInput(onlyTrueIfSelected) |
3754 | if g_gui.currentGui ~= nil or g_currentMission.isPlayerFrozen or self.isHired then |
3755 | return false; |
3756 | end; |
3757 | |
3758 | if self.isEntered then |
3759 | if onlyTrueIfSelected == nil or onlyTrueIfSelected then |
3760 | return self.selectedImplement == nil; |
3761 | else |
3762 | return true; |
3763 | end; |
3764 | end; |
3765 | |
3766 | if self.attacherVehicle ~= nil then |
3767 | if onlyTrueIfSelected == nil or onlyTrueIfSelected then |
3768 | return self.isSelected and self.attacherVehicle:getIsActiveForInput(false); |
3769 | else |
3770 | return self.attacherVehicle:getIsActiveForInput(false); |
3771 | end; |
3772 | end; |
3773 | return false; |
3774 | end; |
3775 | |
3776 | function Vehicle:getIsActiveForSound() |
3777 | if self.isClient then |
3778 | if self.isEntered then |
3779 | return true; |
3780 | end; |
3781 | if self.attacherVehicle ~= nil then |
3782 | return self.attacherVehicle:getIsActiveForSound(); |
3783 | end; |
3784 | end |
3785 | return false; |
3786 | end; |
3787 | |
3788 | function Vehicle:getIsActiveForLights() |
3789 | if self.isEntered and self:canToggleLight() then |
3790 | return true; |
3791 | end; |
3792 | if self.attacherVehicle ~= nil and (self.isSteerable == nil or self.isSteerable == false) then |
3793 | return self.attacherVehicle:getIsActiveForLights(); |
3794 | end; |
3795 | return false; |
3796 | end; |
3797 | |
3798 | function Vehicle:canToggleLight() |
3799 | if not g_currentMission.controlPlayer and g_currentMission.controlledVehicle == self then |
3800 | return true; |
3801 | else |
3802 | return false; |
3803 | end; |
3804 | end; |
3805 | |
3806 | function Vehicle:getIsActive() |
3807 | if self.isBroken then |
3808 | return false; |
3809 | end; |
3810 | if self.isEntered or self.isControlled or self.forceIsActive then |
3811 | return true; |
3812 | end; |
3813 | if self.attacherVehicle ~= nil then |
3814 | return self.attacherVehicle:getIsActive(); |
3815 | end; |
3816 | return false; |
3817 | end; |
3818 | |
3819 | function Vehicle:hasInputConflictWithSelection(inputs) |
3820 | if inputs == nil then |
3821 | inputs = self.conflictCheckedInputs; |
3822 | end |
3823 | local rootAttacherVehicle = self:getRootAttacherVehicle(); |
3824 | local curVehicle = rootAttacherVehicle; |
3825 | if rootAttacherVehicle.selectedImplement ~= nil then |
3826 | curVehicle = rootAttacherVehicle.selectedImplement.object; |
3827 | end |
3828 | -- check selected implement and all of its attachers (up to our vehicle or root) for conflicts |
3829 | -- implements have a higher priority if they have a smaller distance to the selected implement in the tree |
3830 | while curVehicle ~= nil and curVehicle ~= self do |
3831 | if curVehicle:hasInputConflict(inputs, false) then |
3832 | return true; |
3833 | end |
3834 | curVehicle = curVehicle.attacherVehicle; |
3835 | end |
3836 | -- no conflcit if we are between the selection and the root and have no conflict with implements closer to the selection |
3837 | if curVehicle == self then |
3838 | return false; |
3839 | end |
3840 | |
3841 | -- not within the selection and the root, test for conflict to all other implements |
3842 | return rootAttacherVehicle:hasInputConflict(inputs, true, self); |
3843 | end |
3844 | |
3845 | function Vehicle:hasInputConflict(inputs, recursive, excludedVehicle) |
3846 | if next(self.conflictCheckedInputs) ~= nil and self ~= excludedVehicle then |
3847 | for input,_ in pairs(inputs) do |
3848 | if InputBinding.getHasInputConflict(input, self.conflictCheckedInputs) then |
3849 | return true; |
3850 | end |
3851 | end |
3852 | end |
3853 | if recursive then |
3854 | for _,v in pairs(self.attachedImplements) do |
3855 | if v.object ~= nil and v.object:hasInputConflict(inputs, recursive, excludedVehicle) then |
3856 | return true; |
3857 | end |
3858 | end |
3859 | end |
3860 | return false; |
3861 | end |
3862 | |
3863 | function Vehicle:addConflictCheckedInput(actionIndex) |
3864 | self.conflictCheckedInputs[actionIndex] = true; |
3865 | end |
3866 | |
3867 | function Vehicle:removeConflictCheckedInput(actionIndex) |
3868 | self.conflictCheckedInputs[actionIndex] = nil; |
3869 | end |
3870 | |
3871 | function Vehicle:getIsHired() |
3872 | if self.isHired then |
3873 | return true; |
3874 | elseif self.attacherVehicle ~= nil then |
3875 | return self.attacherVehicle:getIsHired(); |
3876 | end; |
3877 | return false; |
3878 | end; |
3879 | |
3880 | function Vehicle:getOwner() |
3881 | if self.owner ~= nil then |
3882 | return self.owner; |
3883 | end; |
3884 | if self.attacherVehicle ~= nil then |
3885 | return self.attacherVehicle:getOwner(); |
3886 | end; |
3887 | return nil; |
3888 | end; |
3889 | |
3890 | function Vehicle:onDeactivateAttachments() |
3891 | for _,v in pairs(self.attachedImplements) do |
3892 | if v.object ~= nil then |
3893 | v.object:onDeactivate(); |
3894 | end |
3895 | end; |
3896 | end; |
3897 | |
3898 | function Vehicle:onActivateAttachments() |
3899 | for _,v in pairs(self.attachedImplements) do |
3900 | if v.object ~= nil then |
3901 | v.object:onActivate(); |
3902 | end |
3903 | end; |
3904 | end; |
3905 | |
3906 | function Vehicle:onDeactivateAttachmentsSounds() |
3907 | for _,v in pairs(self.attachedImplements) do |
3908 | if v.object ~= nil then |
3909 | v.object:onDeactivateSounds(); |
3910 | end |
3911 | end; |
3912 | end; |
3913 | |
3914 | function Vehicle:onDeactivateAttachmentsLights() |
3915 | for _,v in pairs(self.attachedImplements) do |
3916 | if v.object ~= nil then |
3917 | v.object:onDeactivateLights(); |
3918 | end |
3919 | end; |
3920 | end; |
3921 | |
3922 | function Vehicle:onActivate() |
3923 | self:onActivateAttachments(); |
3924 | for k,v in pairs(self.specializations) do |
3925 | if v.onActivate ~= nil then |
3926 | v.onActivate(self); |
3927 | end; |
3928 | end; |
3929 | end; |
3930 | |
3931 | function Vehicle:onDeactivate() |
3932 | self:onDeactivateAttachments(); |
3933 | for k,v in pairs(self.specializations) do |
3934 | if v.onDeactivate ~= nil then |
3935 | v.onDeactivate(self); |
3936 | end; |
3937 | end; |
3938 | |
3939 | self:onDeactivateSounds(); |
3940 | self:onDeactivateLights(); |
3941 | end; |
3942 | |
3943 | function Vehicle:onDeactivateSounds() |
3944 | if self.isClient then |
3945 | Utils.stopSample(self.sampleHydraulic); |
3946 | end; |
3947 | self:onDeactivateAttachmentsSounds(); |
3948 | for k,v in pairs(self.specializations) do |
3949 | if v.onDeactivateSounds ~= nil then |
3950 | v.onDeactivateSounds(self); |
3951 | end; |
3952 | end; |
3953 | end; |
3954 | |
3955 | function Vehicle:onDeactivateLights() |
3956 | self:onDeactivateAttachmentsLights(); |
3957 | for k,v in pairs(self.specializations) do |
3958 | if v.onDeactivateLights ~= nil then |
3959 | v.onDeactivateLights(self); |
3960 | end; |
3961 | end; |
3962 | |
3963 | self:setLightsTypesMask(0, true); |
3964 | self:setBeaconLightsVisibility(false, true); |
3965 | self:setTurnSignalState(Vehicle.TURNSIGNAL_OFF, true); |
3966 | self:setBrakeLightsVisibility(false); |
3967 | self:setReverseLightsVisibility(false); |
3968 | end; |
3969 | |
3970 | function Vehicle:isLowered(default) |
3971 | if self.attacherVehicle ~= nil then |
3972 | local implement = self.attacherVehicle:getImplementByObject(self); |
3973 | if implement ~= nil then |
3974 | local jointDesc = self.attacherVehicle.attacherJoints[implement.jointDescIndex]; |
3975 | if jointDesc.allowsLowering then |
3976 | return jointDesc.moveDown and self.attacherVehicle:isLowered(true); |
3977 | end; |
3978 | end; |
3979 | end; |
3980 | return default; |
3981 | end; |
3982 | |
3983 | function Vehicle:setJointMoveDown(jointDescIndex, moveDown, noEventSend) |
3984 | VehicleLowerImplementEvent.sendEvent(self, jointDescIndex, moveDown, noEventSend) |
3985 | local jointDesc = self.attacherJoints[jointDescIndex] |
3986 | jointDesc.moveDown = moveDown; |
3987 | |
3988 | local implementIndex = self:getImplementIndexByJointDescIndex(jointDescIndex); |
3989 | if implementIndex ~= nil then |
3990 | local implement = self.attachedImplements[implementIndex]; |
3991 | if implement.object ~= nil and implement.object.onSetLowered ~= nil then |
3992 | implement.object:onSetLowered(moveDown) |
3993 | end; |
3994 | end; |
3995 | end; |
3996 | |
3997 | function Vehicle:getIsVehicleNode(nodeId) |
3998 | return self.vehicleNodes[nodeId] ~= nil; |
3999 | end; |
4000 | |
4001 | function Vehicle:getIsAttachedVehicleNode(nodeId) |
4002 | if self.vehicleNodes[nodeId] ~= nil then |
4003 | return true; |
4004 | end; |
4005 | for _,v in pairs(self.attachedImplements) do |
4006 | if v.object ~= nil and v.object:getIsAttachedVehicleNode(nodeId) then |
4007 | return true; |
4008 | end; |
4009 | end; |
4010 | return false; |
4011 | end; |
4012 | |
4013 | function Vehicle:getIsAttachedTo(vehicle) |
4014 | if vehicle == self then |
4015 | return true; |
4016 | end; |
4017 | if self.attacherVehicle ~= nil then |
4018 | return self.attacherVehicle:getIsAttachedTo(vehicle); |
4019 | end; |
4020 | return false; |
4021 | end; |
4022 | |
4023 | function Vehicle:getIsDynamicallyMountedNode(nodeId) |
4024 | if self.dynamicMountedObjects ~= nil then |
4025 | local object = g_currentMission:getNodeObject(nodeId); |
4026 | if object == nil then |
4027 | object = g_currentMission.nodeToVehicle[nodeId]; |
4028 | end; |
4029 | if object ~= nil then |
4030 | if self.dynamicMountedObjects[object] ~= nil or self.pendingDynamicMountObjects[object] ~= nil then |
4031 | return true; |
4032 | end; |
4033 | end; |
4034 | end; |
4035 | for _,v in pairs(self.attachedImplements) do |
4036 | if v.object ~= nil then |
4037 | if v.object:getIsDynamicallyMountedNode(nodeId) then |
4038 | return true; |
4039 | end; |
4040 | end; |
4041 | end; |
4042 | end; |
4043 | |
4044 | function Vehicle:getRootAttacherVehicle() |
4045 | local rootVehicle = self; |
4046 | while rootVehicle.attacherVehicle ~= nil do |
4047 | rootVehicle = rootVehicle.attacherVehicle; |
4048 | end |
4049 | return rootVehicle; |
4050 | end |
4051 | |
4052 | function Vehicle:setComponentsVisibility(visibility) |
4053 | if self.componentsVisibility ~= visibility then |
4054 | self.componentsVisibility = visibility; |
4055 | for k,v in pairs(self.components) do |
4056 | setVisibility(v.node, visibility); |
4057 | if visibility then |
4058 | addToPhysics(v.node); |
4059 | else |
4060 | removeFromPhysics(v.node); |
4061 | end; |
4062 | end; |
4063 | end; |
4064 | end; |
4065 | |
4066 | function Vehicle:setComponentJointRotLimit(componentJoint, axis, minLimit, maxLimit) |
4067 | if self.isServer then |
4068 | componentJoint.rotLimit[axis] = maxLimit; |
4069 | componentJoint.rotMinLimit[axis] = minLimit; |
4070 | |
4071 | if minLimit <= maxLimit then |
4072 | setJointRotationLimit(componentJoint.jointIndex, axis-1, true, minLimit, maxLimit); |
4073 | else |
4074 | setJointRotationLimit(componentJoint.jointIndex, axis-1, false, 0, 0); |
4075 | end |
4076 | end |
4077 | end |
4078 | |
4079 | function Vehicle:setComponentJointTransLimit(componentJoint, axis, minLimit, maxLimit) |
4080 | if self.isServer then |
4081 | componentJoint.transLimit[axis] = maxLimit; |
4082 | componentJoint.transMinLimit[axis] = minLimit; |
4083 | |
4084 | if minLimit <= maxLimit then |
4085 | setJointTranslationLimit(componentJoint.jointIndex, axis-1, true, minLimit, maxLimit); |
4086 | else |
4087 | setJointTranslationLimit(componentJoint.jointIndex, axis-1, false, 0, 0); |
4088 | end |
4089 | end |
4090 | end |
4091 | |
4092 | function Vehicle:loadAreaFromXML(area, xmlFile, key) |
4093 | local start = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#startIndex")); |
4094 | local width = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#widthIndex")); |
4095 | local height = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#heightIndex")); |
4096 | |
4097 | if start ~= nil and width ~= nil and height ~= nil then |
4098 | area.start = start; |
4099 | area.width = width; |
4100 | area.height = height; |
4101 | return true; |
4102 | end; |
4103 | return false; |
4104 | end; |
4105 | |
4106 | function Vehicle:getIsAreaActive(area) |
4107 | return true; |
4108 | end; |
4109 | |
4110 | function Vehicle:loadSpeedRotatingPartFromXML(speedRotatingPart, xmlFile, key) |
4111 | speedRotatingPart.repr = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#index")); |
4112 | speedRotatingPart.shaderNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#shaderNode")); |
4113 | if speedRotatingPart.shaderNode ~= nil then |
4114 | speedRotatingPart.useShaderRotation = Utils.getNoNil(getXMLBool(xmlFile, key.."#useRotation"), true); |
4115 | local scale = Utils.getNoNil(getXMLString(xmlFile, key.."#scrollScale"), "1 0"); |
4116 | speedRotatingPart.scrollScale = Utils.getVectorNFromString(scale, 2); |
4117 | end; |
4118 | |
4119 | if speedRotatingPart.repr == nil and speedRotatingPart.shaderNode == nil then |
4120 | print("Warning: Invalid speedrotationpart '"..tostring(getXMLString(xmlFile, key.."#index")).."'! in '"..self.configFileName.."'"); |
4121 | return false; |
4122 | end; |
4123 | speedRotatingPart.driveNode = Utils.getNoNil(Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#driveNode")), speedRotatingPart.repr); |
4124 | |
4125 | local componentIndex = getXMLInt(xmlFile, key.."#refComponentIndex"); |
4126 | if componentIndex ~= nil and self.components[componentIndex] ~= nil then |
4127 | speedRotatingPart.componentNode = self.components[componentIndex].node; |
4128 | else |
4129 | local node = Utils.getNoNil(speedRotatingPart.driveNode, speedRotatingPart.shaderNode) |
4130 | speedRotatingPart.componentNode = self:getParentComponent(node); |
4131 | end; |
4132 | |
4133 | |
4134 | speedRotatingPart.xDrive = 0; |
4135 | local wheelIndex = getXMLInt(xmlFile, key.."#wheelIndex"); |
4136 | if wheelIndex ~= nil then |
4137 | local wheel = self.wheels[wheelIndex + 1]; |
4138 | if wheel == nil then |
4139 | print("SpeedRotatingParts: Invalid wheel index ("..tostring(wheelIndex)..")"); |
4140 | return false; |
4141 | end; |
4142 | speedRotatingPart.wheel = wheel; |
4143 | speedRotatingPart.lastWheelXRot = 0; |
4144 | end; |
4145 | if speedRotatingPart.wheel == nil then |
4146 | speedRotatingPart.speedRefNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key .. "#speedRefNode")); |
4147 | end |
4148 | |
4149 | speedRotatingPart.dirRefNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#dirRefNode")); |
4150 | speedRotatingPart.dirFrameNode = Utils.indexToObject(self.components, getXMLString(xmlFile, key.."#dirFrameNode")); |
4151 | speedRotatingPart.alignDirection = Utils.getNoNil(getXMLBool(xmlFile, key .. "#alignDirection"), false); |
4152 | |
4153 | speedRotatingPart.versatileYRot = Utils.getNoNil(getXMLBool(xmlFile, key .. "#versatileYRot"), false); |
4154 | if speedRotatingPart.versatileYRot and speedRotatingPart.repr == nil then |
4155 | print("Warning: Versatile speedrotationpart does not support shaderNodes in '"..self.configFileName.."'"); |
4156 | return false; |
4157 | end; |
4158 | |
4159 | local minYRot = getXMLFloat(xmlFile, key .. "#minYRot"); |
4160 | if minYRot ~= nil then |
4161 | speedRotatingPart.minYRot = math.rad(minYRot); |
4162 | end; |
4163 | local maxYRot = getXMLFloat(xmlFile, key .. "#maxYRot"); |
4164 | if maxYRot ~= nil then |
4165 | speedRotatingPart.maxYRot = math.rad(maxYRot); |
4166 | end; |
4167 | speedRotatingPart.steeringAngle = 0; |
4168 | |
4169 | speedRotatingPart.wheelScale = getXMLFloat(xmlFile, key .. "#wheelScale"); |
4170 | if speedRotatingPart.wheelScale == nil then |
4171 | local baseRadius = 1.0; |
4172 | local radius = 1.0; |
4173 | if speedRotatingPart.wheel ~= nil then |
4174 | baseRadius = speedRotatingPart.wheel.radius; |
4175 | radius = speedRotatingPart.wheel.radius; |
4176 | end; |
4177 | speedRotatingPart.wheelScale = baseRadius / Utils.getNoNil(getXMLFloat(xmlFile, key.."#radius"), radius); |
4178 | end; |
4179 | |
4180 | speedRotatingPart.wheelScaleBackup = speedRotatingPart.wheelScale; |
4181 | |
4182 | speedRotatingPart.onlyActiveWhenLowered = Utils.getNoNil(getXMLBool(xmlFile, key .. "#onlyActiveWhenLowered"), false); |
4183 | speedRotatingPart.stopIfNotActive = Utils.getNoNil(getXMLBool(xmlFile, key .. "#stopIfNotActive"), false); |
4184 | speedRotatingPart.lastSpeed = 0; |
4185 | speedRotatingPart.lastDir = 1; |
4186 | |
4187 | return true; |
4188 | end; |
4189 | |
4190 | function Vehicle:getIsSpeedRotatingPartActive(speedRotatingPart) |
4191 | if speedRotatingPart.onlyActiveWhenLowered and not self:isLowered(true) then |
4192 | return false; |
4193 | end; |
4194 | |
4195 | return true; |
4196 | end; |
4197 | |
4198 | function Vehicle:getSpeedRotatingPartDirection(speedRotatingPart) |
4199 | return 1; |
4200 | end; |
4201 | |
4202 | function Vehicle:loadDynamicallyPartsFromXML(dynamicallyLoadedPart, xmlFile, key) |
4203 | local filename = getXMLString(xmlFile, key .. "#filename"); |
4204 | if filename ~= nil then |
4205 | dynamicallyLoadedPart.filename = filename; |
4206 | local i3dNode = Utils.loadSharedI3DFile(filename, self.baseDirectory, false, false, false); |
4207 | if i3dNode ~= 0 then |
4208 | local node = Utils.indexToObject(i3dNode, Utils.getNoNil(getXMLString(xmlFile, key .. "#node"), "0|0")); |
4209 | local linkNode = Utils.indexToObject(self.components, Utils.getNoNil(getXMLString(xmlFile, key .. "#linkNode"), "0>")); |
4210 | |
4211 | local x,y,z = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#position")); |
4212 | if x ~= nil and y ~= nil and z ~= nil then |
4213 | setTranslation(node, x,y,z); |
4214 | end; |
4215 | local rotX, rotY, rotZ = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#rotation")); |
4216 | if rotX ~= nil and rotY ~= nil and rotZ ~= nil then |
4217 | rotX = Utils.degToRad(rotX); |
4218 | rotY = Utils.degToRad(rotY); |
4219 | rotZ = Utils.degToRad(rotZ); |
4220 | setRotation(node, rotX, rotY, rotZ); |
4221 | end; |
4222 | |
4223 | local shaderParameterName = getXMLString(xmlFile, key .. "#shaderParameterName"); |
4224 | local x,y,z,w = Utils.getVectorFromString(getXMLString(xmlFile, key .. "#shaderParameter")); |
4225 | if shaderParameterName ~= nil and x ~= nil and y ~= nil and z ~= nil and w ~= nil then |
4226 | setShaderParameter(node, shaderParameterName, x, y, z, w, false); |
4227 | end; |
4228 | |
4229 | link(linkNode, node); |
4230 | delete(i3dNode); |
4231 | return true; |
4232 | end; |
4233 | end; |
4234 | |
4235 | return false; |
4236 | end; |
4237 | |
4238 | function Vehicle:getLastSpeed(useAttacherVehicleSpeed) |
4239 | if useAttacherVehicleSpeed then |
4240 | if self.attacherVehicle ~= nil then |
4241 | return self.attacherVehicle:getLastSpeed(true); |
4242 | end; |
4243 | end; |
4244 | |
4245 | return self.lastSpeed * 3600; |
4246 | end; |
4247 | |
4248 | function Vehicle:getDirectionSnapAngle() |
4249 | local maxAngle = 0; |
4250 | for _,v in pairs(self.attachedImplements) do |
4251 | if v.object ~= nil then |
4252 | maxAngle = math.max(maxAngle, v.object:getDirectionSnapAngle()); |
4253 | end |
4254 | end |
4255 | return maxAngle; |
4256 | end |
4257 | |
4258 | function Vehicle:updatePowerTakeoff(implement, dt) |
4259 | local jointDesc = self.attacherJoints[implement.jointDescIndex]; |
4260 | if jointDesc.ptoActive then |
4261 | local object = implement.object; |
4262 | |
4263 | local ptoRootNode = jointDesc.ptoOutput.rootNode; |
4264 | local ptoAttachNode = jointDesc.ptoOutput.attachNode; |
4265 | local ptoDirAndScaleNode = jointDesc.ptoOutput.dirAndScaleNode; |
4266 | local ptoBaseDistance = jointDesc.ptoOutput.baseDistance; |
4267 | if object.ptoInput.rootNode ~= nil then |
4268 | ptoRootNode = object.ptoInput.rootNode; |
4269 | ptoAttachNode = object.ptoInput.attachNode |
4270 | ptoDirAndScaleNode = object.ptoInput.dirAndScaleNode |
4271 | ptoBaseDistance = object.ptoInput.baseDistance |
4272 | end |
4273 | |
4274 | if object.ptoInput.rotSpeed ~= 0 and object:getIsPowerTakeoffActive() then |
4275 | jointDesc.ptoOutput.rotation = (jointDesc.ptoOutput.rotation + dt*object.ptoInput.rotSpeed) % (2*math.pi); |
4276 | setRotation(ptoRootNode, 0, 0, jointDesc.ptoOutput.rotation); |
4277 | setRotation(ptoAttachNode, 0, 0, jointDesc.ptoOutput.rotation); |
4278 | end |
4279 | |
4280 | local x,y,z = getWorldTranslation(ptoAttachNode); |
4281 | local dx,dy,dz = worldToLocal(ptoRootNode, x,y,z); |
4282 | setDirection(ptoDirAndScaleNode, dx, dy, dz, 0, 1, 0); |
4283 | |
4284 | local _,_,dist = worldToLocal(ptoDirAndScaleNode, x,y,z); |
4285 | setScale(ptoDirAndScaleNode, 1, 1, dist/ptoBaseDistance); |
4286 | end |
4287 | end |
4288 | |
4289 | function Vehicle:getParentComponent(node) |
4290 | while node ~= 0 do |
4291 | if self:getIsVehicleNode(node) then |
4292 | return node; |
4293 | end; |
4294 | |
4295 | node = getParent(node); |
4296 | end; |
4297 | |
4298 | return 0; |
4299 | end; |
4300 | |
4301 | function Vehicle:setColor(r,g,b,a) |
4302 | self.color = {r,g,b,a}; |
4303 | if self.colorNodes ~= nil then |
4304 | for _, node in pairs(self.colorNodes) do |
4305 | setShaderParameter(node, "colorScale", r, g, b, a, false); |
4306 | end; |
4307 | else |
4308 | for _, comp in pairs(self.components) do |
4309 | Utils.setShaderParameterRec(comp.node, "colorScale", r, g, b, a); |
4310 | end; |
4311 | end; |
4312 | end; |
4313 | |
4314 | function Vehicle:getIsOnField() |
4315 | local wx,wy,wz = getWorldTranslation(self.components[1].node); |
4316 | local densityBits = getDensityAtWorldPos(g_currentMission.terrainDetailId, wx,wz); |
4317 | return densityBits ~= 0; |
4318 | end; |
4319 | |
4320 | function Vehicle:getTotalMass(addAttachables, addOnlyNonWheelAttachables) |
4321 | local mass = 0; |
4322 | if self.isServer then |
4323 | for _, comp in pairs(self.components) do |
4324 | mass = mass + getMass(comp.node); |
4325 | end; |
4326 | else |
4327 | mass = self.serverMass; |
4328 | for k,v in pairs(self.specializations) do |
4329 | if v.getAdditiveClientMass ~= nil then |
4330 | mass = mass + v.getAdditiveClientMass(self); |
4331 | end; |
4332 | end; |
4333 | end; |
4334 | |
4335 | if addAttachables then |
4336 | if addOnlyNonWheelAttachables == nil then |
4337 | addOnlyNonWheelAttachables = true; |
4338 | end |
4339 | for _, implement in pairs(self.attachedImplements) do |
4340 | if implement.object ~= nil and (table.getn(implement.object.wheels) == 0 or not addOnlyNonWheelAttachables) then |
4341 | mass = mass + implement.object:getTotalMass(addAttachables, addOnlyNonWheelAttachables); |
4342 | end; |
4343 | end |
4344 | end |
4345 | |
4346 | return mass; |
4347 | end; |
4348 | |
4349 | function Vehicle:getTotalBrakeForce(addAttachables) |
4350 | local totalBrakeForce = 0; |
4351 | |
4352 | local brakeForce; |
4353 | if self.motor ~= nil then |
4354 | brakeForce = self.motor.brakeForce; |
4355 | elseif self.brakeForce ~= nil then |
4356 | brakeForce = self.brakeForce; |
4357 | end |
4358 | if brakeForce ~= nil then |
4359 | for _, wheel in ipairs(self.wheels) do |
4360 | if wheel.hasGroundContact then |
4361 | totalBrakeForce = totalBrakeForce + brakeForce/wheel.radius; |
4362 | end |
4363 | end |
4364 | end |
4365 | if addAttachables then |
4366 | for _, implement in pairs(self.attachedImplements) do |
4367 | if implement.object ~= nil then |
4368 | totalBrakeForce = totalBrakeForce + implement.object:getTotalBrakeForce(true); |
4369 | end |
4370 | end |
4371 | end |
4372 | |
4373 | return totalBrakeForce; |
4374 | end |
4375 | |
4376 | function Vehicle.drawDebugRendering(self) |
4377 | local x,_,z = getWorldTranslation(self.components[1].node); |
4378 | local fieldOwned = g_currentMission:getIsFieldOwnedAtWorldPos(x,z); |
4379 | local str1 = ""; |
4380 | local str2 = ""; |
4381 | local totalMass = self:getTotalMass(true, false); |
4382 | local vehicleMass = self:getTotalMass(false); |
4383 | if self.motor ~= nil then |
4384 | local torque,_ = self.motor:getTorque(1, false)*1000; -- getTorque is in kNm |
4385 | local motorPower = self.motor.nonClampedMotorRpm*math.pi/30*torque; |
4386 | |
4387 | --local speed = self.lastSpeedReal*1000; |
4388 | --local prevSpeed = math.abs(speed)-math.abs(self.lastSpeedAcceleration*1000*1000); |
4389 | --local accPower = 0.5*totalMass*(speed*speed - prevSpeed*prevSpeed); -- The power that was used to achieve the acceleration on a flat ground |
4390 | |
4391 | str1 = str1.."motor:\n"; str2 = str2..string.format("%1.2frpm\n", self.motor.nonClampedMotorRpm); |
4392 | str1 = str1.."clutch:\n"; str2 = str2..string.format("%1.2frpm\n", self.motor.clutchRpm); |
4393 | str1 = str1.."power:\n"; str2 = str2..string.format("%1.2fhp %1.2fkW\n", motorPower/735.49875, motorPower/1000); |
4394 | str1 = str1.."gear ratio:\n"; str2 = str2..string.format("%1.2f\n", self.motor.gearRatio); |
4395 | str1 = str1.."motor load:\n"; str2 = str2..string.format("%1.2fkN %1.2fkN\n", torque/1000, self.motor.motorLoad); |
4396 | end |
4397 | local totalBrakeForce = self:getTotalBrakeForce(true); |
4398 | local vehicleBrakeForce = self:getTotalBrakeForce(false); |
4399 | |
4400 | str1 = str1.."acc[m/s2]:\n"; str2 = str2..string.format("%1.4f\n", self.lastSpeedAcceleration*1000*1000); |
4401 | str1 = str1.."vel[km/h]:\n"; str2 = str2..string.format("%1.3f\n", self:getLastSpeed()); |
4402 | str1 = str1.."mass [t]:\n"; str2 = str2..string.format("%1.3f %1.3f\n", totalMass, vehicleMass); |
4403 | str1 = str1.."brake [kN]:\n"; str2 = str2..string.format("%1.2f %1.2f\n", totalBrakeForce, vehicleBrakeForce); |
4404 | str1 = str1.."brake acc [m/s2]:\n"; str2 = str2..string.format("%1.2f %1.2f\n", totalBrakeForce/totalMass, vehicleBrakeForce/vehicleMass); |
4405 | str1 = str1.."field owned:\n"; str2 = str2..tostring(fieldOwned).."\n"; |
4406 | |
4407 | if self.isServer then |
4408 | if table.getn(self.wheels) > 0 then |
4409 | local wheelsStrs = {"\n", "longSlip\n", "latSlip\n", "load\n", "frict.\n", "compr.\n", "rpm\n"} |
4410 | --str = str .. " long. slip, lat. slip, load, compression, rpm\n"; |
4411 | for i, wheel in ipairs(self.wheels) do |
4412 | local susp = 100*(wheel.netInfo.y - wheel.netInfo.yMin)/wheel.suspTravel -20; -- If at yMin, we have -20% compression |
4413 | |
4414 | local rpm = getWheelShapeAxleSpeed(wheel.node, wheel.wheelShape)*30/math.pi; |
4415 | local longSlip, latSlip = getWheelShapeSlip(wheel.node, wheel.wheelShape); |
4416 | local gravity = 9.81; |
4417 | |
4418 | local tireLoad = getWheelShapeContactForce(wheel.node, wheel.wheelShape); |
4419 | if tireLoad ~= nil then |
4420 | local nx,ny,nz = getWheelShapeContactNormal(wheel.node, wheel.wheelShape); |
4421 | local dx,dy,dz = localDirectionToWorld(wheel.node, 0,-1,0); |
4422 | tireLoad = -tireLoad*Utils.dotProduct(dx,dy,dz, nx,ny,nz); |
4423 | |
4424 | tireLoad = tireLoad + math.max(ny*gravity, 0.0) * wheel.mass; -- add gravity force of tire |
4425 | else |
4426 | tireLoad = 0; |
4427 | end |
4428 | |
4429 | wheelsStrs[1]=wheelsStrs[1]..string.format("%d:\n", i); |
4430 | wheelsStrs[2]=wheelsStrs[2]..string.format("%2.2f\n", longSlip); |
4431 | wheelsStrs[3]=wheelsStrs[3]..string.format("%2.2f\n", latSlip); |
4432 | wheelsStrs[4]=wheelsStrs[4]..string.format("%2.1f\n", tireLoad/gravity); |
4433 | wheelsStrs[5]=wheelsStrs[5]..string.format("%2.2f\n", wheel.frictionScale*wheel.tireGroundFrictionCoeff); |
4434 | wheelsStrs[6]=wheelsStrs[6]..string.format("%1.0f%%\n", susp); |
4435 | wheelsStrs[7]=wheelsStrs[7]..string.format("%3.1f\n", rpm); |
4436 | |
4437 | --str = str .. string.format("wheel %d: %2.2f, %2.2f, %2.1f, %1.0f%%, %3.1f", i, longSlip, latSlip, tireLoad/gravity, susp, rpm) .. "\n"; |
4438 | |
4439 | -- wheel slip graphs: |
4440 | |
4441 | local longMaxSlip = 1; |
4442 | local latMaxSlip = 0.9; |
4443 | |
4444 | local sizeX = 0.11; |
4445 | local sizeY = 0.15; |
4446 | local spacingX = 0.028; |
4447 | local spacingY = 0.013; |
4448 | local x = 0.028 + (sizeX+spacingX) * (i-1); |
4449 | local longY = 1-spacingY-sizeY; |
4450 | local latY = longY-spacingY-sizeY; |
4451 | |
4452 | local numGraphValues = 20; |
4453 | |
4454 | |
4455 | -- tire forces graph |
4456 | local longGraph = wheel.debugLongitudalFrictionGraph; |
4457 | if longGraph == nil then |
4458 | longGraph = Graph:new(numGraphValues, x, longY, sizeX, sizeY, 0, 0.0001, true, "", Graph.STYLE_LINES); |
4459 | longGraph:setColor(1, 1, 1, 1); |
4460 | wheel.debugLongitudalFrictionGraph = longGraph; |
4461 | end |
4462 | |
4463 | longGraph.maxValue = 0.01; |
4464 | for s=1, numGraphValues do |
4465 | local longForce, _ = computeWheelShapeTireForces(wheel.node, wheel.wheelShape, (s-1)/(numGraphValues-1) * longMaxSlip, latSlip, tireLoad); |
4466 | longGraph:setValue(s, longForce); |
4467 | longGraph.maxValue = math.max(longGraph.maxValue, longForce); |
4468 | end |
4469 | |
4470 | -- lateral graph |
4471 | local latGraph = wheel.debugLateralFrictionGraph; |
4472 | if latGraph == nil then |
4473 | latGraph = Graph:new(numGraphValues, x, latY, sizeX, sizeY, 0, 0.0001, true, "", Graph.STYLE_LINES); |
4474 | latGraph:setColor(1, 1, 1, 1); |
4475 | wheel.debugLateralFrictionGraph = latGraph; |
4476 | |
4477 | |
4478 | --[[print("lat force:"); |
4479 | for s=1, numGraphValues do |
4480 | local o = ""; |
4481 | for s1=1, numGraphValues do |
4482 | local _, latForce = computeWheelShapeTireForces(wheel.node, wheel.wheelShape, (s1-1)/(numGraphValues-1) * longMaxSlip, (s-1)/(numGraphValues-1) * latMaxSlip, 60); |
4483 | latForce = math.abs(latForce); |
4484 | o = o..string.format("%.2f ", latForce); |
4485 | end |
4486 | print(o); |
4487 | end |
4488 | print("long force:"); |
4489 | for s=1, numGraphValues do |
4490 | local o = ""; |
4491 | for s1=1, numGraphValues do |
4492 | local longForce, _ = computeWheelShapeTireForces(wheel.node, wheel.wheelShape, (s1-1)/(numGraphValues-1) * longMaxSlip, (s-1)/(numGraphValues-1) * latMaxSlip, 60); |
4493 | longForce = math.abs(longForce); |
4494 | o = o..string.format("%.2f ", longForce); |
4495 | end |
4496 | print(o); |
4497 | end]] |
4498 | end |
4499 | latGraph.maxValue = 0.01; |
4500 | for s=1, numGraphValues do |
4501 | local _, latForce = computeWheelShapeTireForces(wheel.node, wheel.wheelShape, longSlip, (s-1)/(numGraphValues-1) * latMaxSlip, tireLoad); |
4502 | latForce = math.abs(latForce); |
4503 | latGraph:setValue(s, latForce); |
4504 | latGraph.maxValue = math.max(latGraph.maxValue, latForce); |
4505 | end |
4506 | |
4507 | |
4508 | |
4509 | local longSlipOverlay = wheel.debugLongitudalFrictionSlipOverlay; |
4510 | if longSlipOverlay == nil then |
4511 | longSlipOverlay = createImageOverlay("dataS/scripts/shared/graph_pixel.png"); |
4512 | setOverlayColor(longSlipOverlay, 0, 1, 0, 0.2); |
4513 | wheel.debugLongitudalFrictionSlipOverlay = longSlipOverlay; |
4514 | end |
4515 | local latSlipOverlay = wheel.debugLateralFrictionSlipOverlay; |
4516 | if latSlipOverlay == nil then |
4517 | latSlipOverlay = createImageOverlay("dataS/scripts/shared/graph_pixel.png"); |
4518 | setOverlayColor(latSlipOverlay, 0, 1, 0, 0.2); |
4519 | wheel.debugLateralFrictionSlipOverlay = latSlipOverlay; |
4520 | end |
4521 | |
4522 | longGraph:draw(); |
4523 | latGraph:draw(); |
4524 | |
4525 | local longForce, latForce = computeWheelShapeTireForces(wheel.node, wheel.wheelShape, longSlip, latSlip, tireLoad); |
4526 | |
4527 | renderOverlay(longSlipOverlay, x, longY, sizeX*math.min(math.abs(longSlip)/longMaxSlip, 1), sizeY * math.min(math.abs(longForce)/longGraph.maxValue, 1)); |
4528 | renderOverlay(latSlipOverlay, x, latY, sizeX*math.min(math.abs(latSlip)/latMaxSlip, 1), sizeY * math.min(math.abs(latForce)/latGraph.maxValue, 1)); |
4529 | end; |
4530 | |
4531 | local str1Height = getTextHeight(getCorrectTextSize(0.02), str1); |
4532 | Utils.renderMultiColumnText(0.015, 0.64-str1Height-0.005, getCorrectTextSize(0.02), wheelsStrs, 0.008, {RenderText.ALIGN_RIGHT,RenderText.ALIGN_LEFT}); |
4533 | end |
4534 | |
4535 | -- differentials |
4536 | if self.differentials ~= nil then |
4537 | |
4538 | local getWheelFromDiffIndex = function(self, diffIndex) |
4539 | for _,wheel in pairs(self.wheels) do |
4540 | if wheel.wheelShape == diffIndex then |
4541 | return wheel; |
4542 | end; |
4543 | end; |
4544 | end; |
4545 | |
4546 | local getSpeedsOfDifferential; |
4547 | getSpeedsOfDifferential = function(self, diff) |
4548 | |
4549 | local speed1, speed2; |
4550 | if diff.diffIndex1IsWheel then |
4551 | local wheel = getWheelFromDiffIndex(self, diff.diffIndex1); |
4552 | speed1 = getWheelShapeAxleSpeed(wheel.node, diff.diffIndex1) * wheel.radius; |
4553 | else |
4554 | local s1,s2 = getSpeedsOfDifferential(self, self.differentials[diff.diffIndex1+1]); |
4555 | speed1 = (s1+s2)/2; |
4556 | end |
4557 | if diff.diffIndex2IsWheel then |
4558 | local wheel = getWheelFromDiffIndex(self, diff.diffIndex2); |
4559 | speed2 = getWheelShapeAxleSpeed(wheel.node, diff.diffIndex2) * wheel.radius; |
4560 | else |
4561 | local s1,s2 = getSpeedsOfDifferential(self, self.differentials[diff.diffIndex2+1]); |
4562 | speed2 = (s1+s2)/2; |
4563 | end |
4564 | return speed1,speed2; |
4565 | end |
4566 | |
4567 | local getRatioOfDifferential = function(self, speed1, speed2) |
4568 | |
4569 | -- Note: this is only correct if both rpm values have the sign |
4570 | local ratio = math.abs(math.max(speed1,speed2)) / math.max(math.abs(math.min(speed1,speed2)), 0.001); |
4571 | return ratio; |
4572 | end; |
4573 | |
4574 | local diffStrs = {"\n", "torqueRatio\n", "maxSpeedRatio\n", "actualSpeedRatio\n" } |
4575 | for i,diff in pairs(self.differentials) do |
4576 | diffStrs[1] = diffStrs[1]..string.format("%d:\n", i); |
4577 | diffStrs[2] = diffStrs[2]..string.format("%2.2f\n", diff.torqueRatio); |
4578 | diffStrs[3] = diffStrs[3]..string.format("%2.2f\n", diff.maxSpeedRatio); |
4579 | local speed1,speed2 = getSpeedsOfDifferential(self, diff); |
4580 | local ratio = getRatioOfDifferential(self, speed1, speed2); |
4581 | diffStrs[4] = diffStrs[4]..string.format("%2.2f\n", ratio); |
4582 | end; |
4583 | local str1Height = getTextHeight(getCorrectTextSize(0.02), str1); |
4584 | Utils.renderMultiColumnText(0.015, 0.42-str1Height-0.005, getCorrectTextSize(0.02), diffStrs, 0.008, {RenderText.ALIGN_RIGHT,RenderText.ALIGN_LEFT}); |
4585 | end; |
4586 | |
4587 | |
4588 | -- |
4589 | if self.motor ~= nil then |
4590 | local sizeX = 0.25; |
4591 | local sizeY = 0.2; |
4592 | local x = 0.65; |
4593 | local y = 0.64-sizeY; |
4594 | |
4595 | |
4596 | local curveOverlay = self.motor.debugCurveOverlay; |
4597 | if curveOverlay == nil then |
4598 | curveOverlay = createImageOverlay("dataS/scripts/shared/graph_pixel.png"); |
4599 | setOverlayColor(curveOverlay, 0, 1, 0, 0.2); |
4600 | self.motor.debugCurveOverlay = curveOverlay; |
4601 | end |
4602 | |
4603 | local numTorqueValues = table.getn(self.motor.torqueCurve.keyframes); |
4604 | |
4605 | local minRpm = self.motor.torqueCurve.keyframes[1].time; |
4606 | local maxRpm = self.motor.torqueCurve.keyframes[numTorqueValues].time; |
4607 | |
4608 | local torqueGraph = self.motor.debugTorqueGraph; |
4609 | local powerGraph = self.motor.debugPowerGraph; |
4610 | if torqueGraph == nil then |
4611 | |
4612 | torqueGraph = Graph:new(numTorqueValues, x, y, sizeX, sizeY, 0, 0.0001, true, "kN", Graph.STYLE_LINES); |
4613 | torqueGraph:setColor(1, 1, 1, 1); |
4614 | self.motor.debugTorqueGraph = torqueGraph; |
4615 | |
4616 | powerGraph = Graph:new(numTorqueValues, x, y, sizeX, sizeY, 0, 0.0001, false, "", Graph.STYLE_LINES); |
4617 | powerGraph:setColor(1, 0, 0, 1); |
4618 | self.motor.debugPowerGraph = powerGraph; |
4619 | |
4620 | torqueGraph.maxValue = 0.01; |
4621 | powerGraph.maxValue = 0.01; |
4622 | for s=1, numTorqueValues do |
4623 | local torque = self.motor.torqueCurve.keyframes[s].v; |
4624 | local rpm = self.motor.torqueCurve.keyframes[s].time; |
4625 | local power = torque*1000 * rpm*math.pi/30; |
4626 | local hpPower = power/735.49875; |
4627 | local posX = (rpm - minRpm) / (maxRpm-minRpm); |
4628 | |
4629 | torqueGraph:setValue(s, torque); |
4630 | torqueGraph.maxValue = math.max(torqueGraph.maxValue, torque); |
4631 | torqueGraph:setXPosition(s, posX); |
4632 | |
4633 | powerGraph:setValue(s, hpPower); |
4634 | powerGraph.maxValue = math.max(powerGraph.maxValue, hpPower); |
4635 | powerGraph:setXPosition(s, posX); |
4636 | end |
4637 | end |
4638 | torqueGraph:draw(); |
4639 | powerGraph:draw(); |
4640 | renderOverlay(curveOverlay, x, y, sizeX*Utils.clamp((self.motor.nonClampedMotorRpm-minRpm)/(maxRpm-minRpm), 0, 1), sizeY); |
4641 | |
4642 | local y = y-sizeY-0.013; |
4643 | |
4644 | local maxSpeed = self.motor:getMaximumForwardSpeed(); |
4645 | |
4646 | local effTorqueGraph = self.motor.debugEffectiveTorqueGraph; |
4647 | local effPowerGraph = self.motor.debugEffectivePowerGraph; |
4648 | local effGearRatioGraph = self.motor.debugEffectiveGearRatioGraph; |
4649 | local effRpmGraph = self.motor.debugEffectiveRpmGraph; |
4650 | if effTorqueGraph == nil then |
4651 | local numVelocityValues = 20; |
4652 | |
4653 | effTorqueGraph = Graph:new(numVelocityValues, x, y, sizeX, sizeY, 0, 0.0001, true, "kN", Graph.STYLE_LINES); |
4654 | effTorqueGraph:setColor(1, 1, 1, 1); |
4655 | self.motor.debugEffectiveTorqueGraph = effTorqueGraph; |
4656 | |
4657 | effPowerGraph = Graph:new(numVelocityValues, x, y, sizeX, sizeY, 0, 0.0001, false, "", Graph.STYLE_LINES); |
4658 | effPowerGraph:setColor(1, 0, 0, 1); |
4659 | self.motor.debugEffectivePowerGraph = effPowerGraph; |
4660 | |
4661 | effGearRatioGraph = Graph:new(numVelocityValues, x, y, sizeX, sizeY, 0, 0.0001, false, "", Graph.STYLE_LINES); |
4662 | effGearRatioGraph:setColor(0.35, 1, 0.85, 1); |
4663 | self.motor.debugEffectiveGearRatioGraph = effGearRatioGraph; |
4664 | |
4665 | effRpmGraph = Graph:new(numVelocityValues, x, y, sizeX, sizeY, 0, 0.0001, false, "", Graph.STYLE_LINES); |
4666 | effRpmGraph:setColor(0.18, 0.18, 1, 1); |
4667 | self.motor.debugEffectiveRpmGraph = effRpmGraph; |
4668 | |
4669 | effTorqueGraph.maxValue = 0.01; |
4670 | effPowerGraph.maxValue = 0.01; |
4671 | effGearRatioGraph.maxValue = 0.01; |
4672 | effRpmGraph.maxValue = 0.01; |
4673 | |
4674 | for s=1, numVelocityValues do |
4675 | local speed = (s-1)/(numVelocityValues-1) * maxSpeed; |
4676 | |
4677 | local _, bestGearRatio = self.motor:getBestGear(1, speed*30/math.pi, 0, math.huge, 0) |
4678 | local bestRpm = speed*30/math.pi * bestGearRatio; |
4679 | |
4680 | local torque = self.motor.torqueCurve:get(bestRpm); |
4681 | local power = torque*1000 * bestRpm*math.pi/30; |
4682 | local hpPower = power/735.49875; |
4683 | |
4684 | effTorqueGraph:setValue(s, torque); |
4685 | effTorqueGraph.maxValue = math.max(effTorqueGraph.maxValue, torque); |
4686 | |
4687 | effPowerGraph:setValue(s, hpPower); |
4688 | effPowerGraph.maxValue = math.max(effPowerGraph.maxValue, hpPower); |
4689 | |
4690 | effGearRatioGraph:setValue(s, bestGearRatio); |
4691 | effGearRatioGraph.maxValue = math.max(effGearRatioGraph.maxValue, bestGearRatio); |
4692 | |
4693 | effRpmGraph:setValue(s, bestRpm); |
4694 | effRpmGraph.maxValue = math.max(effRpmGraph.maxValue, bestRpm); |
4695 | end |
4696 | end |
4697 | effTorqueGraph:draw(); |
4698 | effPowerGraph:draw(); |
4699 | effGearRatioGraph:draw(); |
4700 | effRpmGraph:draw(); |
4701 | renderOverlay(curveOverlay, x, y, sizeX*Utils.clamp(self.lastSpeedReal*1000/maxSpeed, 0, 1), sizeY); |
4702 | end |
4703 | end; |
4704 | Utils.renderMultiColumnText(0.015, 0.65, getCorrectTextSize(0.02), {str1,str2}, 0.008, {RenderText.ALIGN_RIGHT,RenderText.ALIGN_LEFT}); |
4705 | end |
4706 | |
4707 | function Vehicle:developmentReloadFromXML() |
4708 | local xmlFile = loadXMLFile("Tmp", self.configFileName); |
4709 | |
4710 | |
4711 | self.maxRotTime = 0; |
4712 | self.minRotTime = 0; |
4713 | self.autoRotateBackSpeed = Utils.getNoNil(getXMLFloat(xmlFile, "vehicle.wheels#autoRotateBackSpeed"), 1.0); |
4714 | |
4715 | for i=1, table.getn(self.wheels) do |
4716 | local wheel = self.wheels[i]; |
4717 | local wheelnamei = string.format("vehicle.wheels.wheel(%d)", wheel.xmlIndex); |
4718 | |
4719 | self:loadDynamicWheelDataFromXML(xmlFile, wheelnamei, wheel); |
4720 | end; |
4721 | |
4722 | self:loadWheelsSteeringDataFromXML(xmlFile); |
4723 | |
4724 | for _,spec in pairs(self.specializations) do |
4725 | if spec.developmentReloadFromXML ~= nil then |
4726 | spec.developmentReloadFromXML(self, xmlFile); |
4727 | end; |
4728 | end; |
4729 | |
4730 | for _,spec in pairs(self.specializations) do |
4731 | if spec.developmentReloadFromXMLPost ~= nil then |
4732 | spec.developmentReloadFromXMLPost(self, xmlFile); |
4733 | end; |
4734 | end; |
4735 | |
4736 | delete(xmlFile); |
4737 | |
4738 | for _, implement in pairs(self.attachedImplements) do |
4739 | implement.object:developmentReloadFromXML(); |
4740 | end; |
4741 | end; |
4742 | |
4743 | function Vehicle.consoleCommandToggleDebugRendering(unusedSelf) |
4744 | Vehicle.debugRendering = not Vehicle.debugRendering; |
4745 | return "VehicleDebugRendering = "..tostring(Vehicle.debugRendering); |
4746 | end; |
4747 | |
4748 | |
4749 | function Vehicle.consoleCommandReloadFromXML(unusedSelf) |
4750 | if g_currentMission ~= nil and g_currentMission.controlledVehicle ~= nil and g_currentMission.controlledVehicle.isServer then |
4751 | |
4752 | g_currentMission.controlledVehicle:developmentReloadFromXML(); |
4753 | |
4754 | return "Reloaded vehicle from xml"; |
4755 | end; |
4756 | |
4757 | return "Failed to reload vehicle from xml. Invalid controlled vehicle"; |
4758 | end; |
4759 | |
4760 | function Vehicle.consoleCommandAnalyze(unusedSelf) |
4761 | if g_currentMission ~= nil and g_currentMission.controlledVehicle ~= nil and g_currentMission.controlledVehicle.isServer then |
4762 | local self = g_currentMission.controlledVehicle; |
4763 | |
4764 | print("Analyzing vehicle '"..self.configFileName.."'. Make sure vehicle is standing on a flat plane parallel to xz-plane"); |
4765 | |
4766 | local groundRaycastResult = { |
4767 | raycastCallback = function (self, transformId, x, y, z, distance) |
4768 | self.groundDistance = distance; |
4769 | end |
4770 | }; |
4771 | for i, attacherJoint in ipairs(self.attacherJoints) do |
4772 | local trx, try, trz = getRotation(attacherJoint.jointTransform); |
4773 | setRotation(attacherJoint.jointTransform, unpack(attacherJoint.jointOrigRot)); |
4774 | if attacherJoint.rotationNode ~= nil or attacherJoint.rotationNode2 ~= nil then |
4775 | local rx,ry,rz; |
4776 | if attacherJoint.rotationNode ~= nil then |
4777 | rx,ry,rz = getRotation(attacherJoint.rotationNode); |
4778 | end |
4779 | local rx2,ry2,rz2; |
4780 | if attacherJoint.rotationNode2 ~= nil then |
4781 | rx2,ry2,rz2 = getRotation(attacherJoint.rotationNode2); |
4782 | end |
4783 | |
4784 | -- test min rot |
4785 | if attacherJoint.rotationNode ~= nil then |
4786 | setRotation(attacherJoint.rotationNode, unpack(attacherJoint.minRot)); |
4787 | end |
4788 | if attacherJoint.rotationNode2 ~= nil then |
4789 | setRotation(attacherJoint.rotationNode2, unpack(attacherJoint.minRot2)); |
4790 | end |
4791 | local x,y,z = getWorldTranslation(attacherJoint.jointTransform); |
4792 | groundRaycastResult.groundDistance = 0; |
4793 | raycastClosest(x, y, z, 0, -1, 0, "raycastCallback", 4, groundRaycastResult); |
4794 | if math.abs(groundRaycastResult.groundDistance - attacherJoint.minRotDistanceToGround) > 0.04 then |
4795 | print(" Issue found: Attacher joint "..i.." has invalid minRotDistanceToGround. True value is: "..groundRaycastResult.groundDistance.. ". Value in XML: "..attacherJoint.minRotDistanceToGround); |
4796 | end |
4797 | local dx,dy,dz = localDirectionToWorld(attacherJoint.jointTransform, 0, 1, 0); |
4798 | local angle = math.deg(math.acos(Utils.clamp(dy, -1, 1))); |
4799 | local dxx,dxy,dxz = localDirectionToWorld(attacherJoint.jointTransform, 1, 0, 0); |
4800 | if dxy < 0 then |
4801 | angle = -angle; |
4802 | end |
4803 | if math.abs(angle-math.deg(attacherJoint.minRotRotationOffset)) > 0.5 then |
4804 | print(" Issue found: Attacher joint "..i.." has invalid minRotRotationOffset. True value is: "..angle.. ". Value in XML: "..math.deg(attacherJoint.minRotRotationOffset)); |
4805 | end |
4806 | |
4807 | -- test max rot |
4808 | if attacherJoint.rotationNode ~= nil then |
4809 | setRotation(attacherJoint.rotationNode, unpack(attacherJoint.maxRot)); |
4810 | end |
4811 | if attacherJoint.rotationNode2 ~= nil then |
4812 | setRotation(attacherJoint.rotationNode2, unpack(attacherJoint.maxRot2)); |
4813 | end |
4814 | local x,y,z = getWorldTranslation(attacherJoint.jointTransform); |
4815 | groundRaycastResult.groundDistance = 0; |
4816 | raycastClosest(x, y, z, 0, -1, 0, "raycastCallback", 4, groundRaycastResult); |
4817 | if math.abs(groundRaycastResult.groundDistance - attacherJoint.maxRotDistanceToGround) > 0.04 then |
4818 | print(" Issue found: Attacher joint "..i.." has invalid maxRotDistanceToGround. True value: "..groundRaycastResult.groundDistance.. ". Value in XML: "..attacherJoint.maxRotDistanceToGround); |
4819 | end |
4820 | local dx,dy,dz = localDirectionToWorld(attacherJoint.jointTransform, 0, 1, 0); |
4821 | local angle = math.deg(math.acos(Utils.clamp(dy, -1, 1))); |
4822 | local dxx,dxy,dxz = localDirectionToWorld(attacherJoint.jointTransform, 1, 0, 0); |
4823 | if dxy < 0 then |
4824 | angle = -angle; |
4825 | end |
4826 | if math.abs(angle-math.deg(attacherJoint.maxRotRotationOffset)) > 2 then |
4827 | print(" Issue found: Attacher joint "..i.." has invalid maxRotRotationOffset. True value is: "..angle.. ". Value in XML: "..math.deg(attacherJoint.maxRotRotationOffset)); |
4828 | end |
4829 | |
4830 | -- reset rotations |
4831 | if attacherJoint.rotationNode ~= nil then |
4832 | setRotation(attacherJoint.rotationNode, rx,ry,rz); |
4833 | end |
4834 | if attacherJoint.rotationNode2 ~= nil then |
4835 | setRotation(attacherJoint.rotationNode2, rx2,ry2,rz2); |
4836 | end |
4837 | end |
4838 | setRotation(attacherJoint.jointTransform, trx, try, trz); |
4839 | end |
4840 | |
4841 | for i, wheel in ipairs(self.wheels) do |
4842 | local comX,comY,comZ = getCenterOfMass(wheel.node); |
4843 | |
4844 | local forcePointY = wheel.positionY + wheel.deltaY - wheel.radius * wheel.forcePointRatio |
4845 | if forcePointY > comY then |
4846 | print(string.format(" Issue found: Wheel %d has force point higher than center of mass. %.2f > %.2f. This can lead to undesired driving behavior (inward-leaning).", i, forcePointY, comY)); |
4847 | end |
4848 | |
4849 | local tireLoad = getWheelShapeContactForce(wheel.node, wheel.wheelShape); |
4850 | if tireLoad ~= nil then |
4851 | local nx,ny,nz = getWheelShapeContactNormal(wheel.node, wheel.wheelShape); |
4852 | local dx,dy,dz = localDirectionToWorld(wheel.node, 0,-1,0); |
4853 | tireLoad = -tireLoad*Utils.dotProduct(dx,dy,dz, nx,ny,nz); |
4854 | |
4855 | local gravity = 9.81; |
4856 | tireLoad = tireLoad + math.max(ny*gravity, 0.0) * wheel.mass; -- add gravity force of tire |
4857 | |
4858 | tireLoad = tireLoad / gravity; |
4859 | |
4860 | if math.abs(tireLoad - wheel.restLoad) > 0.2 then |
4861 | print(string.format(" Issue found: Wheel %d has wrong restLoad. %.2f vs. %.2f in XML. Verify that this leads to the desired behavior.", i, tireLoad, wheel.restLoad)); |
4862 | end |
4863 | end |
4864 | end |
4865 | |
4866 | |
4867 | return "Analyzed vehicle"; |
4868 | end; |
4869 | |
4870 | return "Failed to analyze vehicle. Invalid controlled vehicle"; |
4871 | end; |
4872 | |
4873 | |
4874 | addConsoleCommand("gsVehicleToggleDebugRendering", "Toggles the debug rendering of the vehicles", "Vehicle.consoleCommandToggleDebugRendering", nil); |
4875 | addConsoleCommand("gsVehicleReloadFromXML", "Reload xml of current vehicle", "Vehicle.consoleCommandReloadFromXML", nil); |
4876 | addConsoleCommand("gsVehicleAnalyze", "Analyze vehicle", "Vehicle.consoleCommandAnalyze", nil); |