Sprache Deutsch Language English

Script Dokumentation LS 2015 - Vehicle (Patch 1.3)

Script Dokumentation Übersicht

scripts/vehicles/Vehicle.lua

Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
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
12Vehicle = {};
13
14source("dataS/scripts/vehicles/specializations/VehicleSetBeaconLightEvent.lua");
15source("dataS/scripts/vehicles/specializations/VehicleSetTurnSignalEvent.lua");
16
17InitStaticObjectClass(Vehicle, "Vehicle", ObjectIds.OBJECT_VEHICLE);
18
19Vehicle.springScale = 10;
20
21Vehicle.NUM_JOINTTYPES = 0;
22Vehicle.jointTypeNameToInt = {}
23
24Vehicle.defaultWidth = 8;
25Vehicle.defaultLength = 8;
26
27Vehicle.debugRendering = false;
28
29Vehicle.TURNSIGNAL_OFF = 0;
30Vehicle.TURNSIGNAL_LEFT = 1;
31Vehicle.TURNSIGNAL_RIGHT = 2;
32Vehicle.TURNSIGNAL_HAZARD = 3;
33Vehicle.turnSignalSendNumBits = 2;
34
35Vehicle.WHEEL_NO_CONTACT = 0;
36Vehicle.WHEEL_OBJ_CONTACT = 1;
37Vehicle.WHEEL_GROUND_CONTACT = 2;
38
39Vehicle.useFakeLightsIfNotReal = false;
40
41function 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;
48end;
49
50Vehicle.registerJointType("implement");
51Vehicle.registerJointType("trailer");
52Vehicle.registerJointType("trailerLow");
53Vehicle.registerJointType("telehandler");
54Vehicle.registerJointType("frontloader");
55Vehicle.registerJointType("semitrailer");
56Vehicle.registerJointType("attachableFrontloader");
57Vehicle.registerJointType("wheelLoader");
58Vehicle.registerJointType("manureBarrel");
59
60function 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;
76end;
77
78function 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
107end
108
109function 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;
1223end;
1224
1225function 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;
1252end
1253
1254function 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;
1258end
1259
1260function 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;
1291end
1292
1293function 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
1351end
1352
1353function 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
1476end
1477
1478function 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;
1568end;
1569
1570function 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;
1653end;
1654
1655function 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;
1720end;
1721
1722function 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;
1815end;
1816
1817function 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;
1864end;
1865
1866function 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;
1878end;
1879
1880function 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;
1888end;
1889
1890function 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;
1898end;
1899
1900function 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;
1908end;
1909
1910function 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;
1984end;
1985
1986function 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;
2022end;
2023
2024function 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;
2055end;
2056
2057function 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;
2079end
2080
2081function 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;
2116end
2117
2118function 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;
2160end;
2161
2162function 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);
2165end;
2166
2167function 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);
2170end;
2171
2172function 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;
2182end;
2183
2184function 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;
2194end;
2195
2196function 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;
2207end;
2208
2209function 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;
2220end;
2221
2222function 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;
2565end;
2566
2567function 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;
2738end;
2739
2740function 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;
2759end;
2760
2761function 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
2786end;
2787
2788function 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);
2872end
2873
2874function 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;
2894end;
2895
2896function 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;
2913end;
2914
2915function 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;]]
2923end;
2924
2925function 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;
2950end;
2951
2952function 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
2971end
2972
2973function 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);
2981end
2982
2983function 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
3220end;
3221
3222function 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
3345end;
3346
3347function 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;
3355end;
3356
3357function 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
3370end;
3371
3372-- selects the next implement in a dfs order
3373-- returns if a child or sub*-child was selected
3374function 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;
3425end;
3426
3427function 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
3441end;
3442
3443function 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
3452end;
3453
3454function Vehicle:playAttachSound()
3455 if self.isClient then
3456 Utils.playSample(self.sampleAttach, 1, 0, nil);
3457 end;
3458end;
3459
3460function Vehicle:playDetachSound()
3461 if self.isClient then
3462 Utils.playSample(self.sampleAttach, 1, 0, nil);
3463 end;
3464end;
3465
3466function 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
3487end;
3488
3489function 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;
3505end;
3506
3507function 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;
3525end;
3526
3527function 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;
3538end;
3539
3540function 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;
3548end;
3549
3550function 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;
3557end;
3558
3559function 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;
3566end;
3567
3568function 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;
3575end;
3576
3577function 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
3584end
3585
3586function 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;
3629end;
3630
3631function 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
3652end
3653
3654function 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;
3681end;
3682
3683function 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;
3713end;
3714
3715function 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
3732end
3733
3734function 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
3751end
3752
3753function 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;
3774end;
3775
3776function 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;
3786end;
3787
3788function 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;
3796end;
3797
3798function Vehicle:canToggleLight()
3799 if not g_currentMission.controlPlayer and g_currentMission.controlledVehicle == self then
3800 return true;
3801 else
3802 return false;
3803 end;
3804end;
3805
3806function 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;
3817end;
3818
3819function 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);
3843end
3844
3845function 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;
3861end
3862
3863function Vehicle:addConflictCheckedInput(actionIndex)
3864 self.conflictCheckedInputs[actionIndex] = true;
3865end
3866
3867function Vehicle:removeConflictCheckedInput(actionIndex)
3868 self.conflictCheckedInputs[actionIndex] = nil;
3869end
3870
3871function 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;
3878end;
3879
3880function 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;
3888end;
3889
3890function Vehicle:onDeactivateAttachments()
3891 for _,v in pairs(self.attachedImplements) do
3892 if v.object ~= nil then
3893 v.object:onDeactivate();
3894 end
3895 end;
3896end;
3897
3898function Vehicle:onActivateAttachments()
3899 for _,v in pairs(self.attachedImplements) do
3900 if v.object ~= nil then
3901 v.object:onActivate();
3902 end
3903 end;
3904end;
3905
3906function Vehicle:onDeactivateAttachmentsSounds()
3907 for _,v in pairs(self.attachedImplements) do
3908 if v.object ~= nil then
3909 v.object:onDeactivateSounds();
3910 end
3911 end;
3912end;
3913
3914function Vehicle:onDeactivateAttachmentsLights()
3915 for _,v in pairs(self.attachedImplements) do
3916 if v.object ~= nil then
3917 v.object:onDeactivateLights();
3918 end
3919 end;
3920end;
3921
3922function 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;
3929end;
3930
3931function 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();
3941end;
3942
3943function 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;
3953end;
3954
3955function 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);
3968end;
3969
3970function 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;
3981end;
3982
3983function 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;
3995end;
3996
3997function Vehicle:getIsVehicleNode(nodeId)
3998 return self.vehicleNodes[nodeId] ~= nil;
3999end;
4000
4001function 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;
4011end;
4012
4013function 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;
4021end;
4022
4023function 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;
4042end;
4043
4044function Vehicle:getRootAttacherVehicle()
4045 local rootVehicle = self;
4046 while rootVehicle.attacherVehicle ~= nil do
4047 rootVehicle = rootVehicle.attacherVehicle;
4048 end
4049 return rootVehicle;
4050end
4051
4052function 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;
4064end;
4065
4066function 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
4077end
4078
4079function 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
4090end
4091
4092function 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;
4104end;
4105
4106function Vehicle:getIsAreaActive(area)
4107 return true;
4108end;
4109
4110function 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;
4188end;
4189
4190function Vehicle:getIsSpeedRotatingPartActive(speedRotatingPart)
4191 if speedRotatingPart.onlyActiveWhenLowered and not self:isLowered(true) then
4192 return false;
4193 end;
4194
4195 return true;
4196end;
4197
4198function Vehicle:getSpeedRotatingPartDirection(speedRotatingPart)
4199 return 1;
4200end;
4201
4202function 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;
4236end;
4237
4238function 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;
4246end;
4247
4248function 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;
4256end
4257
4258function 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
4287end
4288
4289function 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;
4299end;
4300
4301function 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;
4312end;
4313
4314function 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;
4318end;
4319
4320function 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;
4347end;
4348
4349function 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;
4374end
4375
4376function 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});
4705end
4706
4707function 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;
4741end;
4742
4743function Vehicle.consoleCommandToggleDebugRendering(unusedSelf)
4744 Vehicle.debugRendering = not Vehicle.debugRendering;
4745 return "VehicleDebugRendering = "..tostring(Vehicle.debugRendering);
4746end;
4747
4748
4749function 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";
4758end;
4759
4760function 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";
4871end;
4872
4873
4874addConsoleCommand("gsVehicleToggleDebugRendering", "Toggles the debug rendering of the vehicles", "Vehicle.consoleCommandToggleDebugRendering", nil);
4875addConsoleCommand("gsVehicleReloadFromXML", "Reload xml of current vehicle", "Vehicle.consoleCommandReloadFromXML", nil);
4876addConsoleCommand("gsVehicleAnalyze", "Analyze vehicle", "Vehicle.consoleCommandAnalyze", nil);
Copyright (c) 2008-2015 GIANTS Software GmbH, Confidential, All Rights Reserved.
This document is to be published solely by ls-mods.de
Script Dokumentation Übersicht