23 | function ArticulatedAxis.initSpecialization() |
24 | local schema = Vehicle.xmlSchema |
25 | schema:setXMLSpecializationType("ArticulatedAxis") |
26 | |
27 | schema:register(XMLValueType.INT, "vehicle.articulatedAxis#componentJointIndex", "Index of component joint") |
28 | schema:register(XMLValueType.ANGLE, "vehicle.articulatedAxis#rotSpeed", "Rotation speed") |
29 | schema:register(XMLValueType.ANGLE, "vehicle.articulatedAxis#rotMax", "Max rotation") |
30 | schema:register(XMLValueType.ANGLE, "vehicle.articulatedAxis#rotMin", "Min rotation") |
31 | schema:register(XMLValueType.INT, "vehicle.articulatedAxis#anchorActor", "Anchor actor index", 0) |
32 | schema:register(XMLValueType.NODE_INDEX, "vehicle.articulatedAxis#rotNode", "Rotation node") |
33 | schema:register(XMLValueType.NODE_INDEX, "vehicle.articulatedAxis#aiRevereserNode", "AI reverser node") |
34 | schema:register(XMLValueType.FLOAT, "vehicle.articulatedAxis#maxTurningRadius", "Fixed turning radius to overwrite automatic calculations") |
35 | schema:register(XMLValueType.VECTOR_N, "vehicle.articulatedAxis#customWheelIndices1", "Component 1 wheel indices. Needed if wheels are not linked to component 1 directly. E.g. dolly axis") |
36 | schema:register(XMLValueType.VECTOR_N, "vehicle.articulatedAxis#customWheelIndices2", "Component 2 wheel indices. Needed if wheels are not linked to component 2 directly. E.g. dolly axis") |
37 | |
38 | schema:register(XMLValueType.NODE_INDEX, "vehicle.articulatedAxis.rotatingPart(?)#node", "Rotation part node") |
39 | schema:register(XMLValueType.VECTOR_ROT, "vehicle.articulatedAxis.rotatingPart(?)#posRot", "Positive rotation") |
40 | schema:register(XMLValueType.VECTOR_ROT, "vehicle.articulatedAxis.rotatingPart(?)#negRot", "Negative rotation") |
41 | schema:register(XMLValueType.FLOAT, "vehicle.articulatedAxis.rotatingPart(?)#posRotFactor", "Positive rotation factor", 1) |
42 | schema:register(XMLValueType.FLOAT, "vehicle.articulatedAxis.rotatingPart(?)#negRotFactor", "Negative rotation factor", 1) |
43 | schema:register(XMLValueType.BOOL, "vehicle.articulatedAxis.rotatingPart(?)#invertSteeringAngle", "Invert steering angle", false) |
44 | |
45 | SoundManager.registerSampleXMLPaths(schema, "vehicle.articulatedAxis.sounds", "steering") |
46 | |
47 | schema:setXMLSpecializationType() |
48 | end |
70 | function ArticulatedAxis:onLoad(savegame) |
71 | local xmlFile = self.xmlFile |
72 | local spec = self.spec_articulatedAxis |
73 | |
74 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.articulatedAxis.rotatingPart(0)#index", "vehicle.articulatedAxis.rotatingPart(0)#node") -- FS17 |
75 | |
76 | local index = xmlFile:getValue("vehicle.articulatedAxis#componentJointIndex") |
77 | if index ~= nil then |
78 | if index == 0 then |
79 | Logging.xmlWarning(self.xmlFile, "Invalid component joint index '0' for articulatedAxis. Indices start with 1!") |
80 | else |
81 | local componentJoint = self.componentJoints[index] |
82 | local rotSpeed = xmlFile:getValue("vehicle.articulatedAxis#rotSpeed") |
83 | local rotMax = xmlFile:getValue("vehicle.articulatedAxis#rotMax") |
84 | local rotMin = xmlFile:getValue("vehicle.articulatedAxis#rotMin") |
85 | if componentJoint ~= nil and rotSpeed ~= nil and rotMax ~= nil and rotMin ~= nil then |
86 | spec.rotSpeed = rotSpeed |
87 | spec.rotMax = rotMax |
88 | spec.rotMin = rotMin |
89 | |
90 | spec.componentJoint = componentJoint |
91 | spec.anchorActor = xmlFile:getValue( "vehicle.articulatedAxis#anchorActor", 0) |
92 | spec.rotationNode = xmlFile:getValue("vehicle.articulatedAxis#rotNode", nil, self.components, self.i3dMappings) |
93 | if spec.rotationNode == nil then |
94 | spec.rotationNode = spec.componentJoint.jointNode |
95 | end |
96 | |
97 | spec.curRot = 0 |
98 | |
99 | local i = 0 |
100 | spec.rotatingParts = {} |
101 | while true do |
102 | local key = string.format("vehicle.articulatedAxis.rotatingPart(%d)", i) |
103 | if not xmlFile:hasProperty(key) then |
104 | break |
105 | end |
106 | |
107 | local node = xmlFile:getValue(key .. "#node", nil, self.components, self.i3dMappings) |
108 | if node ~= nil then |
109 | local rotatingPart = {} |
110 | rotatingPart.node = node |
111 | rotatingPart.defRot = {getRotation(node)} |
112 | rotatingPart.posRot = xmlFile:getValue(key .. "#posRot", nil, true) |
113 | rotatingPart.negRot = xmlFile:getValue(key .. "#negRot", nil, true) |
114 | rotatingPart.negRotFactor = xmlFile:getValue(key .. "#negRotFactor", 1) |
115 | rotatingPart.posRotFactor = xmlFile:getValue(key .. "#posRotFactor", 1) |
116 | rotatingPart.invertSteeringAngle = xmlFile:getValue(key .. "#invertSteeringAngle", false) |
117 | table.insert(spec.rotatingParts, rotatingPart) |
118 | else |
119 | Logging.xmlWarning(self.xmlFile, "Failed to load rotation part '%s'", key) |
120 | end |
121 | i = i + 1 |
122 | end |
123 | |
124 | local customWheelIndices = {{}, {}} |
125 | local customWheelIndices1Sorted = xmlFile:getValue("vehicle.articulatedAxis#customWheelIndices1", nil, true) |
126 | if customWheelIndices1Sorted ~= nil then |
127 | for _, wheelIndex in ipairs(customWheelIndices1Sorted) do |
128 | customWheelIndices[1][wheelIndex] = true |
129 | end |
130 | end |
131 | local customWheelIndices2Sorted = xmlFile:getValue("vehicle.articulatedAxis#customWheelIndices2", nil, true) |
132 | if customWheelIndices2Sorted ~= nil then |
133 | for _, wheelIndex in ipairs(customWheelIndices2Sorted) do |
134 | customWheelIndices[2][wheelIndex] = true |
135 | end |
136 | end |
137 | |
138 | -- adjust steering values |
139 | local maxRotTime = rotMax/rotSpeed |
140 | local minRotTime = rotMin/rotSpeed |
141 | if minRotTime > maxRotTime then |
142 | local temp = minRotTime |
143 | minRotTime = maxRotTime |
144 | maxRotTime = temp |
145 | end |
146 | if maxRotTime > self.maxRotTime then |
147 | self.maxRotTime = maxRotTime |
148 | end |
149 | if minRotTime < self.minRotTime then |
150 | self.minRotTime = minRotTime |
151 | end |
152 | |
153 | self.maxRotation = rotMax |
154 | self.wheelSteeringDuration = MathUtil.sign(rotSpeed) * rotMax / rotSpeed |
155 | |
156 | -- adjust variables used by AIVehicleUtil |
157 | spec.aiRevereserNode = xmlFile:getValue("vehicle.articulatedAxis#aiRevereserNode", nil, self.components, self.i3dMappings) |
158 | |
159 | local maxTurningRadius = 0 |
160 | local specWheels = self.spec_wheels |
161 | for j=1,2 do |
162 | local rootNode = self.components[componentJoint.componentIndices[j]].node |
163 | |
164 | for wheelIndex, wheel in ipairs(specWheels.wheels) do |
165 | if self:getParentComponent(wheel.repr) == rootNode or customWheelIndices[j][wheelIndex] ~= nil then |
166 | |
167 | local wx,_,wz = localToLocal(wheel.driveNode, rootNode, 0,0,0) |
168 | local dx1 = 1 |
169 | if wx < 0 then |
170 | dx1 = -1 |
171 | end |
172 | local dz1 = math.tan( math.max(wheel.rotMin, wheel.rotMax) ) |
173 | if wz > 0 then |
174 | dz1 = -dz1 |
175 | end |
176 | |
177 | local x2, z2 = 0, 0 |
178 | local dx2 = 1 |
179 | if wx < 0 then |
180 | dx2 = -1 |
181 | end |
182 | local dz2 = math.tan( math.max(rotMin, rotMax) ) |
183 | if wz < 0 then |
184 | dz2 = -dz2 |
185 | end |
186 | |
187 | -- normalize directions |
188 | local l1 = MathUtil.vector2Length(dx1, dz1) |
189 | dx1, dz1 = dx1 / l1, dz1 / l1 |
190 | |
191 | local l2 = MathUtil.vector2Length(dx2, dz2) |
192 | dx2, dz2 = dx2 / l2, dz2 / l2 |
193 | |
194 | local intersect, _, f2 = MathUtil.getLineLineIntersection2D(wx,wz, dx1,dz1, x2,z2, dx2,dz2) |
195 | if intersect then |
196 | local radius = math.abs(f2) |
197 | maxTurningRadius = math.max(maxTurningRadius, radius) |
198 | end |
199 | end |
200 | end |
201 | end |
202 | |
203 | if maxTurningRadius ~= 0 then |
204 | self.maxTurningRadius = maxTurningRadius |
205 | end |
206 | |
207 | self.maxTurningRadius = xmlFile:getValue("vehicle.articulatedAxis#maxTurningRadius", self.maxTurningRadius) |
208 | end |
209 | end |
210 | end |
211 | |
212 | if self.isClient then |
213 | spec.samples = {} |
214 | spec.samples.steering = g_soundManager:loadSampleFromXML(self.xmlFile, "vehicle.articulatedAxis.sounds", "steering", self.baseDirectory, self.components, 0, AudioGroup.VEHICLE, self.i3dMappings, self) |
215 | spec.isSteeringSoundPlaying = false |
216 | end |
217 | |
218 | spec.interpolatedRotatedTime = 0 |
219 | end |
258 | function ArticulatedAxis:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
259 | local spec = self.spec_articulatedAxis |
260 | -- interpolatedRotatedTime to manipulate camera rot |
261 | if spec.interpolatedRotatedTime < self.rotatedTime then |
262 | spec.interpolatedRotatedTime = math.min(self.rotatedTime, spec.interpolatedRotatedTime + math.abs(spec.rotSpeed) * dt/500) |
263 | elseif spec.interpolatedRotatedTime > self.rotatedTime then |
264 | spec.interpolatedRotatedTime = math.max(self.rotatedTime, spec.interpolatedRotatedTime - math.abs(spec.rotSpeed) * dt/500) |
265 | end |
266 | |
267 | local steeringAngle = MathUtil.clamp(self.rotatedTime * spec.rotSpeed, spec.rotMin, spec.rotMax) |
268 | if self.updateArticulatedAxisRotation ~= nil then |
269 | steeringAngle = self:updateArticulatedAxisRotation(steeringAngle, dt) |
270 | end |
271 | |
272 | if self.isClient then |
273 | local isSteering = math.abs(steeringAngle - spec.curRot) > 0.0001 |
274 | if isSteering ~= spec.isSteeringSoundPlaying then |
275 | if isSteering then |
276 | g_soundManager:playSample(spec.samples.steering) |
277 | else |
278 | g_soundManager:stopSample(spec.samples.steering) |
279 | end |
280 | |
281 | spec.isSteeringSoundPlaying = isSteering |
282 | end |
283 | end |
284 | |
285 | if math.abs(steeringAngle - spec.curRot) > 0.000001 then |
286 | if self.isServer then |
287 | setRotation(spec.rotationNode, 0, steeringAngle, 0) |
288 | self:setComponentJointFrame(spec.componentJoint, spec.anchorActor) |
289 | spec.curRot = steeringAngle |
290 | end |
291 | |
292 | if self.isClient then |
293 | local percent = 0 |
294 | if steeringAngle > 0 then |
295 | percent = steeringAngle / spec.rotMax |
296 | elseif steeringAngle < 0 then |
297 | percent = steeringAngle / spec.rotMin |
298 | end |
299 | |
300 | for _,rotPart in pairs(spec.rotatingParts) do |
301 | local rx,ry,rz |
302 | if (steeringAngle > 0 and not rotPart.invertSteeringAngle) or (steeringAngle < 0 and rotPart.invertSteeringAngle) then |
303 | rx,ry,rz = MathUtil.vector3ArrayLerp(rotPart.defRot, rotPart.posRot, math.min(1,percent*rotPart.posRotFactor)) |
304 | else |
305 | rx,ry,rz = MathUtil.vector3ArrayLerp(rotPart.defRot, rotPart.negRot, math.min(1,percent*rotPart.negRotFactor)) |
306 | end |
307 | setRotation(rotPart.node, rx,ry,rz) |
308 | if self.setMovingToolDirty ~= nil then |
309 | self:setMovingToolDirty(rotPart.node) |
310 | end |
311 | end |
312 | end |
313 | end |
314 | end |