495 | function Crawlers:getCrawlerWheelMovedDistance(crawler, lastName, useOnlyRotation) |
496 | local minMovedDistance = math.huge |
497 | local direction = 1 |
498 | |
499 | for i=1, #crawler.wheels do |
500 | local wheelData = crawler.wheels[i] |
501 | if wheelData.wheel.contact ~= Wheels.WHEEL_NO_CONTACT or #crawler.wheels == 1 then |
502 | local newX, _, _ = getRotation(wheelData.wheel.driveNode) |
503 | if wheelData[lastName] == nil then |
504 | wheelData[lastName] = newX |
505 | end |
506 | |
507 | local lastRotation = wheelData[lastName] |
508 | |
509 | if newX - lastRotation < -math.pi then |
510 | lastRotation = lastRotation - 2*math.pi |
511 | elseif newX - lastRotation > math.pi then |
512 | lastRotation = lastRotation + 2*math.pi |
513 | end |
514 | |
515 | local distance = wheelData.wheel.radius * (newX - lastRotation) |
516 | if math.abs(wheelData.wheel.steeringAngle) > math.pi * 0.5 then |
517 | distance = -distance |
518 | end |
519 | |
520 | if useOnlyRotation then |
521 | distance = newX - lastRotation |
522 | end |
523 | |
524 | if distance < 0 then |
525 | if distance > -minMovedDistance then |
526 | minMovedDistance = -distance |
527 | direction = -1 |
528 | end |
529 | else |
530 | if distance < minMovedDistance then |
531 | minMovedDistance = distance |
532 | direction = 1 |
533 | end |
534 | end |
535 | |
536 | wheelData[lastName] = newX |
537 | end |
538 | end |
539 | |
540 | if minMovedDistance ~= math.huge then |
541 | return minMovedDistance * direction |
542 | end |
543 | |
544 | return 0 |
545 | end |
27 | function Crawlers.initSpecialization() |
28 | g_storeManager:addVRamUsageFunction(Crawlers.getVRamUsageFromXML) |
29 | |
30 | local schema = Vehicle.xmlSchema |
31 | schema:setXMLSpecializationType("Crawlers") |
32 | |
33 | local crawlerKey = "vehicle.wheels.wheelConfigurations.wheelConfiguration(?).crawlers.crawler(?)" |
34 | |
35 | schema:register(XMLValueType.NODE_INDEX, crawlerKey .. "#linkNode", "Link node") |
36 | schema:register(XMLValueType.BOOL, crawlerKey .. "#isLeft", "Is left crawler", false) |
37 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#trackWidth", "Track width", 1) |
38 | schema:register(XMLValueType.STRING, crawlerKey .. "#filename", "Crawler filename") |
39 | schema:register(XMLValueType.VECTOR_TRANS, crawlerKey .. "#offset", "Crawler position offset") |
40 | schema:register(XMLValueType.INT, crawlerKey .. "#wheelIndex", "Speed reference wheel index") |
41 | schema:register(XMLValueType.VECTOR_N, crawlerKey .. "#wheelIndices", "Multiple speed reference wheels. The average speed of the wheels WITH ground contact is used") |
42 | schema:register(XMLValueType.NODE_INDEX, crawlerKey .. "#speedReferenceNode", "Speed reference node") |
43 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#fieldDirtMultiplier", "Field dirt multiplier", 75) |
44 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#streetDirtMultiplier", "Street dirt multiplier", -150) |
45 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#minDirtPercentage", "Min. dirt while getting clean on non field ground", 0.35) |
46 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#maxDirtOffset", "Max. dirt amount offset to global dirt node", 0.5) |
47 | schema:register(XMLValueType.FLOAT, crawlerKey .. "#dirtColorChangeSpeed", "Defines speed to change the dirt color (sec)", 20) |
48 | |
49 | schema:setXMLSpecializationType() |
50 | |
51 | local crawlerSchema = XMLSchema.new("crawler") |
52 | crawlerSchema:shareDelayedRegistrationFuncs(schema) -- share the same delayed registration funcs since we have AnimatedVehicle elements in crawler schema |
53 | crawlerSchema:register(XMLValueType.STRING, "crawler.file#name", "Crawler i3d filename") |
54 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.file#leftNode", "Crawler left node in i3d") |
55 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.file#rightNode", "Crawler right node in i3d") |
56 | |
57 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.scrollerNodes.scrollerNode(?)#node", "Scroller node") |
58 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.scrollerNodes.scrollerNode(?)#scrollSpeed", "Scroll speed", 1) |
59 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.scrollerNodes.scrollerNode(?)#scrollLength", "Scroll length", 1) |
60 | crawlerSchema:register(XMLValueType.STRING, "crawler.scrollerNodes.scrollerNode(?)#shaderParameterName", "Shader parameter name", "offsetUV") |
61 | crawlerSchema:register(XMLValueType.STRING, "crawler.scrollerNodes.scrollerNode(?)#shaderParameterNamePrev", "Shader parameter name (Prev)", "#shaderParameterName prefixed with 'prev'") |
62 | crawlerSchema:register(XMLValueType.INT, "crawler.scrollerNodes.scrollerNode(?)#shaderParameterComponent", "Shader paramater component", 1) |
63 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.scrollerNodes.scrollerNode(?)#maxSpeed", "Max. speed in m/s", "unlimited") |
64 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.scrollerNodes.scrollerNode(?)#isTrackPart", "Is part of track (Track width is set as scale X)") |
65 | |
66 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.rotatingParts.rotatingPart(?)#node", "Rotating node") |
67 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.rotatingParts.rotatingPart(?)#radius", "Radius") |
68 | crawlerSchema:register(XMLValueType.FLOAT, "crawler.rotatingParts.rotatingPart(?)#speedScale", "Speed scale") |
69 | |
70 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.rimColorNodes.rimColorNode(?)#node", "Rim color node") |
71 | crawlerSchema:register(XMLValueType.STRING, "crawler.rimColorNodes.rimColorNode(?)#shaderParameter", "Shader parameter to set") |
72 | |
73 | crawlerSchema:register(XMLValueType.NODE_INDEX, "crawler.dirtNodes.dirtNode(?)#node", "Nodes that act the same way as wheels and get dirty faster when on field. If not defined everything gets dirty faster.") |
74 | |
75 | crawlerSchema:register(XMLValueType.BOOL, "crawler.animations.animation(?)#isLeft", "Load for left crawler", false) |
76 | AnimatedVehicle.registerAnimationXMLPaths(crawlerSchema, "crawler.animations.animation(?)") |
77 | |
78 | ObjectChangeUtil.registerObjectChangeSingleXMLPaths(crawlerSchema, "crawler") |
79 | |
80 | Crawlers.xmlSchema = crawlerSchema |
81 | end |
230 | function Crawlers:loadCrawlerFromXML(xmlFile, key) |
231 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#crawlerIndex", "Moved to external crawler config file") -- FS17 to FS19 |
232 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#length", "Moved to external crawler config file") -- FS17 to FS19 |
233 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#shaderParameterComponent", "Moved to external crawler config file") -- FS17 to FS19 |
234 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#shaderParameterName", "Moved to external crawler config file") -- FS17 to FS19 |
235 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#scrollLength", "Moved to external crawler config file") -- FS17 to FS19 |
236 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#scrollSpeed", "Moved to external crawler config file") -- FS17 to FS19 |
237 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#index", "Moved to external crawler config file") -- FS17 to FS19 |
238 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key..".rotatingPart", "Moved to external crawler config file") -- FS17 to FS19 |
239 | |
240 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#linkIndex", key.."#linkNode") -- FS17 to FS19 |
241 | local linkNode = xmlFile:getValue(key.."#linkNode", nil, self.components, self.i3dMappings) |
242 | if linkNode == nil then |
243 | Logging.xmlWarning(self.xmlFile, "Missing link node for crawler '%s'", key) |
244 | return |
245 | end |
246 | |
247 | local crawler = {} |
248 | crawler.linkNode = linkNode |
249 | crawler.isLeft = xmlFile:getValue(key .. "#isLeft", false) |
250 | crawler.trackWidth = xmlFile:getValue(key.."#trackWidth", 1) |
251 | |
252 | crawler.translationOffset = xmlFile:getValue(key .. "#offset", nil, true) |
253 | |
254 | XMLUtil.checkDeprecatedXMLElements(xmlFile, key.."#speedRefWheel", key.."#wheelIndex") -- FS17 to FS19 |
255 | local wheelIndex = xmlFile:getValue(key.."#wheelIndex") |
256 | local wheelIndices = xmlFile:getValue(key.."#wheelIndices", nil, true) |
257 | if wheelIndex ~= nil or wheelIndices ~= nil then |
258 | wheelIndices = wheelIndices or {} |
259 | table.insert(wheelIndices, wheelIndex) |
260 | |
261 | crawler.wheels = {} |
262 | for i=1, #wheelIndices do |
263 | local index = wheelIndices[i] |
264 | local wheels = self:getWheels() |
265 | if wheels[index] ~= nil then |
266 | wheels[index].syncContactState = true |
267 | table.insert(crawler.wheels, {wheel = wheels[index]}) |
268 | |
269 | if not wheels[index].isSynchronized then |
270 | Logging.xmlWarning(self.xmlFile, "Wheel '%s' for crawler '%s' in not synchronized! It won't rotate on the client side.", index, key) |
271 | end |
272 | end |
273 | end |
274 | |
275 | if #crawler.wheels > 0 then |
276 | crawler.wheel = crawler.wheels[1].wheel |
277 | end |
278 | end |
279 | |
280 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, key.."#speedRefNode", key.."#speedReferenceNode") -- FS17 to FS19 |
281 | crawler.speedReferenceNode = xmlFile:getValue(key.."#speedReferenceNode", nil, self.components, self.i3dMappings) |
282 | crawler.movedDistance = 0 |
283 | |
284 | crawler.fieldDirtMultiplier = xmlFile:getValue(key.."#fieldDirtMultiplier", 75) |
285 | crawler.streetDirtMultiplier = xmlFile:getValue(key.."#streetDirtMultiplier", -150) |
286 | crawler.minDirtPercentage = xmlFile:getValue(key.."#minDirtPercentage", 0.35) |
287 | crawler.maxDirtOffset = xmlFile:getValue(key.."#maxDirtOffset", 0.5) |
288 | crawler.dirtColorChangeSpeed = 1 / (xmlFile:getValue(key.."#dirtColorChangeSpeed", 20) * 1000) |
289 | |
290 | local filename = xmlFile:getValue(key .. "#filename") |
291 | self:loadCrawlerFromConfigFile(crawler, filename, linkNode) |
292 | end |
324 | function Crawlers:onCrawlerI3DLoaded(i3dNode, failedReason, args) |
325 | local xmlFile = args.xmlFile |
326 | local crawler = args.crawler |
327 | local spec = self.spec_crawlers |
328 | |
329 | if i3dNode ~= 0 then |
330 | local leftRightKey = (crawler.isLeft and "leftNode") or "rightNode" |
331 | crawler.loadedCrawler = xmlFile:getValue("crawler.file#"..leftRightKey, nil, i3dNode) |
332 | if crawler.loadedCrawler ~= nil then |
333 | link(crawler.linkNode, crawler.loadedCrawler) |
334 | |
335 | if crawler.translationOffset ~= nil then |
336 | setTranslation(crawler.loadedCrawler, unpack(crawler.translationOffset)) |
337 | end |
338 | |
339 | crawler.scrollerNodes = {} |
340 | local j = 0 |
341 | while true do |
342 | local key = string.format("crawler.scrollerNodes.scrollerNode(%d)", j) |
343 | if not xmlFile:hasProperty(key) then |
344 | break |
345 | end |
346 | |
347 | local entry = {} |
348 | entry.node = xmlFile:getValue(key.."#node", nil, crawler.loadedCrawler) |
349 | if entry.node ~= nil then |
350 | entry.scrollSpeed = xmlFile:getValue(key.."#scrollSpeed", 1) |
351 | entry.scrollLength = xmlFile:getValue(key.."#scrollLength", 1) |
352 | entry.shaderParameterName = xmlFile:getValue(key.."#shaderParameterName", "offsetUV") |
353 | entry.shaderParameterNamePrev = xmlFile:getValue(key.."#shaderParameterNamePrev") |
354 | if entry.shaderParameterNamePrev ~= nil then |
355 | if not getHasShaderParameter(entry.node, entry.shaderParameterNamePrev) then |
356 | Logging.xmlWarning(xmlFile, "Node '%s' has no shader parameter '%s' (prev) for crawler node '%s'!", getName(entry.node), entry.shaderParameterNamePrev, key) |
357 | return nil |
358 | end |
359 | else |
360 | local prevName = "prev" .. entry.shaderParameterName:sub(1, 1):upper() .. entry.shaderParameterName:sub(2) |
361 | if getHasShaderParameter(entry.node, prevName) then |
362 | entry.shaderParameterNamePrev = prevName |
363 | end |
364 | end |
365 | |
366 | entry.shaderParameterComponent = xmlFile:getValue(key.."#shaderParameterComponent", 1) |
367 | entry.maxSpeed = xmlFile:getValue(key.."#maxSpeed", math.huge) / 1000 |
368 | entry.scrollPosition = 0 |
369 | |
370 | if crawler.trackWidth ~= 1 then |
371 | if xmlFile:getValue(key.."#isTrackPart", true) then |
372 | setScale(entry.node, crawler.trackWidth, 1, 1) |
373 | end |
374 | end |
375 | |
376 | table.insert(crawler.scrollerNodes, entry) |
377 | end |
378 | j = j + 1 |
379 | end |
380 | |
381 | crawler.rotatingParts = {} |
382 | j = 0 |
383 | while true do |
384 | local key = string.format("crawler.rotatingParts.rotatingPart(%d)", j) |
385 | if not xmlFile:hasProperty(key) then |
386 | break |
387 | end |
388 | |
389 | local entry = {} |
390 | entry.node = xmlFile:getValue(key.."#node", nil, crawler.loadedCrawler) |
391 | if entry.node ~= nil then |
392 | entry.radius = xmlFile:getValue(key.."#radius") |
393 | entry.speedScale = xmlFile:getValue(key.."#speedScale") |
394 | if entry.speedScale == nil and entry.radius ~= nil then |
395 | entry.speedScale = 1.0 / entry.radius |
396 | end |
397 | |
398 | table.insert(crawler.rotatingParts, entry) |
399 | end |
400 | |
401 | j = j + 1 |
402 | end |
403 | |
404 | local applyColor = function(name, color) |
405 | j = 0 |
406 | while true do |
407 | local key = string.format("crawler.%s.%s(%d)", name.."s", name, j) |
408 | if not xmlFile:hasProperty(key) then |
409 | break |
410 | end |
411 | |
412 | local node = xmlFile:getValue(key.."#node", nil, crawler.loadedCrawler) |
413 | if node ~= nil then |
414 | local shaderParameter = xmlFile:getValue(key.."#shaderParameter") |
415 | if getHasShaderParameter(node, shaderParameter) then |
416 | local r, g, b, mat = unpack(color) |
417 | if mat == nil then |
418 | local _ |
419 | _, _, _, mat = getShaderParameter(node, shaderParameter) |
420 | end |
421 | I3DUtil.setShaderParameterRec(node, shaderParameter, r, g, b, mat, true) |
422 | else |
423 | Logging.xmlWarning(xmlFile, "Missing shaderParameter '%s' on object '%s' in %s", shaderParameter, getName(node), key) |
424 | end |
425 | end |
426 | |
427 | j = j + 1 |
428 | end |
429 | end |
430 | |
431 | crawler.hasDirtNodes = false |
432 | crawler.dirtNodes = {} |
433 | j = 0 |
434 | while true do |
435 | local key = string.format("crawler.dirtNodes.dirtNode(%d)", j) |
436 | if not xmlFile:hasProperty(key) then |
437 | break |
438 | end |
439 | |
440 | local node = xmlFile:getValue(key.."#node", nil, crawler.loadedCrawler) |
441 | if node ~= nil then |
442 | crawler.dirtNodes[node] = node |
443 | crawler.hasDirtNodes = true |
444 | end |
445 | |
446 | j = j + 1 |
447 | end |
448 | |
449 | local rimColor = Utils.getNoNil(ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations["rimColor"]), self.spec_wheels.rimColor) |
450 | if rimColor ~= nil then |
451 | crawler.rimColorNodes = applyColor("rimColorNode", rimColor) |
452 | end |
453 | |
454 | crawler.objectChanges = {} |
455 | ObjectChangeUtil.loadObjectChangeFromXML(xmlFile, "crawler", crawler.objectChanges, crawler.loadedCrawler, self) |
456 | ObjectChangeUtil.setObjectChanges(crawler.objectChanges, true) |
457 | |
458 | local i = 0 |
459 | while true do |
460 | local key = string.format("crawler.animations.animation(%d)", i) |
461 | if not xmlFile:hasProperty(key) then |
462 | break |
463 | end |
464 | |
465 | if crawler.isLeft == xmlFile:getValue(key .. "#isLeft", false) then |
466 | local animation = {} |
467 | if self:loadAnimation(xmlFile, key, animation, crawler.loadedCrawler) then |
468 | self.spec_animatedVehicle.animations[animation.name] = animation |
469 | end |
470 | end |
471 | |
472 | i = i + 1 |
473 | end |
474 | |
475 | table.insert(self.spec_crawlers.crawlers, crawler) |
476 | end |
477 | |
478 | delete(i3dNode) |
479 | else |
480 | if not (self.isDeleted or self.isDeleting) then |
481 | Logging.xmlWarning(xmlFile, "Failed to find crawler in i3d file '%s'", crawler.filename) |
482 | end |
483 | end |
484 | |
485 | xmlFile:delete() |
486 | spec.xmlLoadingHandles[xmlFile] = nil |
487 | end |
159 | function Crawlers:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
160 | local spec = self.spec_crawlers |
161 | for _, crawler in pairs(spec.crawlers) do |
162 | crawler.movedDistance = 0 |
163 | |
164 | if crawler.speedReferenceNode ~= nil then |
165 | local newX, newY, newZ = getWorldTranslation(crawler.speedReferenceNode) |
166 | if crawler.lastPosition == nil then |
167 | crawler.lastPosition = {newX, newY, newZ} |
168 | end |
169 | local dx, dy, dz = worldDirectionToLocal(crawler.speedReferenceNode, newX-crawler.lastPosition[1], newY-crawler.lastPosition[2], newZ-crawler.lastPosition[3]) |
170 | local movingDirection = 0 |
171 | if dz > 0.0001 then |
172 | movingDirection = 1 |
173 | elseif dz < -0.0001 then |
174 | movingDirection = -1 |
175 | end |
176 | crawler.movedDistance = MathUtil.vector3Length(dx, dy, dz) * movingDirection |
177 | crawler.lastPosition[1] = newX |
178 | crawler.lastPosition[2] = newY |
179 | crawler.lastPosition[3] = newZ |
180 | else |
181 | crawler.movedDistance = self:getCrawlerWheelMovedDistance(crawler, "lastRotationScroll", false) |
182 | end |
183 | |
184 | for _, scrollerNode in pairs(crawler.scrollerNodes) do |
185 | local movedDistance = crawler.movedDistance * scrollerNode.scrollSpeed |
186 | local moveDirection = MathUtil.sign(movedDistance) |
187 | movedDistance = math.min(math.abs(movedDistance), scrollerNode.maxSpeed) * moveDirection |
188 | scrollerNode.scrollPosition = (scrollerNode.scrollPosition + movedDistance) % scrollerNode.scrollLength |
189 | |
190 | local x, y, z, w = getShaderParameter(scrollerNode.node, scrollerNode.shaderParameterName) |
191 | if scrollerNode.shaderParameterComponent == 1 then |
192 | x = scrollerNode.scrollPosition |
193 | else |
194 | y = scrollerNode.scrollPosition |
195 | end |
196 | |
197 | if scrollerNode.shaderParameterNamePrev ~= nil then |
198 | g_animationManager:setPrevShaderParameter(scrollerNode.node, scrollerNode.shaderParameterName, x, y, z, w, false, scrollerNode.shaderParameterNamePrev) |
199 | else |
200 | setShaderParameter(scrollerNode.node, scrollerNode.shaderParameterName, x, y, z, w, false) |
201 | end |
202 | end |
203 | |
204 | local rotationDifference = self:getCrawlerWheelMovedDistance(crawler, "lastRotationRot", true) |
205 | for _, rotatingPart in pairs(crawler.rotatingParts) do |
206 | if crawler.wheel ~= nil and rotatingPart.speedScale == nil then |
207 | rotate(rotatingPart.node, rotationDifference, 0, 0) |
208 | elseif rotatingPart.speedScale ~= nil then |
209 | rotate(rotatingPart.node, rotatingPart.speedScale * crawler.movedDistance, 0, 0) |
210 | end |
211 | end |
212 | end |
213 | end |
549 | function Crawlers:validateWashableNode(superFunc, node) |
550 | local spec = self.spec_crawlers |
551 | for _, crawler in pairs(spec.crawlers) do |
552 | |
553 | local crawlerNodes = crawler.dirtNodes |
554 | if not crawler.hasDirtNodes then |
555 | I3DUtil.getNodesByShaderParam(crawler.loadedCrawler, "RDT", crawlerNodes) |
556 | end |
557 | |
558 | if crawlerNodes[node] ~= nil then |
559 | local nodeData = {} |
560 | nodeData.wheel = crawler.wheel |
561 | nodeData.fieldDirtMultiplier = crawler.fieldDirtMultiplier |
562 | nodeData.streetDirtMultiplier = crawler.streetDirtMultiplier |
563 | nodeData.minDirtPercentage = crawler.minDirtPercentage |
564 | nodeData.maxDirtOffset = crawler.maxDirtOffset |
565 | nodeData.dirtColorChangeSpeed = crawler.dirtColorChangeSpeed |
566 | nodeData.isSnowNode = true |
567 | |
568 | nodeData.loadFromSavegameFunc = function(xmlFile, key) |
569 | nodeData.wheel.snowScale = xmlFile:getValue(key.."#snowScale", 0) |
570 | |
571 | local defaultColor, snowColor = g_currentMission.environment:getDirtColors() |
572 | local r, g, b = MathUtil.vector3ArrayLerp(defaultColor, snowColor, nodeData.wheel.snowScale) |
573 | local washableNode = self:getWashableNodeByCustomIndex(crawler) |
574 | self:setNodeDirtColor(washableNode, r, g, b, true) |
575 | end |
576 | nodeData.saveToSavegameFunc = function(xmlFile, key) |
577 | xmlFile:setValue(key.."#snowScale", nodeData.wheel.snowScale) |
578 | end |
579 | |
580 | return false, self.updateWheelDirtAmount, crawler, nodeData |
581 | end |
582 | end |
583 | |
584 | return superFunc(self, node) |
585 | end |