398 | function Wheels:finalizeWheel(wheel, parentWheel) |
399 | local spec = self.spec_wheels |
400 | |
401 | if parentWheel == nil and wheel.repr ~= nil then |
402 | wheel.startPositionX, wheel.startPositionY, wheel.startPositionZ = getTranslation(wheel.repr) |
403 | wheel.driveNodeStartPosX, wheel.driveNodeStartPosY, wheel.driveNodeStartPosZ = getTranslation(wheel.driveNode) |
404 | wheel.dirtAmount = 0 |
405 | wheel.xDriveOffset = 0 |
406 | wheel.lastColor = {0,0,0,0} |
407 | wheel.lastTerrainAttribute = 0 |
408 | wheel.contact = Wheels.WHEEL_NO_CONTACT |
409 | wheel.steeringAngle = 0 |
410 | wheel.lastMovement = 0 |
411 | wheel.hasGroundContact = false |
412 | wheel.hasHandbrake = true |
413 | |
414 | local vehicleNode = self.vehicleNodes[wheel.node] |
415 | if vehicleNode ~= nil and vehicleNode.component ~= nil and vehicleNode.component.motorized == nil then |
416 | vehicleNode.component.motorized = true |
417 | end |
418 | |
419 | if wheel.useReprDirection then |
420 | wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.repr, wheel.node, 0,-1,0) |
421 | wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.repr, wheel.node, 1,0,0) |
422 | elseif wheel.useDriveNodeDirection then |
423 | wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.driveNodeDirectionNode, wheel.node, 0,-1,0) |
424 | wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.driveNodeDirectionNode, wheel.node, 1,0,0) |
425 | else |
426 | wheel.directionX, wheel.directionY, wheel.directionZ = 0,-1,0 |
427 | wheel.axleX, wheel.axleY, wheel.axleZ = 1,0,0 |
428 | end |
429 | wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = 0,0,0 |
430 | if wheel.repr ~= wheel.driveNode then |
431 | wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = localToLocal(wheel.driveNode, wheel.repr, 0, 0, 0) |
432 | wheel.steeringCenterOffsetX = -wheel.steeringCenterOffsetX |
433 | wheel.steeringCenterOffsetY = -wheel.steeringCenterOffsetY |
434 | wheel.steeringCenterOffsetZ = -wheel.steeringCenterOffsetZ |
435 | end |
436 | |
437 | if g_currentMission.tireTrackSystem ~= nil and wheel.hasTireTracks then |
438 | wheel.tireTrackIndex = g_currentMission.tireTrackSystem:createTrack(wheel.width, wheel.tireTrackAtlasIndex) |
439 | end |
440 | |
441 | wheel.maxLatStiffness = wheel.maxLatStiffness*wheel.restLoad |
442 | wheel.maxLatStiffnessLoad = wheel.maxLatStiffnessLoad*wheel.restLoad |
443 | |
444 | wheel.mass = wheel.mass + wheel.additionalMass |
445 | |
446 | wheel.lastTerrainValue = 0 |
447 | wheel.sink = 0 |
448 | wheel.sinkTarget = 0 |
449 | wheel.radiusOriginal = wheel.radius |
450 | wheel.sinkFrictionScaleFactor = 1 |
451 | wheel.sinkLongStiffnessFactor = 1 |
452 | wheel.sinkLatStiffnessFactor = 1 |
453 | |
454 | local positionY = wheel.positionY+wheel.deltaY |
455 | wheel.netInfo = {} |
456 | wheel.netInfo.xDrive = 0 |
457 | wheel.netInfo.x = wheel.positionX |
458 | wheel.netInfo.y = positionY |
459 | wheel.netInfo.z = wheel.positionZ |
460 | wheel.netInfo.suspensionLength = wheel.suspTravel*0.5 |
461 | |
462 | -- The suspension elongates by 20% of the specified susp travel |
463 | wheel.netInfo.sync = {yMin = -5, yRange = 10} |
464 | wheel.netInfo.yMin = positionY-1.2*wheel.suspTravel |
465 | |
466 | -- |
467 | self:updateWheelBase(wheel) |
468 | self:updateWheelTireFriction(wheel) |
469 | |
470 | wheel.networkInterpolators = {} |
471 | wheel.networkInterpolators.xDrive = InterpolatorAngle:new(wheel.netInfo.xDrive) |
472 | wheel.networkInterpolators.position = InterpolatorPosition:new(wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z) |
473 | wheel.networkInterpolators.suspensionLength = InterpolatorValue:new(wheel.netInfo.suspensionLength) |
474 | end |
475 | |
476 | local loadWheelPart = function(wheel, parent, name, filename, index, offset, widthAndDiam, scale) |
477 | if filename == nil then |
478 | return |
479 | end |
480 | |
481 | local i3dNode = g_i3DManager:loadSharedI3DFile(filename, self.baseDirectory, false, false, false) |
482 | if i3dNode ~= 0 then |
483 | wheel[name] = I3DUtil.indexToObject(i3dNode, index, self.i3dMappings) |
484 | if wheel[name] ~= nil then |
485 | link(parent, wheel[name]) |
486 | delete(i3dNode) |
487 | |
488 | if offset ~= 0 then |
489 | local dir = 1 |
490 | if not wheel.isLeft then |
491 | dir = -1 |
492 | end |
493 | setTranslation(wheel[name], offset*dir, 0, 0) |
494 | end |
495 | if scale ~= nil then |
496 | setScale(wheel[name], scale[1], scale[2], scale[3]) |
497 | end |
498 | if widthAndDiam ~= nil then |
499 | if getHasShaderParameter(wheel[name], "widthAndDiam") then |
500 | setShaderParameter(wheel[name], "widthAndDiam", widthAndDiam[1], widthAndDiam[2], 0, 0, false) |
501 | else |
502 | -- convert width and diam to scale (mesh is normalized to 1 meter) |
503 | local scaleX = MathUtil.inchToM(widthAndDiam[1]) |
504 | local scaleZY = MathUtil.inchToM(widthAndDiam[2]) |
505 | setScale(wheel[name], scaleX, scaleZY, scaleZY) |
506 | end |
507 | end |
508 | else |
509 | g_logManager:xmlWarning(self.configFileName, "Failed to load node '%s' for file '%s'", index, filename) |
510 | end |
511 | else |
512 | g_logManager:xmlWarning(self.configFileName, "Failed to load file '%s' wheel part '%s'", filename, name) |
513 | end |
514 | end |
515 | |
516 | if parentWheel ~= nil then |
517 | wheel.linkNode = createTransformGroup("linkNode") |
518 | link(parentWheel.driveNode, wheel.linkNode) |
519 | end |
520 | |
521 | loadWheelPart(wheel, wheel.linkNode, "wheelTire", wheel.tireFilename, wheel.tireNodeStr, 0, nil, nil) |
522 | loadWheelPart(wheel, wheel.linkNode, "wheelOuterRim", wheel.outerRimFilename, wheel.outerRimNodeStr, 0, wheel.outerRimWidthAndDiam, wheel.outerRimScale) |
523 | loadWheelPart(wheel, wheel.linkNode, "wheelInnerRim", wheel.innerRimFilename, wheel.innerRimNodeStr, wheel.innerRimOffset, wheel.innerRimWidthAndDiam, wheel.innerRimScale) |
524 | loadWheelPart(wheel, wheel.linkNode, "wheelAdditional", wheel.additionalFilename, wheel.additionalNodeStr, wheel.additionalOffset, wheel.additionalWidthAndDiam, wheel.additionalScale) |
525 | |
526 | if wheel.wheelTire ~= nil then |
527 | local zRot = 0 |
528 | if wheel.tireIsInverted then |
529 | zRot = MathUtil.degToRad(180) |
530 | end |
531 | setRotation(wheel.wheelTire, wheel.xRotOffset, 0, zRot) |
532 | |
533 | local x, y, z, _ = getShaderParameter(wheel.wheelTire, "morphPosition") |
534 | setShaderParameter(wheel.wheelTire, "morphPosition", x, y, z, 0, false) |
535 | end |
536 | |
537 | local configColor = ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations["rimColor"]) |
538 | local color = wheel.color or configColor or spec.rimColor |
539 | if color ~= nil then |
540 | local r, g, b, mat = unpack(color) |
541 | if wheel.wheelOuterRim ~= nil then |
542 | -- never use material from config color since it is always '1' |
543 | if mat == nil then |
544 | _,_,_,mat = getShaderParameter(wheel.wheelOuterRim, "colorMat0") |
545 | end |
546 | setShaderParameter(wheel.wheelOuterRim, "colorMat0", r, g, b, mat, false) |
547 | end |
548 | if wheel.wheelInnerRim ~= nil then |
549 | -- never use material from config color since it is always '1' |
550 | if mat == nil then |
551 | _,_,_,mat = getShaderParameter(wheel.wheelInnerRim, "colorMat0") |
552 | end |
553 | setShaderParameter(wheel.wheelInnerRim, "colorMat0", r, g, b, mat, false) |
554 | end |
555 | end |
556 | |
557 | local additionalColor = Utils.getNoNil(wheel.additionalColor, color) |
558 | if wheel.wheelAdditional ~= nil and additionalColor ~= nil then |
559 | local r,g,b,_ = unpack(additionalColor) |
560 | local _,_,_,w = getShaderParameter(wheel.wheelAdditional, "colorMat0") |
561 | setShaderParameter(wheel.wheelAdditional, "colorMat0", r, g, b, w, false) |
562 | end |
563 | |
564 | if wheel.additionalWheels ~= nil then |
565 | local outmostWheelWidth = 0 |
566 | local totalWheelshapeOffset = 0 |
567 | local offsetDir = -1 |
568 | for _, additionalWheel in pairs(wheel.additionalWheels) do |
569 | self:finalizeWheel(additionalWheel, wheel) |
570 | |
571 | local baseWheelWidth = MathUtil.mToInch(wheel.width) |
572 | local dualWheelWidth = MathUtil.mToInch(additionalWheel.width) |
573 | local diameter = 0 |
574 | local wheelOffset = MathUtil.mToInch(additionalWheel.offset) |
575 | |
576 | if wheel.outerRimWidthAndDiam ~= nil then |
577 | baseWheelWidth = wheel.outerRimWidthAndDiam[1] |
578 | diameter = wheel.outerRimWidthAndDiam[2] |
579 | end |
580 | if additionalWheel.outerRimWidthAndDiam ~= nil then |
581 | dualWheelWidth = additionalWheel.outerRimWidthAndDiam[1] |
582 | end |
583 | |
584 | if additionalWheel.isLeft then |
585 | offsetDir = 1 |
586 | end |
587 | |
588 | if wheel.tireIsInverted then |
589 | setRotation(additionalWheel.wheelTire, 0, 0, math.pi) |
590 | end |
591 | |
592 | local totalOffset = 0 |
593 | totalOffset = totalOffset + offsetDir * MathUtil.inchToM(0.5*baseWheelWidth + wheelOffset + 0.5*dualWheelWidth ) |
594 | |
595 | -- get wheel with furthest offset |
596 | if math.abs(totalOffset) > math.abs(totalWheelshapeOffset) then |
597 | totalWheelshapeOffset = math.abs(totalOffset) |
598 | outmostWheelWidth = additionalWheel.width |
599 | end |
600 | |
601 | if additionalWheel.connector ~= nil then |
602 | self:finalizeConnector(wheel, additionalWheel.connector, diameter, baseWheelWidth, wheelOffset, offsetDir, dualWheelWidth) |
603 | end |
604 | |
605 | local x,y,z = getTranslation(additionalWheel.linkNode) |
606 | setTranslation(additionalWheel.linkNode, x+totalOffset,y,z) |
607 | |
608 | if additionalWheel.driveGroundParticleSystems ~= nil then |
609 | for name, ps in pairs(additionalWheel.driveGroundParticleSystems) do |
610 | ps.offsets[1] = ps.offsets[1] + totalOffset |
611 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)) |
612 | setTranslation(ps.emitterShape, wx + ps.offsets[1], wy + ps.offsets[2], wz + ps.offsets[3]) |
613 | table.insert(wheel.driveGroundParticleSystems[name], ps) |
614 | end |
615 | end |
616 | end |
617 | |
618 | wheel.widthOffset = wheel.widthOffset + offsetDir * (totalWheelshapeOffset/2) |
619 | local wheelX, _, _ = getTranslation(wheel.wheelTire) |
620 | local additionalWheelX = wheelX + totalWheelshapeOffset |
621 | wheel.wheelshapeWidth = (wheel.width/2 + math.abs(wheelX-additionalWheelX) + outmostWheelWidth/2) |
622 | end |
623 | end |
2008 | function Wheels:loadDynamicWheelDataFromXML(xmlFile, key, wheelnamei, wheel) |
2009 | local spec = self.spec_wheels |
2010 | |
2011 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, string.format("vehicle.wheels%s#hasTyreTracks", wheelnamei), string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels%s#hasTireTracks", wheelnamei)) |
2012 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, string.format("vehicle.wheels%s#tyreTrackAtlasIndex", wheelnamei), string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels%s#tireTrackAtlasIndex", wheelnamei)) |
2013 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, string.format("vehicle.wheels%s#configIndex", wheelnamei), string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels%s#configId", wheelnamei)) |
2014 | |
2015 | local colorStr = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#color", getXMLString, nil, nil, nil) |
2016 | if colorStr ~= nil then |
2017 | wheel.color = ConfigurationUtil.getColorFromString(colorStr) |
2018 | end |
2019 | |
2020 | local additionalColorStr = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#additionalColor", getXMLString, nil, nil, nil) |
2021 | if additionalColorStr ~= nil then |
2022 | wheel.additionalColor = ConfigurationUtil.getColorFromString(additionalColorStr) |
2023 | end |
2024 | |
2025 | wheel.isLeft = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#isLeft", getXMLBool, true, nil, nil) |
2026 | |
2027 | local wheelXmlFilename = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#filename", getXMLString, nil, nil, nil) |
2028 | if wheelXmlFilename ~= nil and wheelXmlFilename ~= "" then |
2029 | local wheelConfigId = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#configId", getXMLString, "default", nil, nil) |
2030 | wheel.xRotOffset = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#xRotOffset", getXMLFloat, 0, nil, nil) |
2031 | self:loadWheelDataFromExternalXML(wheel, wheelXmlFilename, wheelConfigId, true) |
2032 | end |
2033 | |
2034 | self:loadWheelData(wheel, xmlFile, key .. wheelnamei) |
2035 | |
2036 | if wheel.mass == nil then |
2037 | g_logManager:xmlWarning(self.configFileName, "Missing 'mass' for wheel '%s'. Using default '0.1'!", string.format(key.."%s", wheelnamei)) |
2038 | wheel.mass = 0.1 |
2039 | end |
2040 | |
2041 | self:loadWheelPhysicsData(self.xmlFile, key, wheelnamei, wheel) |
2042 | |
2043 | local key, _ = ConfigurationUtil.getXMLConfigurationKey(xmlFile, self.configurations["wheel"], "vehicle.wheels.wheelConfigurations.wheelConfiguration", "vehicle", "wheels") |
2044 | key = key .. ".wheels" |
2045 | local i = 0 |
2046 | while true do |
2047 | local additionalWheelKey = string.format(key..wheelnamei..".additionalWheel(%d)", i) |
2048 | if not hasXMLProperty(xmlFile, additionalWheelKey) then |
2049 | break |
2050 | end |
2051 | |
2052 | local wheelXmlFilename = getXMLString(xmlFile, additionalWheelKey.."#filename") |
2053 | if wheelXmlFilename ~= nil and wheelXmlFilename ~= "" then |
2054 | |
2055 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, additionalWheelKey.."#configIndex", additionalWheelKey.."#configId") |
2056 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, additionalWheelKey.."#addRaycast", nil) |
2057 | |
2058 | if wheel.additionalWheels == nil then |
2059 | wheel.additionalWheels = {} |
2060 | end |
2061 | local additionalWheel = {} |
2062 | additionalWheel.node = wheel.node |
2063 | additionalWheel.key = additionalWheelKey |
2064 | additionalWheel.linkNode = wheel.linkNode |
2065 | |
2066 | local wheelConfigId = Utils.getNoNil(getXMLString(xmlFile, additionalWheelKey.."#configId"), "default") |
2067 | additionalWheel.isLeft = Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#isLeft"), wheel.isLeft) or false |
2068 | additionalWheel.xRotOffset = Utils.getNoNilRad(getXMLFloat(xmlFile, additionalWheelKey.."#xRotOffset"), 0) |
2069 | additionalWheel.color = Utils.getNoNil(ConfigurationUtil.getColorFromString(getXMLString(xmlFile, additionalWheelKey.."#color")), wheel.color) |
2070 | |
2071 | self:loadWheelDataFromExternalXML(additionalWheel, wheelXmlFilename, wheelConfigId, false) |
2072 | |
2073 | additionalWheel.hasParticles = Utils.getNoNil(Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#hasParticles"), wheel.hasParticles), false) |
2074 | additionalWheel.hasTireTracks = Utils.getNoNil(Utils.getNoNil(getXMLBool(xmlFile, additionalWheelKey.."#hasTireTracks"), wheel.hasTireTracks), false) |
2075 | if g_currentMission.tireTrackSystem ~= nil and additionalWheel.hasTireTracks then |
2076 | additionalWheel.tireTrackIndex = g_currentMission.tireTrackSystem:createTrack(additionalWheel.width, additionalWheel.tireTrackAtlasIndex) |
2077 | end |
2078 | |
2079 | additionalWheel.offset = Utils.getNoNil(getXMLFloat(xmlFile, additionalWheelKey.."#offset"), 0) |
2080 | |
2081 | self:loadConnectorFromXML(wheel, additionalWheel, xmlFile, additionalWheelKey) |
2082 | |
2083 | table.insert(wheel.additionalWheels, additionalWheel) |
2084 | |
2085 | wheel.mass = wheel.mass + additionalWheel.mass |
2086 | wheel.maxLatStiffness = wheel.maxLatStiffness + additionalWheel.maxLatStiffness |
2087 | wheel.maxLongStiffness = wheel.maxLongStiffness + additionalWheel.maxLongStiffness |
2088 | end |
2089 | |
2090 | i = i + 1 |
2091 | end |
2092 | |
2093 | local i = 0 |
2094 | while true do |
2095 | local chockKey = string.format("%s%s.wheelChock(%d)", key, wheelnamei, i) |
2096 | if not hasXMLProperty(self.xmlFile, chockKey) then |
2097 | break |
2098 | end |
2099 | |
2100 | local filename = Utils.getNoNil(getXMLString(xmlFile, chockKey.."#filename"), "$data/shared/assets/wheelChocks/wheelChock01.i3d") |
2101 | local i3dNode = g_i3DManager:loadSharedI3DFile(filename, self.baseDirectory, false, false, false) |
2102 | if i3dNode ~= 0 then |
2103 | local chockNode = getChildAt(i3dNode, 0) |
2104 | local posRefNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "posRefNode"), self.i3dMappings) |
2105 | if posRefNode ~= nil then |
2106 | local chock = {} |
2107 | chock.wheel = wheel |
2108 | chock.node = chockNode |
2109 | chock.filename = filename |
2110 | chock.scale = Utils.getNoNil(StringUtil.getVectorNFromString(getXMLString(xmlFile, chockKey.."#scale"), 3), {1,1,1}) |
2111 | setScale(chock.node, unpack(chock.scale)) |
2112 | |
2113 | chock.parkingNode = I3DUtil.indexToObject(self.components, getXMLString(xmlFile, chockKey.."#parkingNode"), self.i3dMappings) |
2114 | chock.isInverted = Utils.getNoNil(getXMLBool(xmlFile, chockKey.."#isInverted"), false) |
2115 | chock.isParked = Utils.getNoNil(getXMLBool(xmlFile, chockKey.."#isParked"), false) |
2116 | _, chock.height, chock.zOffset = localToLocal(posRefNode, chock.node, 0, 0, 0) |
2117 | chock.height = chock.height / chock.scale[2] |
2118 | chock.zOffset = chock.zOffset / chock.scale[3] |
2119 | |
2120 | chock.offset = Utils.getNoNil(StringUtil.getVectorNFromString(getXMLString(xmlFile, chockKey.."#offset"), 3), {0,0,0}) |
2121 | |
2122 | chock.parkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "parkedNode"), self.i3dMappings) |
2123 | chock.linkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "linkedNode"), self.i3dMappings) |
2124 | |
2125 | local color = g_brandColorManager:loadColorAndMaterialFromXML(self.configFileName, chockNode, "colorMat0", xmlFile, chockKey, "color", false) |
2126 | if color ~= nil then |
2127 | I3DUtil.setShaderParameterRec(chockNode, "colorMat0", color[1], color[2], color[3], color[4]) |
2128 | end |
2129 | |
2130 | self:updateWheelChockPosition(chock, chock.isParked) |
2131 | |
2132 | wheel.updateWheelChock = false |
2133 | if wheel.wheelChocks == nil then |
2134 | wheel.wheelChocks = {} |
2135 | end |
2136 | table.insert(wheel.wheelChocks, chock) |
2137 | |
2138 | table.insert(spec.wheelChocks, chock) |
2139 | else |
2140 | g_logManager:xmlWarning(self.configFileName, "Missing 'posRefNode'-userattribute for wheel-chock '%s'!", chockKey) |
2141 | end |
2142 | |
2143 | delete(i3dNode) |
2144 | end |
2145 | i=i+1 |
2146 | end |
2147 | |
2148 | if wheel.hasParticles then |
2149 | self:loadWheelParticleSystem(wheel, xmlFile, key .. wheelnamei) |
2150 | end |
2151 | |
2152 | if wheel.driveGroundParticleSystems ~= nil then |
2153 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)) |
2154 | for _,typedPs in pairs(wheel.driveGroundParticleSystems) do |
2155 | for _, ps in ipairs(typedPs) do |
2156 | setTranslation(ps.rootNode, wx + ps.offsets[1], wy - wheel.radius + ps.offsets[2], wz + ps.offsets[3]) |
2157 | end |
2158 | end |
2159 | end |
2160 | |
2161 | wheel.wheelShape = 0 |
2162 | wheel.wheelShapeCreated = false |
2163 | end |
2310 | function Wheels:loadWheelData(wheel, xmlFile, configKey) |
2311 | local key = "nodeLeft" |
2312 | if not wheel.isLeft then |
2313 | key = "nodeRight" |
2314 | end |
2315 | |
2316 | wheel.radius = getXMLFloat(xmlFile, configKey..".physics#radius") or wheel.radius |
2317 | if wheel.radius == nil then |
2318 | g_logManager:xmlWarning(self.configFileName, "No radius defined for wheel '%s'! Using default value of 0.5!", configKey..".physics#radius") |
2319 | wheel.radius = 0.5 |
2320 | end |
2321 | wheel.width = getXMLFloat(xmlFile, configKey..".physics#width") or wheel.width |
2322 | if wheel.width == nil then |
2323 | g_logManager:xmlWarning(self.configFileName, "No width defined for wheel '%s'! Using default value of 0.5!", configKey..".physics#width") |
2324 | wheel.width = 0.5 |
2325 | end |
2326 | wheel.mass = getXMLFloat(xmlFile, configKey..".physics#mass") or wheel.mass or 0.1 |
2327 | |
2328 | local tireTypeName = getXMLString(xmlFile, configKey..".tire#tireType") |
2329 | if tireTypeName ~= nil then |
2330 | local tireType = WheelsUtil.getTireType(tireTypeName) |
2331 | if tireType ~= nil then |
2332 | wheel.tireType = tireType |
2333 | else |
2334 | g_logManager:xmlWarning(self.configFileName, "Tire type '%s' not defined!", tireTypeName) |
2335 | end |
2336 | end |
2337 | |
2338 | wheel.frictionScale = getXMLFloat(xmlFile, configKey..".physics#frictionScale") or wheel.frictionScale |
2339 | wheel.maxLongStiffness = getXMLFloat(xmlFile, configKey..".physics#maxLongStiffness") or wheel.maxLongStiffness -- [t / rad] |
2340 | wheel.maxLatStiffness = getXMLFloat(xmlFile, configKey..".physics#maxLatStiffness") or wheel.maxLatStiffness -- xml is ratio to restLoad [1/rad], final value is [t / rad] |
2341 | wheel.maxLatStiffnessLoad = getXMLFloat(xmlFile, configKey..".physics#maxLatStiffnessLoad") or wheel.maxLatStiffnessLoad -- xml is ratio to restLoad, final value is [t] |
2342 | |
2343 | wheel.tireTrackAtlasIndex = getXMLInt(xmlFile, configKey..".tire#tireTrackAtlasIndex") or wheel.tireTrackAtlasIndex or 0 |
2344 | wheel.widthOffset = getXMLFloat(xmlFile, configKey..".tire#widthOffset") or wheel.widthOffset or 0.0 |
2345 | wheel.xOffset = getXMLFloat(xmlFile, configKey..".tire#xOffset") or wheel.xOffset or 0 |
2346 | wheel.maxDeformation = getXMLFloat(xmlFile, configKey..".tire#maxDeformation") or wheel.maxDeformation or 0 |
2347 | wheel.deformation = 0 |
2348 | wheel.isCareWheel = Utils.getNoNil(Utils.getNoNil(getXMLBool(xmlFile, configKey..".tire#isCareWheel"), wheel.isCareWheel), true) |
2349 | wheel.smoothGroundRadius = getXMLFloat(xmlFile, configKey..".physics#smoothGroundRadius") or math.max(0.6, wheel.width*0.75) |
2350 | |
2351 | wheel.tireFilename = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".tire#filename", "", getXMLString, wheel.tireFilename) |
2352 | wheel.tireIsInverted = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".tire#isInverted", "", getXMLBool, wheel.tireIsInverted) |
2353 | wheel.tireNodeStr = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".tire#node", "", getXMLString, nil) or XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".tire#"..key, "", getXMLString, wheel.tireNodeStr) |
2354 | wheel.outerRimFilename = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".outerRim#filename", "", getXMLString, wheel.outerRimFilename) |
2355 | wheel.outerRimNodeStr = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".outerRim#node", "", getXMLString, wheel.outerRimNodeStr) or "0|0" |
2356 | wheel.outerRimWidthAndDiam = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".outerRim#widthAndDiam", "", getXMLString, wheel.outerRimWidthAndDiam, StringUtil.getVectorNFromString, 2) |
2357 | wheel.outerRimScale = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".outerRim#scale", "", getXMLString, wheel.outerRimScale, StringUtil.getVectorNFromString, 3) |
2358 | wheel.innerRimFilename = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#filename", "", getXMLString, wheel.innerRimFilename) |
2359 | wheel.innerRimNodeStr = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#node", "", getXMLString, nil) or XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#"..key, "", getXMLString, wheel.innerRimNodeStr) |
2360 | wheel.innerRimWidthAndDiam = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#widthAndDiam", "", getXMLString, wheel.innerRimWidthAndDiam, StringUtil.getVectorNFromString, 2) |
2361 | wheel.innerRimOffset = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#offset", "", getXMLFloat, wheel.innerRimOffset) or 0 |
2362 | wheel.innerRimScale = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".innerRim#scale", "", getXMLString, wheel.innerRimScale, StringUtil.getVectorNFromString, 3); |
2363 | wheel.additionalFilename = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#filename", "", getXMLString, wheel.additionalFilename) |
2364 | wheel.additionalNodeStr = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#node", "", getXMLString, nil) or XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#"..key, "", getXMLString, wheel.additionalNodeStr) |
2365 | wheel.additionalOffset = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#offset", "", getXMLFloat, wheel.additionalOffset) or 0 |
2366 | wheel.additionalScale = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#scale", "", getXMLString, wheel.additionalScale, StringUtil.getVectorNFromString, 3) |
2367 | wheel.additionalMass = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#mass", "", getXMLFloat, wheel.additionalMass) or 0 |
2368 | wheel.additionalWidthAndDiam = XMLUtil.getXMLOverwrittenValue(xmlFile, configKey, ".additional#widthAndDiam", "", getXMLString, wheel.additionalWidthAndDiam, StringUtil.getVectorNFromString, 2) |
2369 | end |
706 | function Wheels:loadWheelPhysicsData(xmlFile, key, wheelnamei, wheel) |
707 | local physicsKey = wheelnamei .. ".physics" |
708 | if wheel.repr ~= nil then |
709 | wheel.node = self:getParentComponent(wheel.repr) |
710 | if wheel.node ~= 0 then |
711 | XMLUtil.checkDeprecatedXMLElements(xmlFile, self.configFileName, key..wheelnamei.."#steeringNode", string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels%s.steering#node", wheelnamei)) |
712 | |
713 | local driveNodeStr = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#driveNode", getXMLString, nil, nil, nil) |
714 | wheel.driveNode = I3DUtil.indexToObject(self.components, driveNodeStr, self.i3dMappings) |
715 | |
716 | if wheel.driveNode == wheel.repr then |
717 | g_logManager:xmlWarning(self.configFileName, "repr and driveNode may not be equal for '%s'. Using default driveNode instead!", key.."."..physicsKey) |
718 | wheel.driveNode = nil |
719 | end |
720 | |
721 | wheel.linkNode = I3DUtil.indexToObject(self.components, ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#linkNode", getXMLString, nil, nil, nil), self.i3dMappings) |
722 | |
723 | if wheel.driveNode == nil then |
724 | -- create a new repr and use repr as drivenode |
725 | local newRepr = createTransformGroup("wheelReprNode") |
726 | local reprIndex = getChildIndex(wheel.repr) |
727 | link(getParent(wheel.repr), newRepr, reprIndex) |
728 | setTranslation(newRepr, getTranslation(wheel.repr)) |
729 | setRotation(newRepr, getRotation(wheel.repr)) |
730 | setScale(newRepr, getScale(wheel.repr)) |
731 | wheel.driveNode = wheel.repr |
732 | |
733 | link(newRepr, wheel.driveNode) |
734 | setTranslation(wheel.driveNode, 0, 0, 0) |
735 | setRotation(wheel.driveNode, 0, 0, 0) |
736 | setScale(wheel.driveNode, 1, 1, 1) |
737 | wheel.repr = newRepr |
738 | end |
739 | |
740 | if wheel.driveNode ~= nil then |
741 | local driveNodeDirectionNode = createTransformGroup("driveNodeDirectionNode") |
742 | link(getParent(wheel.repr), driveNodeDirectionNode) |
743 | setWorldTranslation(driveNodeDirectionNode, getWorldTranslation(wheel.driveNode)) |
744 | setWorldRotation(driveNodeDirectionNode, getWorldRotation(wheel.driveNode)) |
745 | wheel.driveNodeDirectionNode = driveNodeDirectionNode |
746 | end |
747 | |
748 | if wheel.linkNode == nil then |
749 | wheel.linkNode = wheel.driveNode |
750 | end |
751 | |
752 | wheel.yOffset = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#yOffset", getXMLFloat, 0.0, nil, nil) |
753 | if wheel.yOffset ~= 0 then |
754 | -- move drivenode in y direction. Use convert yOffset from driveNode local space to driveNodeParent local space to translate according to directions |
755 | setTranslation(wheel.driveNode, localToLocal(wheel.driveNode, getParent(wheel.driveNode), 0, wheel.yOffset, 0)) |
756 | end |
757 | |
758 | wheel.showSteeringAngle = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#showSteeringAngle", getXMLBool, true, nil, nil) |
759 | wheel.suspTravel = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#suspTravel", getXMLFloat, 0.01, nil, nil) |
760 | local initialCompression = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#initialCompression", getXMLFloat, nil, nil, nil) |
761 | if initialCompression ~= nil then |
762 | wheel.deltaY = (1-initialCompression*0.01)*wheel.suspTravel |
763 | else |
764 | wheel.deltaY = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#deltaY", getXMLFloat, 0.0, nil, nil) |
765 | end |
766 | wheel.spring = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#spring", getXMLFloat, 0, nil, nil)*Vehicle.SPRING_SCALE |
767 | |
768 | wheel.torque = 0 |
769 | wheel.brakeFactor = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#brakeFactor", getXMLFloat, 1, nil, nil) |
770 | wheel.autoHoldBrakeFactor = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#autoHoldBrakeFactor", getXMLFloat, wheel.brakeFactor, nil, nil) |
771 | |
772 | wheel.damperCompressionLowSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperCompressionLowSpeed", getXMLFloat, nil, nil, nil) |
773 | wheel.damperRelaxationLowSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperRelaxationLowSpeed", getXMLFloat, nil, nil, nil) |
774 | if wheel.damperRelaxationLowSpeed == nil then |
775 | wheel.damperRelaxationLowSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damper", getXMLFloat, Utils.getNoNil(wheel.damperCompressionLowSpeed, 0), nil, nil) |
776 | end |
777 | -- by default, the high speed relaxation damper is set to 90% of the low speed relaxation damper |
778 | wheel.damperRelaxationHighSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperRelaxationHighSpeed", getXMLFloat, wheel.damperRelaxationLowSpeed * 0.7, nil, nil) |
779 | |
780 | -- by default, we set the low speed compression damper to 90% of the low speed relaxation damper |
781 | if wheel.damperCompressionLowSpeed == nil then |
782 | wheel.damperCompressionLowSpeed = wheel.damperRelaxationLowSpeed * 0.9 |
783 | end |
784 | -- by default, the high speed compression damper is set to 20% of the low speed compression damper |
785 | wheel.damperCompressionHighSpeed = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperCompressionHighSpeed", getXMLFloat, wheel.damperCompressionLowSpeed * 0.2, nil, nil) |
786 | wheel.damperCompressionLowSpeedThreshold = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperCompressionLowSpeedThreshold", getXMLFloat, 0.1016, nil, nil) -- default 4 inch / s |
787 | wheel.damperRelaxationLowSpeedThreshold = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#damperRelaxationLowSpeedThreshold", getXMLFloat, 0.1524, nil, nil) -- default 6 inch / s |
788 | |
789 | wheel.forcePointRatio = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#forcePointRatio", getXMLFloat, 0, nil, nil) |
790 | wheel.driveMode = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#driveMode", getXMLInt, 0, nil, nil) |
791 | wheel.xOffset = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#xOffset", getXMLFloat, 0, nil, nil) |
792 | |
793 | |
794 | wheel.transRatio = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#transRatio", getXMLFloat, 0.0, nil, nil) |
795 | |
796 | wheel.isSynchronized = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#isSynchronized", getXMLBool, true, nil, nil) |
797 | wheel.tipOcclusionAreaGroupId = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#tipOcclusionAreaGroupId", getXMLInt, nil, nil, nil) |
798 | |
799 | wheel.positionX, wheel.positionY, wheel.positionZ = localToLocal(wheel.driveNode, wheel.node, 0,0,0) |
800 | wheel.useReprDirection = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#useReprDirection", getXMLBool, false, nil, nil) |
801 | wheel.useDriveNodeDirection = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#useDriveNodeDirection", getXMLBool, false, nil, nil) |
802 | |
803 | |
804 | wheel.mass = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#mass", getXMLFloat, wheel.mass, nil, nil) |
805 | wheel.radius = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#radius", getXMLFloat, Utils.getNoNil(wheel.radius, 0.5), nil, nil) |
806 | wheel.width = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#width", getXMLFloat, Utils.getNoNil(wheel.width, 0.6), nil, nil) |
807 | wheel.wheelshapeWidth = wheel.width |
808 | wheel.widthOffset = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#widthOffset", getXMLFloat, 0, nil, nil) |
809 | wheel.restLoad = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#restLoad", getXMLFloat, Utils.getNoNil(wheel.restLoad, 1.0), nil, nil) -- [t] |
810 | wheel.maxLongStiffness = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#maxLongStiffness", getXMLFloat, Utils.getNoNil(wheel.maxLongStiffness, 30.0), nil, nil) -- [t / rad] |
811 | wheel.maxLatStiffness = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#maxLatStiffness", getXMLFloat, Utils.getNoNil(wheel.maxLatStiffness, 40.0), nil, nil) -- xml is ratio to restLoad [1/rad], final value is [t / rad] |
812 | wheel.maxLatStiffnessLoad = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#maxLatStiffnessLoad", getXMLFloat, Utils.getNoNil(wheel.maxLatStiffnessLoad, 2), nil, nil) -- xml is ratio to restLoad, final value is [t] |
813 | wheel.frictionScale = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#frictionScale", getXMLFloat, Utils.getNoNil(wheel.frictionScale, 1.0), nil, nil) |
814 | wheel.rotationDamping = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotationDamping", getXMLFloat, wheel.mass * 0.035, nil, nil) |
815 | wheel.tireGroundFrictionCoeff = 1.0 -- This will be changed dynamically based on the tire-ground pair |
816 | |
817 | local tireTypeName = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#tireType", getXMLString, "mud", nil, nil) |
818 | wheel.tireType = WheelsUtil.getTireType(tireTypeName) |
819 | if wheel.tireType == nil then |
820 | g_logManager:xmlWarning(self.configFileName, "Failed to find tire type '%s'. Defaulting to 'mud'!", tireTypeName) |
821 | wheel.tireType = WheelsUtil.getTireType("mud") |
822 | end |
823 | wheel.fieldDirtMultiplier = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#fieldDirtMultiplier", getXMLFloat, 75, nil, nil) |
824 | wheel.streetDirtMultiplier = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#streetDirtMultiplier", getXMLFloat, -150, nil, nil) |
825 | wheel.minDirtPercentage = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#minDirtPercentage", getXMLFloat, 0.35, nil, nil) |
826 | |
827 | wheel.smoothGroundRadius = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#smoothGroundRadius", getXMLFloat, Utils.getNoNil(wheel.smoothGroundRadius, math.max(0.6, wheel.width*0.75)), nil, nil) |
828 | wheel.versatileYRot = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#versatileYRot", getXMLBool, false, nil, nil) |
829 | wheel.forceVersatility = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#forceVersatility", getXMLBool, false, nil, nil) |
830 | wheel.supportsWheelSink = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#supportsWheelSink", getXMLBool, true, nil, nil) |
831 | wheel.maxWheelSink = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#maxWheelSink", getXMLFloat, math.huge, nil, nil) |
832 | |
833 | wheel.hasTireTracks = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#hasTireTracks", getXMLBool, false, nil, nil) |
834 | wheel.hasParticles = ConfigurationUtil.getConfigurationValue(xmlFile, key, wheelnamei, "#hasParticles", getXMLBool, false, nil, nil) |
835 | |
836 | local steeringKey = wheelnamei .. ".steering" |
837 | wheel.steeringNode = I3DUtil.indexToObject(self.components, ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#node", getXMLString, nil, nil, nil), self.i3dMappings) |
838 | wheel.steeringRotNode = I3DUtil.indexToObject(self.components, ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#rotNode", getXMLString, nil, nil, nil), self.i3dMappings) |
839 | wheel.steeringNodeMinTransX = ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#nodeMinTransX", getXMLFloat, nil, nil, nil) |
840 | wheel.steeringNodeMaxTransX = ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#nodeMaxTransX", getXMLFloat, nil, nil, nil) |
841 | wheel.steeringNodeMinRotY = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#nodeMinRotY", getXMLFloat, nil, nil, nil)) |
842 | wheel.steeringNodeMaxRotY = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringKey, "#nodeMaxRotY", getXMLFloat, nil, nil, nil)) |
843 | |
844 | local fenderKey = wheelnamei .. ".fender" |
845 | wheel.fenderNode = I3DUtil.indexToObject(self.components, ConfigurationUtil.getConfigurationValue(xmlFile, key, fenderKey, "#node", getXMLString, nil, nil, nil), self.i3dMappings) |
846 | wheel.fenderRotMax = ConfigurationUtil.getConfigurationValue(xmlFile, key, fenderKey, "#rotMax", getXMLFloat, nil, nil, nil) |
847 | wheel.fenderRotMin = ConfigurationUtil.getConfigurationValue(xmlFile, key, fenderKey, "#rotMin", getXMLFloat, nil, nil, nil) |
848 | |
849 | local steeringAxleKey = wheelnamei .. ".steeringAxle" |
850 | wheel.steeringAxleScale = ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringAxleKey, "#scale", getXMLFloat, 0, nil, nil) |
851 | wheel.steeringAxleRotMax = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringAxleKey, "#rotMax", getXMLFloat, 0, nil, nil)) |
852 | wheel.steeringAxleRotMin = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, steeringAxleKey, "#rotMin", getXMLFloat, -0, nil, nil)) |
853 | |
854 | wheel.rotSpeed = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotSpeed", getXMLFloat, nil, nil, nil)) |
855 | wheel.rotSpeedNeg = Utils.getNoNilRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotSpeedNeg", getXMLFloat, nil, nil, nil), nil) |
856 | wheel.rotMax = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotMax", getXMLFloat, nil, nil, nil)) |
857 | wheel.rotMin = MathUtil.degToRad(ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotMin", getXMLFloat, nil, nil, nil)) |
858 | |
859 | wheel.rotSpeedLimit = ConfigurationUtil.getConfigurationValue(xmlFile, key, physicsKey, "#rotSpeedLimit", getXMLFloat, nil, nil, nil) |
860 | else |
861 | g_logManager:xmlWarning(self.configFileName, "Invalid repr for wheel '%s'. Needs to be a child of a collision!", key..physicsKey) |
862 | end |
863 | else |
864 | g_logManager:xmlWarning(self.configFileName, "Invalid repr for wheel '%s'!", key..physicsKey) |
865 | end |
866 | return true |
867 | end |
2373 | function Wheels:loadWheelsSteeringDataFromXML(xmlFile, ackermannSteeringIndex) |
2374 | local spec = self.spec_wheels |
2375 | |
2376 | local key, _ = ConfigurationUtil.getXMLConfigurationKey(xmlFile, ackermannSteeringIndex, "vehicle.wheels.ackermannSteeringConfigurations.ackermannSteering", "vehicle.ackermannSteering", "ackermann") |
2377 | |
2378 | spec.steeringCenterNode = nil |
2379 | local rotSpeed = getXMLFloat(xmlFile, key.."#rotSpeed") |
2380 | local rotMax = getXMLFloat(xmlFile, key.."#rotMax") |
2381 | |
2382 | local centerX |
2383 | local centerZ |
2384 | local rotCenterWheel1 = getXMLInt(xmlFile, key.."#rotCenterWheel1") |
2385 | if rotCenterWheel1 ~= nil and spec.wheels[rotCenterWheel1] ~= nil then |
2386 | local wheel = spec.wheels[rotCenterWheel1] |
2387 | centerX, _, centerZ = localToLocal(wheel.node, self.components[1].node, wheel.positionX, wheel.positionY, wheel.positionZ) |
2388 | |
2389 | local rotCenterWheel2 = getXMLInt(xmlFile, key.."#rotCenterWheel2") |
2390 | if rotCenterWheel2 ~= nil and spec.wheels[rotCenterWheel2] ~= nil then |
2391 | local wheel2 = spec.wheels[rotCenterWheel2] |
2392 | local x, _, z = localToLocal(wheel2.node, self.components[1].node, wheel2.positionX, wheel2.positionY, wheel2.positionZ) |
2393 | centerX, centerZ = 0.5*(centerX + x), 0.5*(centerZ + z) |
2394 | end |
2395 | else |
2396 | local centerNode, _ = I3DUtil.indexToObject(self.components, getXMLString(xmlFile, key.."#rotCenterNode"), self.i3dMappings) |
2397 | if centerNode ~= nil then |
2398 | centerX, _, centerZ = localToLocal(centerNode, self.components[1].node, 0,0,0) |
2399 | spec.steeringCenterNode = centerNode |
2400 | else |
2401 | local p = StringUtil.getVectorNFromString(getXMLString(xmlFile, key.."#rotCenter", 2)) |
2402 | if p ~= nil then |
2403 | centerX = p[1] |
2404 | centerZ = p[2] |
2405 | end |
2406 | end |
2407 | end |
2408 | if spec.steeringCenterNode == nil then |
2409 | spec.steeringCenterNode = createTransformGroup("steeringCenterNode") |
2410 | link(self.components[1].node, spec.steeringCenterNode) |
2411 | if centerX ~= nil and centerZ ~= nil then |
2412 | setTranslation(spec.steeringCenterNode, centerX, 0, centerZ) |
2413 | end |
2414 | end |
2415 | |
2416 | if rotSpeed ~= nil and rotMax ~= nil and centerX ~= nil then |
2417 | rotSpeed = math.abs(math.rad(rotSpeed)) |
2418 | rotMax = math.abs(math.rad(rotMax)) |
2419 | |
2420 | -- find the wheel that should get the maximum steering (the one that results in the maximum turnign radius) |
2421 | local maxTurningRadius = 0 |
2422 | local maxTurningRadiusWheel = 0 |
2423 | for i, wheel in ipairs(spec.wheels) do |
2424 | if wheel.rotSpeed ~= 0 then |
2425 | local diffX, _, diffZ = localToLocal(wheel.node, spec.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ) |
2426 | local turningRadius = math.abs(diffZ)/math.tan(rotMax) + math.abs(diffX) |
2427 | if turningRadius >= maxTurningRadius then |
2428 | maxTurningRadius = turningRadius |
2429 | maxTurningRadiusWheel = i |
2430 | end |
2431 | end |
2432 | end |
2433 | self.maxRotation = math.max(Utils.getNoNil(self.maxRotation, 0), rotMax) |
2434 | self.maxTurningRadius = maxTurningRadius |
2435 | self.maxTurningRadiusWheel = maxTurningRadiusWheel |
2436 | self.wheelSteeringDuration = rotMax / rotSpeed |
2437 | |
2438 | if maxTurningRadiusWheel > 0 then |
2439 | for _, wheel in ipairs(spec.wheels) do |
2440 | |
2441 | if wheel.rotSpeed ~= 0 then |
2442 | local diffX, _, diffZ = localToLocal(wheel.node, spec.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ) |
2443 | |
2444 | local rotMaxI = math.atan(diffZ/(maxTurningRadius-diffX)) |
2445 | local rotMinI = -math.atan(diffZ/(maxTurningRadius+diffX)) |
2446 | |
2447 | local switchMaxMin = rotMinI > rotMaxI |
2448 | if switchMaxMin then |
2449 | rotMaxI, rotMinI = rotMinI, rotMaxI |
2450 | end |
2451 | |
2452 | wheel.rotMax = rotMaxI |
2453 | wheel.rotMin = rotMinI |
2454 | wheel.rotSpeed = rotMaxI/self.wheelSteeringDuration |
2455 | wheel.rotSpeedNeg = -rotMinI/self.wheelSteeringDuration |
2456 | |
2457 | if switchMaxMin then |
2458 | wheel.rotSpeed, wheel.rotSpeedNeg = -wheel.rotSpeedNeg, -wheel.rotSpeed |
2459 | end |
2460 | end |
2461 | end |
2462 | end |
2463 | end |
2464 | |
2465 | for _, wheel in ipairs(spec.wheels) do |
2466 | if wheel.rotSpeed ~= 0 then |
2467 | -- if both speed and rot have the same sign, we can reach it with the positive time |
2468 | if (wheel.rotMax >= 0) == (wheel.rotSpeed >= 0) then |
2469 | self.maxRotTime = math.max(wheel.rotMax/wheel.rotSpeed, self.maxRotTime) |
2470 | end |
2471 | if (wheel.rotMin >= 0) == (wheel.rotSpeed >= 0) then |
2472 | self.maxRotTime = math.max(wheel.rotMin/wheel.rotSpeed, self.maxRotTime) |
2473 | end |
2474 | |
2475 | -- if speed and rot have a different sign, we can reach it with the negative time |
2476 | local rotSpeedNeg = wheel.rotSpeedNeg |
2477 | if rotSpeedNeg == nil then |
2478 | rotSpeedNeg = wheel.rotSpeed |
2479 | end |
2480 | if (wheel.rotMax >= 0) ~= (rotSpeedNeg >= 0) then |
2481 | self.minRotTime = math.min(wheel.rotMax/rotSpeedNeg, self.minRotTime) |
2482 | end |
2483 | if (wheel.rotMin >= 0) ~= (rotSpeedNeg >= 0) then |
2484 | self.minRotTime = math.min(wheel.rotMin/rotSpeedNeg, self.minRotTime) |
2485 | end |
2486 | end |
2487 | |
2488 | wheel.fenderRotMax = Utils.getNoNilRad(wheel.fenderRotMax, wheel.rotMax) |
2489 | wheel.fenderRotMin = Utils.getNoNilRad(wheel.fenderRotMin, wheel.rotMin) |
2490 | wheel.steeringNodeMaxRot = math.max(wheel.rotMax, wheel.steeringAxleRotMax) |
2491 | wheel.steeringNodeMinRot = math.min(wheel.rotMin, wheel.steeringAxleRotMin) |
2492 | |
2493 | if wheel.rotSpeedLimit ~= nil then |
2494 | wheel.rotSpeedDefault = wheel.rotSpeed |
2495 | wheel.rotSpeedNegDefault = wheel.rotSpeedNeg |
2496 | wheel.currentRotSpeedAlpha = 1 |
2497 | end |
2498 | end |
2499 | end |
141 | function Wheels:onLoad(savegame) |
142 | local spec = self.spec_wheels |
143 | |
144 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.driveGroundParticleSystems", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#hasParticles") --FS13 to FS15 |
145 | |
146 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheelConfigurations.wheelConfiguration", "vehicle.wheels.wheelConfigurations.wheelConfiguration") --FS17 to FS19 |
147 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.rimColor", "vehicle.wheels.rimColor") --FS17 to FS19 |
148 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.hubColor", "vehicle.wheels.hubs.color0") --FS17 to FS19 |
149 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.dynamicallyLoadedWheels", "vehicle.wheels.dynamicallyLoadedWheels") --FS17 to FS19 |
150 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.ackermannSteeringConfigurations", "vehicle.wheels.ackermannSteeringConfigurations") --FS17 to FS19 |
151 | |
152 | -- wheel setup |
153 | local wheelConfigurationId = Utils.getNoNil(self.configurations["wheel"], 1) |
154 | local configKey = string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration(%d)", wheelConfigurationId -1) |
155 | local key = configKey..".wheels" |
156 | if self.configurations["wheel"] ~= nil and not hasXMLProperty(self.xmlFile, key) then |
157 | g_logManager:xmlWarning(self.configFileName, "Invalid wheelConfigurationId '%d'. Using default wheel config instead!", self.configurations["wheel"]) |
158 | -- reset to wheel config 1 to ensure wheels are present |
159 | wheelConfigurationId = 1 |
160 | key = string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration(%d)", 0) .. ".wheels" |
161 | end |
162 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.wheels.wheelConfigurations.wheelConfiguration", wheelConfigurationId , self.components, self) |
163 | |
164 | -- load configuration independent settings |
165 | local rimColorStr = getXMLString(self.xmlFile, "vehicle.wheels.rimColor") |
166 | if rimColorStr ~= nil then |
167 | spec.rimColor = ConfigurationUtil.getColorFromString(rimColorStr) |
168 | elseif getXMLBool(self.xmlFile, "vehicle.wheels.rimColor#useBaseColor") then |
169 | spec.rimColor = ConfigurationUtil.getColorByConfigId(self, "baseColor", self.configurations["baseColor"]) or ConfigurationUtil.getColorByConfigId(self, "baseMaterial", self.configurations["baseMaterial"]) |
170 | end |
171 | |
172 | if spec.rimColor ~= nil then |
173 | -- overwrite material from color string with nil unless explicitly defined in material attribute |
174 | spec.rimColor[4] = getXMLInt(self.xmlFile, "vehicle.wheels.rimColor#material") |
175 | end |
176 | |
177 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheels.wheel", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel") --FS17 to FS19 |
178 | |
179 | -- load hubs to hubs/repr nodes |
180 | spec.hubs = {} |
181 | self:loadHubs() |
182 | |
183 | self.maxRotTime = 0 |
184 | self.minRotTime = 0 |
185 | self.rotatedTimeInterpolator = InterpolatorValue:new(0) |
186 | |
187 | self.autoRotateBackSpeed = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, "", "#autoRotateBackSpeed", getXMLFloat, 1.0, nil, nil) |
188 | self.speedDependentRotateBack = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, "", "#speedDependentRotateBack", getXMLBool, true, nil, nil) |
189 | self.differentialIndex = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, "", "#differentialIndex", getXMLInt, nil, nil, nil) -- needed by Drivable |
190 | spec.ackermannSteeringIndex = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, "", "#ackermannSteeringIndex", getXMLInt, nil, nil, nil) |
191 | |
192 | --load surface sounds |
193 | if Utils.getNoNil(getXMLBool(self.xmlFile, key.."#hasSurfaceSounds"), true) then |
194 | local surfaceSoundLinkNodeStr = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, "", "#surfaceSoundLinkNode", getXMLString, "0>", nil, nil) |
195 | local surfaceSoundLinkNode = I3DUtil.indexToObject(self.components, surfaceSoundLinkNodeStr, self.i3dMappings) |
196 | spec.surfaceSounds = {} |
197 | spec.surfaceIdToSound = {} |
198 | spec.surfaceNameToSound = {} |
199 | spec.currentSurfaceSound = nil |
200 | for _, surfaceSound in pairs(g_currentMission.surfaceSounds) do |
201 | if surfaceSound.type == "wheel" and surfaceSound.sample ~= nil then |
202 | local sample = g_soundManager:cloneSample(surfaceSound.sample, surfaceSoundLinkNode, self) |
203 | sample.sampleName = surfaceSound.name |
204 | |
205 | table.insert(spec.surfaceSounds, sample) |
206 | spec.surfaceIdToSound[surfaceSound.materialId] = sample |
207 | spec.surfaceNameToSound[surfaceSound.name] = sample |
208 | end |
209 | end |
210 | end |
211 | |
212 | spec.wheelSmoothAccumulation = 0 |
213 | |
214 | spec.wheelCreationTimer = 0 |
215 | |
216 | spec.wheels = {} |
217 | spec.wheelChocks = {} |
218 | |
219 | -- load wheels |
220 | local i = 0 |
221 | while true do |
222 | local wheelnamei = string.format(".wheel(%d)", i) |
223 | if not hasXMLProperty(self.xmlFile, key..wheelnamei) then |
224 | break |
225 | end |
226 | |
227 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
228 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheelConfigurations.wheelConfiguration.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
229 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
230 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#configIndex", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#configId") --FS17 to FS19 |
231 | |
232 | local reprStr = ConfigurationUtil.getConfigurationValue(self.xmlFile, key, wheelnamei, ".physics#repr", getXMLString, nil, nil, nil) |
233 | if reprStr ~= nil then |
234 | local wheel = {} |
235 | wheel.repr = I3DUtil.indexToObject(self.components, reprStr, self.i3dMappings) |
236 | if wheel.repr ~= nil then |
237 | wheel.xmlIndex = i |
238 | |
239 | self:loadDynamicWheelDataFromXML(self.xmlFile, key, wheelnamei, wheel) |
240 | |
241 | self:finalizeWheel(wheel) |
242 | |
243 | table.insert(spec.wheels, wheel) |
244 | else |
245 | g_logManager:xmlWarning(self.configFileName, "Invalid wheel repr '%s'!", reprStr) |
246 | end |
247 | else |
248 | g_logManager:xmlWarning(self.configFileName, "No repr node given for wheel '%s'!", wheelnamei) |
249 | end |
250 | i = i+1 |
251 | end |
252 | |
253 | -- load non physical wheels |
254 | spec.dynamicallyLoadedWheels = {} |
255 | local i=0 |
256 | while true do |
257 | local baseName = string.format("vehicle.wheels.dynamicallyLoadedWheels.dynamicallyLoadedWheel(%d)", i) |
258 | if not hasXMLProperty(self.xmlFile, baseName) then |
259 | break |
260 | end |
261 | |
262 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, self.configFileName, baseName .. "#configIndex", baseName .. "#configId") --FS17 to FS19 |
263 | |
264 | local dynamicallyLoadedWheel = {} |
265 | if self:loadNonPhysicalWheelFromXML(dynamicallyLoadedWheel, self.xmlFile, baseName) then |
266 | table.insert(spec.dynamicallyLoadedWheels, dynamicallyLoadedWheel) |
267 | end |
268 | i = i + 1 |
269 | end |
270 | |
271 | spec.networkTimeInterpolator = InterpolationTime:new(1.2) |
272 | |
273 | -- find opposite wheel |
274 | local numWheels = table.getn(spec.wheels) |
275 | for iWheel=1,numWheels do |
276 | local wheel1 = spec.wheels[iWheel] |
277 | if wheel1.oppositeWheelIndex == nil then |
278 | for jWheel=1,numWheels do |
279 | if iWheel ~= jWheel then |
280 | local wheel2 = spec.wheels[jWheel] |
281 | if math.abs(wheel1.positionX + wheel2.positionX) < 0.1 and math.abs(wheel1.positionZ - wheel2.positionZ) < 0.1 and math.abs(wheel1.positionY - wheel2.positionY) < 0.1 then |
282 | wheel1.oppositeWheelIndex = jWheel |
283 | wheel2.oppositeWheelIndex = iWheel |
284 | break |
285 | end |
286 | end |
287 | end |
288 | end |
289 | end |
290 | |
291 | -- |
292 | self:loadWheelsSteeringDataFromXML(self.xmlFile, spec.ackermannSteeringIndex) |
293 | |
294 | |
295 | SpecializationUtil.raiseEvent(self, "onFinishedWheelLoading", self.xmlFile, key) |
296 | |
297 | spec.brakePedal = 0 |
298 | spec.forceIsActiveTime = 3000 |
299 | spec.forceIsActiveTimer = 0 |
300 | spec.dirtyFlag = self:getNextDirtyFlag() |
301 | end |
1188 | function Wheels:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
1189 | local spec = self.spec_wheels |
1190 | |
1191 | for _, wheel in pairs(spec.wheels) do |
1192 | if wheel.rotSpeedLimit ~= nil then |
1193 | local dir = -1 |
1194 | if self:getLastSpeed() <= wheel.rotSpeedLimit then |
1195 | dir = 1 |
1196 | end |
1197 | |
1198 | wheel.currentRotSpeedAlpha = MathUtil.clamp(wheel.currentRotSpeedAlpha + dir*(dt/1000), 0, 1) |
1199 | wheel.rotSpeed = wheel.rotSpeedDefault * wheel.currentRotSpeedAlpha |
1200 | wheel.rotSpeedNeg = wheel.rotSpeedNegDefault * wheel.currentRotSpeedAlpha |
1201 | end |
1202 | end |
1203 | |
1204 | if self.isClient then |
1205 | local speed = self:getLastSpeed() |
1206 | local groundWetness = g_currentMission.environment.weather:getGroundWetness() |
1207 | local groundIsWet = groundWetness > 0.2 |
1208 | for _,wheel in pairs(spec.wheels) do |
1209 | if wheel.driveGroundParticleSystems ~= nil then |
1210 | local states = wheel.driveGroundParticleStates |
1211 | local enableSoilPS = false |
1212 | if wheel.lastTerrainValue > 0 and wheel.lastTerrainValue < 5 then |
1213 | enableSoilPS = (speed > 1) -- and wheel.sink > 0 |
1214 | end |
1215 | local sizeScale = 2 * wheel.width * wheel.radiusOriginal |
1216 | |
1217 | states.driving_dry = enableSoilPS |
1218 | states.driving_wet = enableSoilPS and groundIsWet |
1219 | states.driving_dust = not groundIsWet |
1220 | |
1221 | for psName, state in pairs(states) do |
1222 | local typedPs = wheel.driveGroundParticleSystems[psName] |
1223 | |
1224 | for _, ps in ipairs(typedPs) do |
1225 | if state then |
1226 | if self.movingDirection < 0 then |
1227 | setRotation(ps.emitterShape, 0, math.pi+wheel.steeringAngle, 0) |
1228 | else |
1229 | setRotation(ps.emitterShape, 0, wheel.steeringAngle, 0) |
1230 | end |
1231 | |
1232 | local scale |
1233 | if psName ~= "driving_dust" then |
1234 | local wheelSpeed = MathUtil.rpmToMps(wheel.netInfo.xDriveSpeed / (2*math.pi) * 60, wheel.radius) |
1235 | local wheelSlip = math.pow(wheelSpeed/self.lastSpeedReal, 2.5) |
1236 | scale = self:getDriveGroundParticleSystemsScale(ps, wheelSpeed) * wheelSlip |
1237 | else |
1238 | scale = self:getDriveGroundParticleSystemsScale(ps, self.lastSpeedReal) |
1239 | end |
1240 | |
1241 | if ps.isTintable then |
1242 | -- interpolate between different ground colors to avoid unrealisitic particle color changes |
1243 | if ps.lastColor == nil then |
1244 | ps.lastColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1245 | ps.targetColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1246 | ps.currentColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1247 | ps.alpha = 1 |
1248 | end |
1249 | |
1250 | if ps.alpha ~= 1 then |
1251 | ps.alpha = math.min(ps.alpha + dt/1000, 1) |
1252 | ps.currentColor = {MathUtil.vector3ArrayLerp(ps.lastColor, ps.targetColor, ps.alpha)} |
1253 | if ps.alpha == 1 then |
1254 | ps.lastColor[1] = ps.currentColor[1] |
1255 | ps.lastColor[2] = ps.currentColor[2] |
1256 | ps.lastColor[3] = ps.currentColor[3] |
1257 | end |
1258 | end |
1259 | |
1260 | 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 |
1261 | ps.alpha = 0 |
1262 | ps.targetColor[1] = ps.wheel.lastColor[1] |
1263 | ps.targetColor[2] = ps.wheel.lastColor[2] |
1264 | ps.targetColor[3] = ps.wheel.lastColor[3] |
1265 | end |
1266 | end |
1267 | |
1268 | if scale > 0 then |
1269 | ParticleUtil.setEmittingState(ps, true) |
1270 | if ps.isTintable then |
1271 | setShaderParameter(ps.shape, "psColor", ps.currentColor[1], ps.currentColor[2], ps.currentColor[3], 1, false) |
1272 | end |
1273 | else |
1274 | ParticleUtil.setEmittingState(ps, false) |
1275 | end |
1276 | |
1277 | -- emit count |
1278 | local maxSpeed = (50 / 3.6) |
1279 | local circum = wheel.radiusOriginal |
1280 | local maxWheelRpm = maxSpeed / circum |
1281 | local wheelRotFactor = Utils.getNoNil(wheel.netInfo.xDriveSpeed, 0) / maxWheelRpm |
1282 | local emitScale = scale * wheelRotFactor * sizeScale |
1283 | ParticleUtil.setEmitCountScale(ps, MathUtil.clamp(emitScale, ps.minScale, ps.maxScale)) |
1284 | |
1285 | -- speeds |
1286 | local speedFactor = 1.0 |
1287 | ParticleUtil.setParticleSystemSpeed(ps, ps.particleSpeed * speedFactor) |
1288 | ParticleUtil.setParticleSystemSpeedRandom(ps, ps.particleRandomSpeed * speedFactor) |
1289 | else |
1290 | ParticleUtil.setEmittingState(ps, false) |
1291 | end |
1292 | end |
1293 | |
1294 | states[psName] = false |
1295 | end |
1296 | end |
1297 | end |
1298 | end |
1299 | end |
1633 | function Wheels:updateWheelDensityMapHeight(wheel, dt) |
1634 | if not self.isServer then |
1635 | return |
1636 | end |
1637 | |
1638 | local spec = self.spec_wheels |
1639 | |
1640 | -- smoothing of tipAny |
1641 | local wheelSmoothAmount = 0 |
1642 | --if self.lastSpeedReal > 0.0002 and next(spec.wheels) ~= nil then -- start smoothing if driving faster than 0.7km/h |
1643 | if self.lastSpeedReal > 0.0002 then -- start smoothing if driving faster than 0.7km/h |
1644 | wheelSmoothAmount = spec.wheelSmoothAccumulation + math.max(self.lastMovedDistance * 1.2, 0.0003*dt) -- smooth 1.2m per meter driving or at least 0.3m/s |
1645 | local rounded = DensityMapHeightUtil.getRoundedHeightValue(wheelSmoothAmount) |
1646 | spec.wheelSmoothAccumulation = wheelSmoothAmount - rounded |
1647 | else |
1648 | spec.wheelSmoothAccumulation = 0 |
1649 | end |
1650 | |
1651 | if wheelSmoothAmount == 0 then |
1652 | return |
1653 | end |
1654 | |
1655 | -- using netinfo because of tire deformation |
1656 | local wx, wy, wz = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z |
1657 | wy = wy - wheel.radius |
1658 | wx = wx + wheel.xOffset |
1659 | wx, wy, wz = localToWorld(wheel.node, wx,wy,wz) |
1660 | |
1661 | if wheel.smoothGroundRadius > 0 then --and wheelSmoothAmount > 0 then |
1662 | local smoothYOffset = -0.1 |
1663 | local heightType = DensityMapHeightUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, wheel.smoothGroundRadius) |
1664 | if heightType ~= nil and heightType.allowsSmoothing then |
1665 | local terrainHeightUpdater = g_densityMapHeightManager:getTerrainDetailHeightUpdater() |
1666 | if terrainHeightUpdater ~= nil then |
1667 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz) |
1668 | local physicsDeltaHeight = wy - terrainHeight |
1669 | local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale |
1670 | deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset) |
1671 | deltaHeight = math.max(deltaHeight + smoothYOffset, 0) |
1672 | local internalHeight = terrainHeight + deltaHeight |
1673 | smoothDensityMapHeightAtWorldPos(terrainHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, wheel.smoothGroundRadius, wheel.smoothGroundRadius + 1.2) |
1674 | if Vehicle.debugRendering then |
1675 | DebugUtil.drawDebugCircle(wx,internalHeight,wz, wheel.smoothGroundRadius, 10) |
1676 | end |
1677 | end |
1678 | end |
1679 | if wheel.additionalWheels ~= nil then |
1680 | for _, additionalWheel in pairs(wheel.additionalWheels) do |
1681 | local refNode = wheel.repr |
1682 | local xShift,yShift,zShift = localToLocal(additionalWheel.wheelTire, refNode, additionalWheel.xOffset,0,0) |
1683 | local wx,wy,wz = localToWorld(refNode, xShift, yShift-additionalWheel.radius, zShift) |
1684 | local heightType = DensityMapHeightUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, additionalWheel.smoothGroundRadius) |
1685 | if heightType ~= nil and heightType.allowsSmoothing then |
1686 | local terrainHeightUpdater = g_densityMapHeightManager:getTerrainDetailHeightUpdater() |
1687 | if terrainHeightUpdater ~= nil then |
1688 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz) |
1689 | local physicsDeltaHeight = wy - terrainHeight |
1690 | local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale |
1691 | deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset) |
1692 | deltaHeight = math.max(deltaHeight + smoothYOffset, 0) |
1693 | local internalHeight = terrainHeight + deltaHeight |
1694 | smoothDensityMapHeightAtWorldPos(terrainHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, additionalWheel.smoothGroundRadius, additionalWheel.smoothGroundRadius + 1.2) |
1695 | if Vehicle.debugRendering then |
1696 | DebugUtil.drawDebugCircle(wx,internalHeight,wz, additionalWheel.smoothGroundRadius, 10) |
1697 | end |
1698 | end |
1699 | end |
1700 | end |
1701 | end |
1702 | end |
1703 | |
1704 | end |
1708 | function Wheels:updateWheelDestruction(wheel, dt) |
1709 | if self:getIsWheelFoliageDestructionAllowed(wheel) then |
1710 | -- limit size of destruction |
1711 | local width = 0.5 * wheel.width |
1712 | local length = math.min(0.5, 0.5 * wheel.width) |
1713 | local x, _, z = localToLocal(wheel.driveNode, wheel.repr, 0, 0, 0) |
1714 | local x0, y0, z0 = localToWorld(wheel.repr, x + width, 0, z - length) |
1715 | local x1, y1, z1 = localToWorld(wheel.repr, x - width, 0, z - length) |
1716 | local x2, y2, z2 = localToWorld(wheel.repr, x + width, 0, z + length) |
1717 | |
1718 | if g_currentMission.accessHandler:canFarmAccessLand(self:getActiveFarm(), x0, z0) then |
1719 | self:destroyFruitArea(x0, z0, x1, z1, x2, z2) |
1720 | end |
1721 | |
1722 | if VehicleDebug.state == VehicleDebug.DEBUG_PHYSICS then |
1723 | drawDebugLine(x0, y0, z0, 1, 0, 0, x1, y1, z1, 1, 0, 0) |
1724 | drawDebugLine(x0, y0, z0, 1, 1, 0, x2, y2, z2, 1, 1, 0) |
1725 | end |
1726 | |
1727 | if wheel.additionalWheels ~= nil then |
1728 | for _,additionalWheel in pairs(wheel.additionalWheels) do |
1729 | local width = 0.5 * additionalWheel.width |
1730 | local length = math.min(0.5, 0.5 * additionalWheel.width) |
1731 | local refNode = wheel.node |
1732 | |
1733 | if wheel.repr ~= wheel.driveNode then |
1734 | refNode = wheel.repr |
1735 | end |
1736 | |
1737 | local xShift, yShift, zShift = localToLocal(additionalWheel.wheelTire, refNode, 0, 0, 0) |
1738 | local x0, y0, z0 = localToWorld(refNode, xShift + width, yShift, zShift - length) |
1739 | local x1, y1, z1 = localToWorld(refNode, xShift - width, yShift, zShift - length) |
1740 | local x2, y2, z2 = localToWorld(refNode, xShift + width, yShift, zShift + length) |
1741 | |
1742 | if g_farmlandManager:getIsOwnedByFarmAtWorldPosition(self:getActiveFarm(), x0, z0) then |
1743 | self:destroyFruitArea(x0, z0, x1, z1, x2, z2) |
1744 | end |
1745 | |
1746 | if VehicleDebug.state == VehicleDebug.DEBUG_PHYSICS then |
1747 | drawDebugLine(x0, y0, z0, 1, 0, 0, x1, y1, z1, 1, 0, 0) |
1748 | drawDebugLine(x0, y0, z0, 1, 1, 0, x2, y2, z2, 1, 1, 0) |
1749 | end |
1750 | end |
1751 | end |
1752 | end |
1753 | end |
1785 | function Wheels:updateWheelSink(wheel, dt) |
1786 | if wheel.supportsWheelSink then |
1787 | if self.isServer and self.isAddedToPhysics then |
1788 | local spec = self.spec_wheels |
1789 | |
1790 | -- map noise to an asbolute value or to a certain percentage of the wheel radius? |
1791 | local maxSink = 0.20 |
1792 | local sinkTarget = 0 |
1793 | |
1794 | if wheel.mirroredWheel == nil then |
1795 | for _, mirWheel in ipairs(spec.wheels) do |
1796 | if mirWheel.mirroredWheel == nil and mirWheel ~= wheel then -- only the first wheel got the mirrored one |
1797 | local x1, y1, z1 = localToLocal(wheel.node, wheel.repr, 0, 0, 0) |
1798 | local x2, y2, z2 = localToLocal(wheel.node, mirWheel.repr, 0, 0, 0) |
1799 | local diff = math.abs(x1-(-x2)) + math.abs(y1-y2) + math.abs(z1-z2) |
1800 | if diff < 0.25 then |
1801 | wheel.mirroredWheel = mirWheel |
1802 | mirWheel.invMirroredWheel = wheel |
1803 | end |
1804 | end |
1805 | end |
1806 | end |
1807 | |
1808 | local force = false |
1809 | |
1810 | if wheel.contact ~= Wheels.WHEEL_NO_CONTACT and self:getLastSpeed() > 0.3 then |
1811 | wheel.avgSink = nil |
1812 | |
1813 | local width = 0.25 * wheel.width |
1814 | local length = 0.25 * wheel.width |
1815 | |
1816 | local x,_,z = localToLocal(wheel.driveNode, wheel.repr, 0,0,0) |
1817 | local x0,_,z0 = localToWorld(wheel.repr, x + width, 0, z - length) |
1818 | local x1,_,z1 = localToWorld(wheel.repr, x - width, 0, z - length) |
1819 | local x2,_,z2 = localToWorld(wheel.repr, x + width, 0, z + length) |
1820 | |
1821 | local x,z, widthX,widthZ, heightX,heightZ = MathUtil.getXZWidthAndHeight(x0, z0, x1, z1, x2, z2) |
1822 | local density, area = FSDensityMapUtil.getFieldValue(x0, z0, x1, z1, x2, z2) |
1823 | |
1824 | local terrainValue = 0 |
1825 | if area > 0 then |
1826 | terrainValue = math.floor(density/area + 0.5) |
1827 | end |
1828 | wheel.lastTerrainValue = terrainValue |
1829 | |
1830 | local noiseValue = 0 |
1831 | if terrainValue > 0 then |
1832 | local xPerlin = x + 0.5*widthX + 0.5*heightX |
1833 | local zPerlin = z + 0.5*widthZ + 0.5*heightZ |
1834 | -- Round to 1cm to avoid sliding when not moving |
1835 | xPerlin = math.floor(xPerlin*100)*0.01 |
1836 | zPerlin = math.floor(zPerlin*100)*0.01 |
1837 | |
1838 | local perlinNoise = Wheels.perlinNoiseSink |
1839 | local noiseSink = 0.5 * (1 + getPerlinNoise2D(xPerlin*perlinNoise.randomFrequency, zPerlin*perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed)) |
1840 | |
1841 | perlinNoise = Wheels.perlinNoiseWobble |
1842 | local noiseWobble = 0.5 * (1 + getPerlinNoise2D(xPerlin*perlinNoise.randomFrequency, zPerlin*perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed)) |
1843 | |
1844 | -- estimiate pressure on surface |
1845 | local gravity = 9.81 |
1846 | local tireLoad = getWheelShapeContactForce(wheel.node, wheel.wheelShape) |
1847 | if tireLoad ~= nil then |
1848 | local nx,ny,nz = getWheelShapeContactNormal(wheel.node, wheel.wheelShape) |
1849 | local dx,dy,dz = localDirectionToWorld(wheel.node, 0,-1,0) |
1850 | tireLoad = -tireLoad*MathUtil.dotProduct(dx,dy,dz, nx,ny,nz) |
1851 | |
1852 | tireLoad = tireLoad + math.max(ny*gravity, 0.0) * wheel.mass -- add gravity force of tire |
1853 | else |
1854 | tireLoad = 0 |
1855 | end |
1856 | tireLoad = tireLoad / gravity |
1857 | |
1858 | local loadFactor = math.min(1.0, math.max(0, tireLoad / wheel.maxLatStiffnessLoad)) |
1859 | |
1860 | local wetnessFactor = g_currentMission.environment.weather:getGroundWetness() |
1861 | noiseSink = 0.333*(2*loadFactor + wetnessFactor) * noiseSink |
1862 | |
1863 | noiseValue = math.max(noiseSink, noiseWobble) |
1864 | end |
1865 | |
1866 | maxSink = Wheels.MAX_SINK[terrainValue] or maxSink |
1867 | |
1868 | -- plowing effect |
1869 | if terrainValue == 2 and wheel.oppositeWheelIndex ~= nil then |
1870 | local oppositeWheel = spec.wheels[wheel.oppositeWheelIndex] |
1871 | if oppositeWheel.lastTerrainValue ~= nil and oppositeWheel.lastTerrainValue ~= 2 then |
1872 | maxSink = maxSink * 1.3 |
1873 | end |
1874 | end |
1875 | |
1876 | sinkTarget = math.min(0.2*wheel.radiusOriginal, math.min(maxSink, wheel.maxWheelSink) * noiseValue) |
1877 | elseif self:getLastSpeed() < 0.3 then |
1878 | -- if we are standing still we try to synchronize the left and right wheel radius to avoid wobbeling |
1879 | if wheel.mirroredWheel ~= nil then |
1880 | if wheel.avgSink == nil then |
1881 | wheel.avgSink = (wheel.mirroredWheel.sinkTarget + wheel.sinkTarget) / 2 |
1882 | end |
1883 | sinkTarget = wheel.avgSink |
1884 | force = wheel.sink ~= wheel.avgSink |
1885 | wheel.sinkTarget = sinkTarget |
1886 | elseif wheel.invMirroredWheel ~= nil then |
1887 | if wheel.invMirroredWheel.avgSink ~= nil then |
1888 | sinkTarget = wheel.invMirroredWheel.avgSink |
1889 | force = wheel.sink ~= wheel.invMirroredWheel.avgSink |
1890 | wheel.sinkTarget = sinkTarget |
1891 | end |
1892 | end |
1893 | end |
1894 | |
1895 | if wheel.sinkTarget < sinkTarget then |
1896 | wheel.sinkTarget = math.min(sinkTarget, wheel.sinkTarget + (0.05 * math.min(30, math.max(0, self:getLastSpeed()-0.2)) * (dt/1000))) |
1897 | elseif wheel.sinkTarget > sinkTarget then |
1898 | wheel.sinkTarget = math.max(sinkTarget, wheel.sinkTarget - (0.05 * math.min(30, math.max(0, self:getLastSpeed()-0.2)) * (dt/1000))) |
1899 | end |
1900 | |
1901 | if math.abs(wheel.sink - wheel.sinkTarget) > 0.001 or force then |
1902 | wheel.sink = wheel.sinkTarget |
1903 | |
1904 | local radius = wheel.radiusOriginal - wheel.sink |
1905 | if radius ~= wheel.radius then |
1906 | wheel.radius = radius |
1907 | if self.isServer then |
1908 | self:setWheelPositionDirty(wheel) |
1909 | |
1910 | local sinkFactor = (wheel.sink/maxSink) * (1 + (0.4 * g_currentMission.environment.weather:getGroundWetness())) |
1911 | wheel.sinkLongStiffnessFactor = (1.0 - (0.10 * sinkFactor)) |
1912 | wheel.sinkLatStiffnessFactor = (1.0 - (0.20 * sinkFactor)) |
1913 | self:setWheelTireFrictionDirty(wheel) |
1914 | end |
1915 | end |
1916 | end |
1917 | end |
1918 | end |
1919 | end |